精华内容
下载资源
问答
  • 依赖属性和附加属性

    2021-05-15 16:39:50
    依赖属性dependency property 为什么需要依赖属性?是为了节约资源(节省内存开销)。 DependencyObject类 Object->DispatcherObject->DependencyObject 表示参与依赖属性系统的对象。所有依赖属性都...

    依赖属性dependency property

    为什么需要依赖属性?是为了节约资源(节省内存开销)。

     

    DependencyObject类

    Object->DispatcherObject->DependencyObject

    表示参与依赖属性系统的对象。所有依赖属性都派生自次类。

    DependencyObject类在其多个派生类上启用 Windows Presentation Foundation (WPF) 属性系统服务。

    属性系统的主要功能是计算属性值,并提供有关已更改值的系统通知。 参与属性系统的另一个关键类是DependencyProperty。 DependencyProperty允许将依赖项属性注册到属性系统中,并提供有关每个依赖项属性的标识和信息,而DependencyObject作为基类使对象可以使用依赖项属性。

    DependencyObject 服务和特征包括:

    • 依赖属性承载支持。 通过调用 Register 方法,并将该方法的返回值存储为类中的公共静态字段,来注册依赖属性。
    • 附加属性托管支持。 可以通过调用方法来注册附加属性 RegisterAttached ,并将该方法的返回值存储为类中的公共静态只读字段。 (还有其他成员要求;请注意,这表示附加属性的 WPF 特定实现。 有关详细信息,请参阅 附加属性概述。然后可以在派生自的任何类上设置附加属性 DependencyObject ) 。
    • 获取、设置和清除中存在的任何依赖项属性值的实用工具方法 DependencyObject 。
    • 元数据、强制值支持、属性更改通知和替代依赖属性或附加属性的回调。 此外, DependencyObject 类还有助于依赖项属性的每个所有者的属性元数据。
    • 派生自、或的类的公共 ContentElement 基类 Freezable Visual 。 (UIElement ,另一个基元素类具有包含的类层次结构 Visual 。 )
    DependencyObject属性
    名称备注权限

    DependencyObjectType

    获取 DependencyObjectType 包装此实例的 CLR 类型的。get;

    IsSealed

    获取一个值,该值指示此实例当前是否为密封的(只读)。get;
    DependencyObject方法
    名称备注权限

    ClearValue

    清除属性的本地值。public

    CoerceValue

    对指定依赖属性的值进行强制。 通过对调用方 DependencyObject 上存在的依赖属性的属性元数据中所指定的任何 CoerceValueCallback 函数进行调用来完成此操作。public

    Equals

    确定提供的 DependencyObject 是否等效于当前 DependencyObjectpublic

    GetHashCode

    获取此 DependencyObject 的哈希代码。public

    GetLocalValueEnumerator

    创建一个专用的枚举数,用于确定哪些依赖项属性在此 DependencyObject 上具有以本地方式设置的值。public

    GetValue

    对 DependencyObject 的此实例返回依赖属性的当前有效值。public

    InvalidateProperty

    重新评估指定依赖属性的有效值。public

    OnPropertyChanged

    每当更新此 DependencyObject 的任何依赖属性的有效值时调用。 更改的特定依赖属性将在事件数据中报告。protected

    ReadLocalValue

    如果存在,则返回依赖属性的本地值。public

    SetCurrentValue

    设置依赖属性的值而不更改其值源。public

    SetValue

    设置依赖属性的本地值。public

    ShouldSerializeProperty

    返回一个值,该值指示序列化进程是否应序列化所提供的依赖属性的值。protected

     

    DependencyProperty类

    Object->DependencyProperty

    表示可通过诸如样式、数据绑定、动画和继承等方法设置的属性。

    • 可以设置样式的属性。有关更多信息,请参见样式和模板

    • 可以通过数据绑定设置该属性。有关数据绑定依赖项属性的更多信息,请参见如何:绑定两个控件的属性

    • 可以使用动态资源引用来设置该属性。有关更多信息,请参见XAML资源

    • 该属性可以自动从元素树中的父元素继承其值。有关更多信息,请参见属性值继承

    • 该属性可以设置动画。有关更多信息,请参见动画概述

    • 该属性可以报告该属性的先前值已​​更改的时间,并且该属性值可以被强制。有关更多信息,请参见依赖项属性回调和验证

    • 该属性向WPF报告信息,例如,更改属性值是否需要布局系统重新组合元素的外观。

    • 该属性在WPF Designer for Visual Studio中获得支持。例如,可以在“属性”窗口中编辑该属性

    • 附加属性是使任何对象能够将信息报告给定义该附加属性的类型的属性。在WPF中,任何从DependencyObject继承的类型都可以使用附加属性,而不管该类型是否从定义该属性的类型继承。附加属性是XAML语言的功能。要在XAML中设置附加属性,请使用ownerTypepropertyName语法。附加属性的一个示例是DockPanel.Dock属性。如果要创建可在所有DependencyObject类型上使用的属性,则应创建一个附加属性。要了解有关附加属性的更多信息,包括如何创建它们,请参见附加属性概述

     

    DependencyProperty.字段
    名称备注权限

    UnsetValue

    指定一个由 WPF 属性系统使用的静态值,而不是 null 指示该属性存在,但不具有属性系统设置的值。public
    DependencyProperty属性
    名称备注权限

    DefaultMetadata

    获取依赖项对象的默认元数据。get;

    GlobalIndex

    获取唯一标识依赖项对象的内部生成值。get;

    Name

    获取依赖属性的名称。get;

    OwnerType

    获取使用属性系统注册依赖属性或者将自己作为属性所有者添加的对象的类型。get;

    PropertyType

    获取依赖项对象用于其值的类型。get;

    ReadOnly

    获取一个值,该值指示由此 DependencyProperty 实例标识的依赖项对象是否为只读依赖项对象。get;

    ValidateValueCallback

    获取依赖项对象的值验证回调。get;

     

    DependencyProperty方法
    名称备注权限

    AddOwner

    将另一种类型添加为已注册到一种类型的依赖属性的所有者。public

    GetHashCode

    返回此 DependencyProperty 的哈希代码。public

    GetMetadata

    返回与此依赖属性关联的的元数据(只要它对于特定的类型存在)。 它可以是在其中首次注册依赖属性的类型、随后将其添加到的类型,或在其中通过继承获取依赖属性而已专门重写元数据的类型。public

    IsValidType

    确定指定的值对于该依赖项对象的类型是否可接受(与原依赖项对象注册中提供的属性类型相对照)。public

    IsValidValue

    确定所提供的值通过基本类型检查后是否被属性类型接受,以及它是否有可能在该类型的值的允许范围以内。public

    OverrideMetadata

    当此依赖属性位于指定类型的实例上时为其提供替换元数据(而不是在最初注册依赖属性时提供的元数据)。public

    Register

    注册依赖属性。public

    RegisterAttached

    在属性系统上注册附加属性。public

    RegisterAttachedReadOnly

    注册只读附加属性。public

    RegisterReadOnly

    将依赖属性注册为只读依赖属性。public

    ToString

    返回依赖属性的字符串表示形式。public

     

     

    PropertyMetadata类

    Object->PropertyMetadata

    定义依赖属性在应用于特定类型(包括该属性向其注册的条件)时行为的某些方面。

    可以在调用Register方法(或附加属性或只读依赖项属性的变体)时在依赖项属性注册期间定义属性元数据,并在调用OverrideMetadata方法时在原始所有者注册后定义和使用属性元数据。AddOwner还采用属性元数据。

    此类是一个具体的基类,可以在每个这些调用中使用。但是,使用诸如FrameworkPropertyMetadata之类的派生类之一指定元数据是非常普遍的。这些派生类支持作为布尔属性值携带的更详细的元数据,这些元数据对于检测或启用仅在WPF框架级别实现的某些属性系统和布局行为很有用。

    此类的几个属性可读写到对象模型,但只能在实例在属性系统操作(例如RegisterOverrideMetadata)中使用之前写入。这些属性中的每一个也可以由构造函数设置,但是可以公开,以便Merge方法实现可以对其进行设置。

     

    PropertyMetadata 属性
    名称备注权限

    CoerceValueCallback

    获取或设置对此元数据中所指定 CoerceValueCallback 实现的引用。get; set;

    DefaultValue

    获取或设置依赖属性的默认值。get; set;

    IsSealed

    获取一个值,该值确定是否已通过某种方式将元数据应用于属性,从而导致该元数据实例变为不可变状态。get;

    PropertyChangedCallback

    获取或设置对此元数据中所指定 PropertyChangedCallback 实现的引用。get; set;
    PropertyMetadata方法
    名称备注权限

    Merge

    将此元数据与基元数据合并。protected

    OnApply

    当此元数据已经应用到一个属性时(这表明正在密封元数据)调用。protected

    依赖属性

    创建依赖属性

    • 创建的类派生自DependencyObject 
    • 成员变量同时被public static readonly修饰
    • 用DependencyProperty.Register方法注册依赖属性

     

    设置、获取依赖属性

    快速创建

    输入propdp,二次按下Tab,如下,进行相应修改

        public int MyProperty
        {
            get { return (int)GetValue(MyPropertyProperty); }
            set { SetValue(MyPropertyProperty, value); }
        }
    
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));

    PropertyMetadata

    CoerceValueCallback:依赖属性值被强制改变时,此委托会被调用。此委托可关联一个相应函数。

    DefaultValue:依赖属性未被显式赋值时,若读取之则获取此默认值,不设此值会抛出异常。

    IsSealed:控制PropertyMetadata的属性值是否可以被修改,默认值是true。

    PropertyChangedCallback:依赖属性的值被改变之后,此委托会被调用,此委托可以关联一个相应函数。

    依赖属性的DefaultMetadata只能通过Register方法的第四个参数进行赋值,一旦赋值不能改变(只读)。如果想要新的PropertyMetadata替换,需要使用DependencyProperty.OverrideMetadata方法。

     

    DependencyProperty.Register方法

    public static System.Windows.DependencyPropertyKey RegisterReadOnly (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata typeMetadata);
    
    public static System.Windows.DependencyPropertyKey RegisterReadOnly (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata typeMetadata, System.Windows.ValidateValueCallback validateValueCallback);
    
    public static System.Windows.DependencyProperty Register (string name, Type propertyType, Type ownerType);
    
    public static readonly DependencyProperty IsDirtyProperty = DependencyProperty.Register(
      "IsDirty",
      typeof(Boolean),
      typeof(AquariumObject3)
    );
    
    public static System.Windows.DependencyProperty Register (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata typeMetadata, System.Windows.ValidateValueCallback validateValueCallback);
    
    public static System.Windows.DependencyProperty RegisterAttached (string name, Type propertyType, Type ownerType);
    
    public static System.Windows.DependencyProperty RegisterAttached (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata defaultMetadata);
    
    public static System.Windows.DependencyProperty RegisterAttached (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata defaultMetadata, System.Windows.ValidateValueCallback validateValueCallback);
    
    public static System.Windows.DependencyPropertyKey RegisterAttachedReadOnly (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata defaultMetadata);
    
    public static System.Windows.DependencyPropertyKey RegisterAttachedReadOnly (string name, Type propertyType, Type ownerType, System.Windows.PropertyMetadata defaultMetadata, System.Windows.ValidateValueCallback validateValueCallback);
    name:要注册的依赖属性的名称。
    propertyType:属性的类型。
    ownerType:正在注册依赖属性的所有者类型。
    typeMetadata:依赖属性的属性元数据。
    validateValueCallback:对回调的引用,除了典型的类型验证之外,该引用还应执行依赖属性值的任何自定义验证。

    FrameworkPropertyMetadata

    Object->PropertyMetadata->UIPropertyMetadata->FrameworkPropertyMetadata

    使用FrameworkPropertyMetadata对象配置创建的依赖属性的附加功能。

    为依赖属性报告或应用元数据(从而专门添加特定于框架的属性系统特征)。

    此类从PropertyMetadata派生(通过UIPropertyMetadata)。对于大多数WPF框架级应用程序开发目的,FrameworkPropertyMetadata是用于依赖项属性元数据的类型,而不是基本元数据类型PropertyMetadataUIPropertyMetadata。对于现有的依赖项属性和大多数自定义依赖项属性方案都是如此。

    此类声明的补充PropertyMetadata基类的成员包括各种布尔属性,这些布尔属性指定或报告WPF框架级别的属性系统行为,例如属性继承,数据绑定和布局。

    用于创建FrameworkPropertyMetadata实例的多个构造函数签名带有FrameworkPropertyMetadataOptions参数。所述FrameworkPropertyMetadataOptions枚举仅用于在构造来指定初始行为,并且之后不以其它方式暴露FrameworkPropertyMetadata构造。从构造的实例中,您可以通过各种属性来获取或设置相应的信息,这些属性共享构造函数调用中使用的枚举值的名称。

    FrameworkPropertyMetadata属性
    名称备注权限

    AffectsArrange

    获取或设置一个值,该值表示依赖属性是否会影响布局引擎操作期间的排列过程。get; set;

    AffectsMeasure

    获取或设置一个值,该值表示依赖属性是否会影响布局引擎操作期间的测量过程。get; set;

    AffectsParentArrange

    获取或设置一个值,该值表示依赖属性是否会影响布局引擎操作期间父元素布局的排列处理过程。get; set;

    AffectsParentMeasure

    获取或设置一个值,该值表示依赖属性是否会影响布局引擎操作期间其父元素布局的测量过程。get; set;

    AffectsRender

    获取或设置一个值,该值指示依赖属性是否会以某种方式(这种方式不会具体影响排列或测量,但将请求重绘)对常规布局造成潜在影响。get; set;

    BindsTwoWayByDefault

    获取或设置一个值,该值指示默认情况下属性是否双向绑定。get; set;

    DefaultUpdateSourceTrigger

    获取或设置在应用具有该元数据的属性的绑定时要使用的 UpdateSourceTrigger 的默认值,这些绑定的 UpdateSourceTrigger 设置为 Defaultget; set;

    Inherits

    获取或设置一个值,该值指示依赖属性的值是否可继承。get; set;

    IsDataBindingAllowed

    获取一个值,该值指示依赖属性是否支持数据绑定。get;

    IsNotDataBindable

    获取或设置一个值,该值指示依赖属性是否支持数据绑定。get; set;

    Journal

    获取或设置一个值,该值表示该属性是否包含应用程序可以或应该作为日记功能实现的一部分而存储的日记信息。get; set;

    OverridesInheritanceBehavior

    获取或设置一个值,该值指示属性值继承计算是否应跨越元素逻辑树中的某些内容边界。get; set;

    SubPropertiesDoNotAffectRender

    获取或设置一个值,该值指示依赖属性的子属性是否影响包含对象的呈现。get; set;
    FrameworkPropertyMetadata方法
    名称备注权限

    Merge

    允许合并源元数据和基本元数据。protected

    OnApply

    当此元数据已经应用到一个属性时(这表明正在密封元数据)调用。protected

    WPF对依赖属性值的读取优先级

    优先级
    1)WPF属性系统强制值
    2)由动画过程控制的值
    3)本地变量值(存储在Effective ValueEntry数组中)
    4)由上级元素的Template设置值
    5)由隐藏样式(Implicit Style)设置的值
    6)由样式触发器(Style Trigger)设置的值
    7)由模板触发器(Template Trigger)设置的值
    8)由样式(Style Setter)设置的值
    9)由默认样式(Default Style)设置的值,默认模式其实就是由主题(Theme)指定的模式。
    10)由上级元素继承而来的值
    11)默认值,源于依赖属性的元数据(metadata)

    WPF对依赖属性值的设置

    1. 首先验证依赖属性的值是否可写,如果不能就排除异常。
    2. 检查值是不是DependencyProperty.UnsetValue,如果是,说明调用者的意图是清空现有值。此时程序会调用C了爱人ValueCommom方法来清空现有值。
    3. 检查EffectiveValueEntry数组中是否已经存在响应依赖属性的位置,如果有就把旧值改写为新值;如果没有就新建EffectiveValueEntry对象并存储新值。
    4. 调用UpdataEffectiveValue对新值做一些响应处理。

    WPF使用依赖属性的方式(更改通知和动态值识别)

    当属性值变化时,如果希望进行响应:

    • 创建绑定
    • 触发器
    • OnPropertyChangedCallBack()方法、PropertyChangedCallBack回调函数

    共享依赖属性

    TextBlock.FontFamily和Control.FontFamily属性指向一个静态的依赖属性。

    TextElement类的静态构造函数注册该属性,而TextBlock类和Control类的静态构造函数只能通过调用DependencyProperty.AddOwner()方法重用该属性:

    TextBlock.FontFamilyProperty=TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

     

    范例

    XAML范例

    <Window x:Class="DependencyPropertyDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:DependencyPropertyDemo"
            mc:Ignorable="d"
            Title="MainWindow" Height="250" Width="400">
        <Window.Resources>
            <local:MyDependencyObject x:Key="myData"/>
        </Window.Resources>
        <StackPanel>
            <TextBlock  Margin="5" Text="{Binding Source={StaticResource myData}, Path=MyProperty}"/>
            <TextBox  Margin="5"  Text="{Binding Source={StaticResource myData},Path=MyProperty,UpdateSourceTrigger=PropertyChanged}"/>      
        </StackPanel>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace DependencyPropertyDemo
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();         
            }
        }
    
        public class MyDependencyObject: DependencyObject
        {
            public string MyProperty
            {
                get { return (string)GetValue(MyPropertyProperty); }
                set { SetValue(MyPropertyProperty, value); }
            }
            public static readonly DependencyProperty MyPropertyProperty =DependencyProperty.Register("MyProperty", typeof(string), typeof(MyDependencyObject));
        }
    }

    C#范例

    <Window x:Class="DependencyPropertyDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:DependencyPropertyDemo"
            mc:Ignorable="d"
            Title="MainWindow" Height="250" Width="400">
        <StackPanel>
            <TextBlock  Margin="5" />
            <TextBox  Margin="5"  />      
        </StackPanel>
    </Window>
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace DependencyPropertyDemo
    {
        /// <summary>
        /// MainWindow.xaml 的交互逻辑
        /// </summary>
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                MyDependencyObject myDependencyObject = new MyDependencyObject();
                myDependencyObject.MyProperty = "MyProperty";
    
                Binding binding1 = new Binding();
                binding1.Path = new PropertyPath("MyProperty");
                Binding binding2 = new Binding();
                binding2.Path = new PropertyPath("Text");
                binding2.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                foreach (var item in (this.Content as StackPanel).Children)
                {
                    if (item is TextBlock)
                    {
                        binding1.Source = myDependencyObject;
                        BindingOperations.SetBinding((item as TextBlock), TextBlock.TextProperty, binding1);
                    }
                    if (item is TextBox)
                    {
                        binding2.Source = (item as TextBox);
                        BindingOperations.SetBinding(myDependencyObject, MyDependencyObject.MyPropertyProperty, binding2);
                    }
                }
            }
        }
    
        public class MyDependencyObject: DependencyObject
        {
            public string MyProperty
            {
                get { return (string)GetValue(MyPropertyProperty); }
                set { SetValue(MyPropertyProperty, value); }
            }
            public static readonly DependencyProperty MyPropertyProperty =DependencyProperty.Register("MyProperty", typeof(string), typeof(MyDependencyObject));
        }
    }

     


    附加属性AttachedProperty

    Object->AttachedProperty

    用于将附加信息关联或附加到对象的实例。

    把对象放入特定环境后对象才具有的属性(被环境赋予的属性)就称为附加属性。

    Windows Workflow Designer使用它来跟踪在编辑会话期间有趣的数据片段,否则这些数据是瞬态的,这些数据(在大多数情况下)将在编辑会话结束时被丢弃。AttachedProperty非常有用,因为View中的项目可以绑定到值并对更改做出反应。例如,这就是显示所选项目模板的方式(它从附加属性中取消选择)。

     

    附加属性的声明、注册和使用

    输入propa,两次按下Tab后,修改以下代码

        public static int GetMyProperty(DependencyObject obj)
        {
            return (int)obj.GetValue(MyPropertyProperty);
        }
    
        public static void SetMyProperty(DependencyObject obj, int value)
        {
            obj.SetValue(MyPropertyProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for MyProperty.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyPropertyProperty =
            DependencyProperty.RegisterAttached("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));
    • 创建的类派生自DependencyObject 
    • 成员变量同时被public static readonly修饰
    • 实现GetMyProperty、SetMyProperty
    • 用DependencyProperty.RegisterAttached方法注册附加属性

    附加属性也可以使用Binding依赖在其他对象的数据上。

            <Canvas>
                <TextBlock Canvas.Left="{Binding ElementName=slider1,Path=Value}"/>
            </Canvas>
    TextBlock.SetBinding(Canvas.LeftProperty,new Binding("Value"){Source=slider1});

     

    范例:PasswordBox的绑定

    来源:

    https://www.bilibili.com/video/BV1Jy4y1C7hU?p=12

    <PasswordBox common:PasswordHelper.Attach="True"
                 common:PasswordHelper.Password="{BindingLoginModel.Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
    public class PasswordHelper
        {
            //防止附加属性值与控件属性值循环触发赋值< Password_PasswordChanged > < OnPropertyChanged >
            private static bool _isUpdating = false;
     
            //附加属性值变化后给推送给控件
            public static readonly DependencyProperty PasswordProperty =
                DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper), new FrameworkPropertyMetadata(default(string), new
                     PropertyChangedCallback(OnPropertyChanged)));
            public static string GetPassword(DependencyObject d)
            {
                return d.GetValue(PasswordProperty).ToString();
            }
            public static void SetPassword(DependencyObject d, object value)
            {
                d.SetValue(PasswordProperty, value);
            }
            private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                PasswordBox password = d as PasswordBox;
                password.PasswordChanged -= Password_PasswordChanged;
                if (_isUpdating)
                    password.Password = e.NewValue?.ToString();
                password.PasswordChanged += Password_PasswordChanged;
            }
     
     
     
            public static readonly DependencyProperty AttachProperty =
                DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(PasswordHelper), new FrameworkPropertyMetadata(default(bool), new
                     PropertyChangedCallback(OnAttached)));
            public static bool GetAttach(DependencyObject d)
            {
                return (bool)d.GetValue(AttachProperty);
            }
            public static void SetAttach(DependencyObject d, object value)
            {
                d.SetValue(AttachProperty, value);
            }
            //控件的值变化后给推送给附加属性
            private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                PasswordBox password = d as PasswordBox;
                password.PasswordChanged += Password_PasswordChanged;
            }
            //控件的值变化后给推送给附加属性
            private static void Password_PasswordChanged(object sender, RoutedEventArgs e)
            {
                PasswordBox passwordBox = sender as PasswordBox;
                _isUpdating = true;
                SetPassword(passwordBox, passwordBox.Password);
                _isUpdating = false;
            }
        }

    属性验证

    WPF阻止非法值的方法

    表示用作验证依赖属性有效值的回调的方法。

    public delegate bool ValidateValueCallback(object value);

     

    为只要重新计算依赖项属性值或专门请求强制转换时就调用的方法提供一个模板。

    public delegate object CoerceValueCallback(DependencyObject d, object baseValue);

    设置依赖属性的过程:

    1. CoerceValueCallback修改提供的值,或者返回DependencyProperty.UnsetValue(完全拒绝修改)
    2. ValidateValueCallback,该方法返回true以接受一个值作为合法值,或者返回false拒绝新值
    3. PropertyChangedCallback,引发更改事件。

    PropertyChangedCallback 委托

    表示在依赖属性的有效属性值更改时调用的回调。

    public delegate void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e);

     

    范例

     

    <Window x:Class="DependencyPropertyDemo.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:DependencyPropertyDemo"
            mc:Ignorable="d"
            Title="MainWindow" Height="250" Width="400">
        <Window.Resources>
            <local:MyDependencyObject x:Key="myData"/>
        </Window.Resources>
        <StackPanel>
            <TextBlock  Margin="5" Text="{Binding Source={StaticResource myData}, Path=MyProperty}" Focusable="True"/>
            <TextBox  Margin="5"  Text="{Binding Source={StaticResource myData},Path=MyProperty,UpdateSourceTrigger=LostFocus}"/>
        </StackPanel>
    </Window>
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace DependencyPropertyDemo
    {
        
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
        }
    
        public class MyDependencyObject : DependencyObject
        {
            static int i = 0;
            public string MyProperty
            {
                get { return (string)GetValue(MyPropertyProperty); }
                set { SetValue(MyPropertyProperty, value); }
            }
            public static readonly DependencyProperty MyPropertyProperty = 
                   DependencyProperty.Register("MyProperty", typeof(string), typeof(MyDependencyObject),
                       new FrameworkPropertyMetadata("1", FrameworkPropertyMetadataOptions.None,new PropertyChangedCallback(OnPropertyChanged), new CoerceValueCallback(OnoerceValue)),
                       new ValidateValueCallback(OnValidateValue));
    
            private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
               
                MessageBox.Show("OnPropertyChanged:"+i++.ToString());
            }
    
    
            private static bool OnValidateValue(object value)
            {
                if (value.ToString().Length<=6)   return true; 
                return false;
            }
    
            private static object OnoerceValue(DependencyObject d, object baseValue)
            {           
                return baseValue.ToString();
            }
        }
    }


     

     

     

    展开全文
  • 一、引言  感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天...在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多

    一、引言

      感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列。尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF。今天继续分享WPF中一个最重要的知识点——依赖属性。

    二、依赖属性的全面解析

      听到依赖属性,自然联想到C#中属性的概念。C#中属性是抽象模型的核心部分,而依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性。依赖属性重要性在于,在WPF核心特性,如动画、数据绑定以及样式中都需要使用到依赖属性。既然WPF引入了依赖属性,也自然有其引入的道理。WPF中的依赖属性主要有以下三个优点:

    • 依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量。许多之前需要写很多代码才能实现的功能,在WPF中可以轻松实现。
    • 节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。关于依赖属性如何节约内存的更多内容参考:WPF的依赖属性是怎么节约内存的
    • 支持多种提供对象:可以通过多种方式来设置依赖属性的值。可以配合表达式、样式和绑定来对依赖属性设置值。

    2.1 依赖属性的定义

      上面介绍了依赖属性所带来的好处,这时候,问题又来了,怎样自己定义一个依赖属性呢?C#属性的定义大家再熟悉不过了。下面通过把C#属性进行改写成依赖属性的方式来介绍依赖属性的定义。下面是一个属性的定义:

    1 public class Person
    2     {
    3         public string Name { get; set; }
    6     }

      在把上面属性改写为依赖属性之前,下面总结下定义依赖属性的步骤:

    1. 让依赖属性的所在类型继承自DependencyObject类。
    2. 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
    3. 在类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。
    4. 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。

      根据上面的四个步骤,下面来把Name属性来改写成一个依赖属性,具体的实现代码如下所示:

    复制代码

    复制代码

    // 1. 使类型继承DependencyObject类
        public class Person : DependencyObject
        {
            // 2. 声明一个静态只读的DependencyProperty 字段
            public static readonly DependencyProperty nameProperty;
           
            static Person()
            {
                // 3. 注册定义的依赖属性
                nameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person), 
                    new PropertyMetadata("Learning Hard",OnValueChanged)); 
            }
    
            // 4. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
            public string Name
            {
                get { return (string)GetValue(nameProperty); }
                set { SetValue(nameProperty, value); }
            }
    
            private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
            {
                // 当只发生改变时回调的方法
            }
    
        }

    复制代码

    复制代码

      从上面代码可以看出,依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的。它使用哈希表来进行存储的,对应的Key就是属性的HashCode值,而值(Value)则是注册的DependencyPropery;而C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。总结为:属性是字段的包装,WPF中使用属性对依赖属性进行包装。

    2.2 依赖属性的优先级

       WPF允许在多个地方设置依赖属性的值,则自然就涉及到依赖属性获取值的优先级问题。例如下面XMAL代码,我们在三个地方设置了按钮的背景颜色,那最终按钮会读取那个设置的值呢?是Green、Yellow还是Red?

    复制代码

    复制代码

    <Window x:Class="DPSample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Button x:Name="myButton" Background="Green" Width="100" Height="30">
                <Button.Style>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="Background" Value="Yellow"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Red" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
                Click Me 
            </Button>
        </Grid>
    </Window>

    复制代码

    复制代码

      上面按钮的背景颜色是Green。之所以背景色是Green,是因为WPF每访问一个依赖属性,它都会按照下面的顺序由高到底处理该值。具体优先级如下图所示:

      在上面XAML中,按钮的本地值设置的是Green,自定义Style Trigger设置的为Red,自定义的Style Setter设置的为Yellow,由于这里的本地值的优先级最高,所以按钮的背景色或者的是Green值。如果此时把本地值Green去掉的话,此时按钮的背景颜色是Yellow而不是Red。这里尽管Style Trigger的优先级比Style Setter高,但是由于此时Style Trigger的IsMouseOver属性为false,即鼠标没有移到按钮上,一旦鼠标移到按钮上时,此时按钮的颜色就为Red。此时才会体现出Style Trigger的优先级比Style Setter优先级高。所以上图中优先级是比较理想情况下,很多时候还需要具体分析。

    2.3 依赖属性的继承

      依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。下面代码演示了依赖属性的继承。

    复制代码

    复制代码

    <Window x:Class="Custom_DPInherited.DPInherited"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
          mc:Ignorable="d" 
          d:DesignHeight="300" d:DesignWidth="300"
          FontSize="18"
          Title="依赖属性的继承">
        <StackPanel >
            <Label Content="继承自Window的FontSize" />
            <Label Content="显式设置FontSize" 
                   TextElement.FontSize="36"/>
            <StatusBar>Statusbar没有继承自Window的FontSize</StatusBar>
        </StackPanel>
    </Window>

    复制代码

    复制代码

    上面的代码的运行效果如下图所示:

      在上面XAML代码中。Window.FontSize设置会影响所有内部子元素字体大小,这就是依赖属性的继承。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。

      这时,你可能已经发现了问题:StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值。那这是什么原因呢?其实导致这样的问题:并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值。

      前面介绍了依赖属性的继承,那我们如何把自定义的依赖属性设置为可被其他控件继承呢?通过AddOwer方法可以依赖属性的继承。具体的实现代码如下所示:

    复制代码

    复制代码

     1  public class CustomStackPanel : StackPanel
     2     {
     3         public static readonly DependencyProperty MinDateProperty;
     4 
     5         static CustomStackPanel()
     6         {
     7             MinDateProperty = DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
     8         }
     9 
    10         public DateTime MinDate
    11         {
    12             get { return (DateTime)GetValue(MinDateProperty); }
    13             set { SetValue(MinDateProperty, value); }
    14         }
    15     }
    16 
    17     public class CustomButton :Button
    18     {
    19         private static readonly DependencyProperty MinDateProperty;
    20 
    21         static CustomButton()
    22         {
    23             // AddOwner方法指定依赖属性的所有者,从而实现依赖属性的继承,即CustomStackPanel的MinDate属性被CustomButton控件继承。
    24             // 注意FrameworkPropertyMetadataOptions的值为Inherits
    25             MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
    26         }
    27 
    28         public DateTime MinDate
    29         {
    30             get { return (DateTime)GetValue(MinDateProperty); }
    31             set { SetValue(MinDateProperty, value); }
    32         }
    33     }

    复制代码

    复制代码

      接下来,你可以在XAML中进行测试使用,具体的XAML代码如下:

    复制代码

    复制代码

    <Window x:Class="Custom_DPInherited.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:Custom_DPInherited"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            Title="实现自定义依赖属性的继承" Height="350" Width="525">
        <Grid>
            <local:CustomStackPanel x:Name="customStackPanle" MinDate="{x:Static sys:DateTime.Now}">
                <!--CustomStackPanel的依赖属性-->
                <ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanle}"/>
                <local:CustomButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=MinDate}" Height="25"/>
            </local:CustomStackPanel>
        </Grid>
    </Window>

    复制代码

    复制代码

      上面XAML代码中,显示设置了CustomStackPanel的MinDate的值,而在CustomButton中却没有显式设置其MinDate值。CustomButton的Content属性的值是通过绑定MinDate属性来进行获取的,关于绑定的更多内容会在后面文章中分享。在这里CustomButton中并没有设置MinDate的值,但是CustomButton的Content的值却是当前的时间,从而可以看出,此时CustomButton的MinDate属性继承了CustomStackPanel的MinDate的值,从而设置了其Content属性。最终的效果如下图所示:

    2.4 只读依赖属性

       在C#属性中,我们可以通过设置只读属性来防止外界恶意更改该属性值,同样,在WPF中也可以设置只读依赖属性。如IsMouseOver就是一个只读依赖属性。那我们如何创建一个只读依赖属性呢?其实只读的依赖属性的定义方式与一般依赖属性的定义方式基本一样。只读依赖属性仅仅是用DependencyProperty.RegisterReadonly替换了DependencyProperty.Register而已。下面代码实现了一个只读依赖属性。

    复制代码

    复制代码

     1 public partial class MainWindow : Window
     2     {
     3         public MainWindow()
     4         {
     5             InitializeComponent();
     6 
     7             // 内部使用SetValue来设置值
     8             SetValue(counterKey, 8);
     9         }
    10 
    11         // 属性包装器,只提供GetValue,你也可以设置一个private的SetValue进行限制。
    12         public int Counter
    13         {
    14             get { return (int)GetValue(counterKey.DependencyProperty); }
    15         }
    16 
    17         // 使用RegisterReadOnly来代替Register来注册一个只读的依赖属性
    18         private static readonly DependencyPropertyKey counterKey =
    19             DependencyProperty.RegisterReadOnly("Counter",
    20             typeof(int),
    21             typeof(MainWindow),
    22             new PropertyMetadata(0));
    23     }

    复制代码

    复制代码

      对应的XAML代码为:

    复制代码

    复制代码

    <Window x:Class="ReadOnlyDP.MainWindow" 
            Name="ThisWin"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="ReadOnly Dependency Property" Height="350" Width="525">
        <Grid>
            <Viewbox>
                <TextBlock Text="{Binding ElementName=ThisWin, Path=Counter}"/>
            </Viewbox>
        </Grid>
    </Window>

    复制代码

    复制代码

      此时Counter包装的counterKey就是一个只读依赖属性,因为其定义为private的,所以在类外也不能使用DependencyObject.SetValue方法来对其值,而包装的Counter属性又只提供了GetValue方法,所以类外部只能对该依赖属性进行读取,而不能对其赋值。此时运行效果如下图所示。

    2.5 附加属性

      WPF中还有一类特殊的属性——附加属性。附加是一种特殊的依赖属性。它允许给一个对象添加一个值,而该对象可能对这个值一无所知。附加属性最常见的例子就是布局容器中DockPanel类中的Dock附加属性和Grid类中Row和Column附加属性。那问题又来了,我们怎样在自己的类中定义一个附加属性呢?其实定义附加属性和定义一般的依赖属性一样没什么区别,只是用RegisterAttached方法代替了Register方法罢了。下面代码演示了附加属性的定义。

    复制代码

    复制代码

    public class AttachedPropertyClass
        {
            // 通过使用RegisterAttached来注册一个附加属性
            public static readonly DependencyProperty IsAttachedProperty =
                DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyClass),
                new FrameworkPropertyMetadata((bool)false));
    
            // 通过静态方法的形式暴露读的操作
            public static bool GetIsAttached(DependencyObject dpo)
            {
                return (bool)dpo.GetValue(IsAttachedProperty);
            }
    
            public static void SetIsAttached(DependencyObject dpo, bool value)
            {
                dpo.SetValue(IsAttachedProperty, value);
            }
        }

    复制代码

    复制代码

      在上面代码中,IsAttached就是一个附加属性,附加属性没有采用CLR属性进行封装,而是使用静态SetIsAttached方法和GetIsAttached方法来存取IsAttached值。这两个静态方法内部一样是调用SetValue和GetValue来对附加属性读写的。

    2.6 依赖属性验证和强制

       在定义任何类型的属性时,都需要考虑错误设置属性的可能性。对于传统的CLR属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。然而WPF有其代替的方式,WPF中提供了两种方法来用于验证依赖属性的值。

    • ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为DependencyProperty.Register方法的一个参数。
    • CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。例如某个依赖属性Age的值范围是0到120,在该回调函数中,可以对设置的值进行强制修改,对于不满足条件的值,强制修改为满足条件的值。如当设置为负值时,可强制修改为0。该回调函数可作为PropertyMetadata构造函数参数进行传递。

      当应用程序设置一个依赖属性时,所涉及的验证过程如下所示:

    1. 首先,CoerceValueCallback方法可以修改提供的值或返回DependencyProperty.UnsetValue
    2. 如果CoerceValueCallback方法强制修改了提供的值,此时会激活ValidateValueCallback方法进行验证,如果该方法返回为true,表示该值合法,被认为可被接受的,否则拒绝该值。不像CoerceValueCallback方法,ValidateValueCallback方法不能访问设置属性的实际对象,这意味着你不能检查其他属性值。即该方法中不能对类的其他属性值进行访问。
    3. 如果上面两个阶段都成功的话,最后会触发PropertyChangedCallback方法来触发依赖属性值的更改。

      下面代码演示了基本的流程。

    复制代码

    复制代码

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             SimpleDPClass sDPClass = new SimpleDPClass();
     6             sDPClass.SimpleDP = 2;
     7             Console.ReadLine();
     8         }
     9     }
    10 
    11     public class SimpleDPClass : DependencyObject
    12     {
    13         public static readonly DependencyProperty SimpleDPProperty =
    14             DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
    15                 new FrameworkPropertyMetadata((double)0.0,
    16                     FrameworkPropertyMetadataOptions.None,
    17                     new PropertyChangedCallback(OnValueChanged),
    18                     new CoerceValueCallback(CoerceValue)),
    19                     new ValidateValueCallback(IsValidValue));
    20 
    21         public double SimpleDP
    22         {
    23             get { return (double)GetValue(SimpleDPProperty); }
    24             set { SetValue(SimpleDPProperty, value); }
    25         }
    26 
    27         private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    28         {
    29             Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
    30         }
    31 
    32         private static object CoerceValue(DependencyObject d, object value)
    33         {
    34             Console.WriteLine("对值进行限定,强制值: {0}", value);
    35             return value;
    36         }
    37 
    38         private static bool IsValidValue(object value)
    39         {
    40             Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
    41             return true;
    42         }
    43     }

    复制代码

    复制代码

      其运行结果如下图所示:

      从运行结果可以看出,此时并没有按照上面的流程先Coerce后Validate的顺序执行,这可能是WPF内部做了一些特殊的处理。当属性被改变时,首先会调用Validate来判断传入的value是否有效,如果无效就不继续后续操作。并且CoerceValue后面并没有运行ValidateValue,而是直接调用PropertyChanged。这是因为CoerceValue操作并没有强制改变属性的值,而前面对这个值已经验证过了,所以也就没有必要再运行Valudate方法来进行验证了。但是如果在Coerce中改变了Value的值,那么还会再次调用Valudate操作来验证值是否合法。

    2.7  依赖属性的监听

      我们可以用两种方法对依赖属性的改变进行监听。这两种方法是:

      下面分别使用这两种方式来实现下对依赖属性的监听。

      第一种方式:定义一个派生于依赖属性所在的类,然后重写依赖属性的元数据并传递一个PropertyChangedCallback参数即可,具体的实现如下代码所示:

    复制代码

    复制代码

     1 public class MyTextBox : TextBox
     2     {
     3         public MyTextBox()
     4             : base()
     5         {
     6         }
     7 
     8         static MyTextBox()
     9         {
    10             //第一种方法,通过OverrideMetadata
    11             TextProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
    12         }
    13 
    14         private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    15         {
    16             MessageBox.Show("", "Changed");
    17         }
    18     }

    复制代码

    复制代码

      第二种方法:这个方法更加简单,获取DependencyPropertyDescriptor并调用AddValueChange方法为其绑定一个回调函数。具体实现代码如下所示:

    复制代码

    复制代码

     public MainWindow()
            {
                InitializeComponent();
                //第二种方法,通过OverrideMetadata
                DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
                descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
            }
    
            private void tbxEditMe_TextChanged(object sender, EventArgs e)
            {
                MessageBox.Show("", "Changed");
            }

    复制代码

    复制代码

    三、总结

       到这里,依赖属性的介绍就结束了。WPF中的依赖属性通过一个静态只读字段进行定义,并且在静态构造函数中进行注册,最后通过.NET传统属性进行包装,使其使用与传统的.NET属性并无两样。在后面一篇文章将分享WPF中新的事件机制——路由事件。

      本文所有源码下载:DependencyPropertyDemo.zip

    作者:三小 声明:原创博客请在转载时保留原文链接或者在文章开头加上本人博客地址,如发现错误,欢迎批评指正。凡是转载于本人的文章,不能设置打赏功能,如有特殊需求请与本人联系!

    分类: wpf

    展开全文
  • 一、依赖属性的意义和实现 在我开始使用WPF进行界面的开发过程中,遇到了这样一个问题:在MVVM框架下,有时需要让某一个控件从后台代码中获取某个数据,但是当这个控件又没有现成的属性可以binding的时候该怎么办...

    一、依赖属性的意义和实现

           在我开始使用WPF进行界面的开发过程中,遇到了这样一个问题:在MVVM框架下,有时需要让某一个控件从后台代码中获取某个数据,但是当这个控件又没有现成的属性可以binding的时候该怎么办呢 ?为了解决这个问题,我最后使用了自定义依赖属性的方法,通过这次测试与实践,也让我对WPF中经常使用的Binding有了更加深入的认识。

            首先依赖属性的作用:作为一种特殊的属性,可以通过绑定数据的方式获得数据,因此可以不需要一个私有字段作为数据源,从而节省了内存空间,也让控件的数据传递更加自由灵活。

            依赖属性的实现:

    class Person:DependencyObject
    {
        //依赖属性对外的包装器
        public int Age
        {
            get{(int)GetValue(AgeProperty);}
            set{SetValue(AgeProperty,value);}
        }
        
        
        //定义依赖属性
        public static readonly DependencyProperty AgeProperty =
        DependencyProperty.Register("Age",typeof(int),typeof(Person),new PropertyMetadata(0));
    
    }

            1.继承DependencyObject类;

            2.创建依赖属性,依赖属性的必须是public static readonly的,通过DenpendencyProperty.Register方法创建依赖属性,该方法中有多种重载,VS中默认采用三个参数的重载,这三个参数分别需要传入依赖属性包装器的名称依赖属性的数据类型包含此依赖属性的类的类型依赖属性的默认值;

            3.创建依赖属性的包装器,依赖属性的包装器是依赖属性对外的接口,包装器的名称也就是我们经常在XAML代码经常用来binding的属性名称;

            这样就完成了一个依赖属性的实现,在使用过程中可以通过在自定义控件中添加依赖属性的方式实现对需要数据的绑定;

    二、附加属性

            附加属性是依赖属性的一种特殊情况,在使用WPF进行开发的时候,我们常常会使用Grid控件进行布局,当我们把其他控件放在<Grid></Grid>中进行布局的时候,这些控件就能够使用Grid.Row这样的属性,这些属性就是Grid的附加属性,而使用这些属性的是Grid中的控件,所以使用附加属性的类并非定义附加属性的类

            附加属性的实现与依赖属性相似,但是主要有两点不同:

    class Person:DependencyObject
    {
        //附加属性对外的包装器
        public static int GetAge(DependencyObject obj){return (int)obj.GetValue(AgeProperty);}
        public static void SetAge(DependencyObject obj){obj.SetValue(AgeProperty,value);}
        
        //定义附加属性
        public static readonly DependencyProperty AgeProperty =
        DependencyProperty.RegisterAttached("Age",typeof(int),typeof(Person),new PropertyMetadata(0));
    
    }

            1.如代码所示,附加属性的对外包装器定义了两个方法分别是GetAge和SetAge,其中Age就是我们对外包装器的名称;

            2.在创建附加属性的时候,采用了RegisterAttached方法,而不是Register方法;

    展开全文
  • 一、引言  感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天...在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多

    一、引言

      感觉最近都颓废了,好久没有学习写博文了,出于负罪感,今天强烈逼迫自己开始更新WPF系列。尽管最近看到一篇WPF技术是否老矣的文章,但是还是不能阻止我系统学习WPF。今天继续分享WPF中一个最重要的知识点——依赖属性。

    二、依赖属性的全面解析

      听到依赖属性,自然联想到C#中属性的概念。C#中属性是抽象模型的核心部分,而依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,但我们必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性。依赖属性重要性在于,在WPF核心特性,如动画、数据绑定以及样式中都需要使用到依赖属性。既然WPF引入了依赖属性,也自然有其引入的道理。WPF中的依赖属性主要有以下三个优点:

    • 依赖属性加入了属性变化通知、限制、验证等功能。这样可以使我们更方便地实现应用,同时大大减少了代码量。许多之前需要写很多代码才能实现的功能,在WPF中可以轻松实现。
    • 节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。关于依赖属性如何节约内存的更多内容参考:WPF的依赖属性是怎么节约内存的
    • 支持多种提供对象:可以通过多种方式来设置依赖属性的值。可以配合表达式、样式和绑定来对依赖属性设置值。

    2.1 依赖属性的定义

      上面介绍了依赖属性所带来的好处,这时候,问题又来了,怎样自己定义一个依赖属性呢?C#属性的定义大家再熟悉不过了。下面通过把C#属性进行改写成依赖属性的方式来介绍依赖属性的定义。下面是一个属性的定义:

    1 public class Person
    2     {
    3         public string Name { get; set; }
    6     }

      在把上面属性改写为依赖属性之前,下面总结下定义依赖属性的步骤:

    1. 让依赖属性的所在类型继承自DependencyObject类。
    2. 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
    3. 在类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。
    4. 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。

      根据上面的四个步骤,下面来把Name属性来改写成一个依赖属性,具体的实现代码如下所示:

    复制代码

    复制代码

    // 1. 使类型继承DependencyObject类
        public class Person : DependencyObject
        {
            // 2. 声明一个静态只读的DependencyProperty 字段
            public static readonly DependencyProperty nameProperty;
           
            static Person()
            {
                // 3. 注册定义的依赖属性
                nameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Person), 
                    new PropertyMetadata("Learning Hard",OnValueChanged)); 
            }
    
            // 4. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性
            public string Name
            {
                get { return (string)GetValue(nameProperty); }
                set { SetValue(nameProperty, value); }
            }
    
            private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e)
            {
                // 当只发生改变时回调的方法
            }
    
        }

    复制代码

    复制代码

      从上面代码可以看出,依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的。它使用哈希表来进行存储的,对应的Key就是属性的HashCode值,而值(Value)则是注册的DependencyPropery;而C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。总结为:属性是字段的包装,WPF中使用属性对依赖属性进行包装。

    2.2 依赖属性的优先级

       WPF允许在多个地方设置依赖属性的值,则自然就涉及到依赖属性获取值的优先级问题。例如下面XMAL代码,我们在三个地方设置了按钮的背景颜色,那最终按钮会读取那个设置的值呢?是Green、Yellow还是Red?

    复制代码

    复制代码

    <Window x:Class="DPSample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Button x:Name="myButton" Background="Green" Width="100" Height="30">
                <Button.Style>
                    <Style TargetType="{x:Type Button}">
                        <Setter Property="Background" Value="Yellow"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="Red" />
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
                Click Me 
            </Button>
        </Grid>
    </Window>

    复制代码

    复制代码

      上面按钮的背景颜色是Green。之所以背景色是Green,是因为WPF每访问一个依赖属性,它都会按照下面的顺序由高到底处理该值。具体优先级如下图所示:

      在上面XAML中,按钮的本地值设置的是Green,自定义Style Trigger设置的为Red,自定义的Style Setter设置的为Yellow,由于这里的本地值的优先级最高,所以按钮的背景色或者的是Green值。如果此时把本地值Green去掉的话,此时按钮的背景颜色是Yellow而不是Red。这里尽管Style Trigger的优先级比Style Setter高,但是由于此时Style Trigger的IsMouseOver属性为false,即鼠标没有移到按钮上,一旦鼠标移到按钮上时,此时按钮的颜色就为Red。此时才会体现出Style Trigger的优先级比Style Setter优先级高。所以上图中优先级是比较理想情况下,很多时候还需要具体分析。

    2.3 依赖属性的继承

      依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。下面代码演示了依赖属性的继承。

    复制代码

    复制代码

    <Window x:Class="Custom_DPInherited.DPInherited"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
          mc:Ignorable="d" 
          d:DesignHeight="300" d:DesignWidth="300"
          FontSize="18"
          Title="依赖属性的继承">
        <StackPanel >
            <Label Content="继承自Window的FontSize" />
            <Label Content="显式设置FontSize" 
                   TextElement.FontSize="36"/>
            <StatusBar>Statusbar没有继承自Window的FontSize</StatusBar>
        </StackPanel>
    </Window>

    复制代码

    复制代码

    上面的代码的运行效果如下图所示:

      在上面XAML代码中。Window.FontSize设置会影响所有内部子元素字体大小,这就是依赖属性的继承。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。

      这时,你可能已经发现了问题:StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值。那这是什么原因呢?其实导致这样的问题:并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值。

      前面介绍了依赖属性的继承,那我们如何把自定义的依赖属性设置为可被其他控件继承呢?通过AddOwer方法可以依赖属性的继承。具体的实现代码如下所示:

    复制代码

    复制代码

     1  public class CustomStackPanel : StackPanel
     2     {
     3         public static readonly DependencyProperty MinDateProperty;
     4 
     5         static CustomStackPanel()
     6         {
     7             MinDateProperty = DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
     8         }
     9 
    10         public DateTime MinDate
    11         {
    12             get { return (DateTime)GetValue(MinDateProperty); }
    13             set { SetValue(MinDateProperty, value); }
    14         }
    15     }
    16 
    17     public class CustomButton :Button
    18     {
    19         private static readonly DependencyProperty MinDateProperty;
    20 
    21         static CustomButton()
    22         {
    23             // AddOwner方法指定依赖属性的所有者,从而实现依赖属性的继承,即CustomStackPanel的MinDate属性被CustomButton控件继承。
    24             // 注意FrameworkPropertyMetadataOptions的值为Inherits
    25             MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
    26         }
    27 
    28         public DateTime MinDate
    29         {
    30             get { return (DateTime)GetValue(MinDateProperty); }
    31             set { SetValue(MinDateProperty, value); }
    32         }
    33     }

    复制代码

    复制代码

      接下来,你可以在XAML中进行测试使用,具体的XAML代码如下:

    复制代码

    复制代码

    <Window x:Class="Custom_DPInherited.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:Custom_DPInherited"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            Title="实现自定义依赖属性的继承" Height="350" Width="525">
        <Grid>
            <local:CustomStackPanel x:Name="customStackPanle" MinDate="{x:Static sys:DateTime.Now}">
                <!--CustomStackPanel的依赖属性-->
                <ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanle}"/>
                <local:CustomButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=MinDate}" Height="25"/>
            </local:CustomStackPanel>
        </Grid>
    </Window>

    复制代码

    复制代码

      上面XAML代码中,显示设置了CustomStackPanel的MinDate的值,而在CustomButton中却没有显式设置其MinDate值。CustomButton的Content属性的值是通过绑定MinDate属性来进行获取的,关于绑定的更多内容会在后面文章中分享。在这里CustomButton中并没有设置MinDate的值,但是CustomButton的Content的值却是当前的时间,从而可以看出,此时CustomButton的MinDate属性继承了CustomStackPanel的MinDate的值,从而设置了其Content属性。最终的效果如下图所示:

    2.4 只读依赖属性

       在C#属性中,我们可以通过设置只读属性来防止外界恶意更改该属性值,同样,在WPF中也可以设置只读依赖属性。如IsMouseOver就是一个只读依赖属性。那我们如何创建一个只读依赖属性呢?其实只读的依赖属性的定义方式与一般依赖属性的定义方式基本一样。只读依赖属性仅仅是用DependencyProperty.RegisterReadonly替换了DependencyProperty.Register而已。下面代码实现了一个只读依赖属性。

    复制代码

    复制代码

     1 public partial class MainWindow : Window
     2     {
     3         public MainWindow()
     4         {
     5             InitializeComponent();
     6 
     7             // 内部使用SetValue来设置值
     8             SetValue(counterKey, 8);
     9         }
    10 
    11         // 属性包装器,只提供GetValue,你也可以设置一个private的SetValue进行限制。
    12         public int Counter
    13         {
    14             get { return (int)GetValue(counterKey.DependencyProperty); }
    15         }
    16 
    17         // 使用RegisterReadOnly来代替Register来注册一个只读的依赖属性
    18         private static readonly DependencyPropertyKey counterKey =
    19             DependencyProperty.RegisterReadOnly("Counter",
    20             typeof(int),
    21             typeof(MainWindow),
    22             new PropertyMetadata(0));
    23     }

    复制代码

    复制代码

      对应的XAML代码为:

    复制代码

    复制代码

    <Window x:Class="ReadOnlyDP.MainWindow" 
            Name="ThisWin"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="ReadOnly Dependency Property" Height="350" Width="525">
        <Grid>
            <Viewbox>
                <TextBlock Text="{Binding ElementName=ThisWin, Path=Counter}"/>
            </Viewbox>
        </Grid>
    </Window>

    复制代码

    复制代码

      此时Counter包装的counterKey就是一个只读依赖属性,因为其定义为private的,所以在类外也不能使用DependencyObject.SetValue方法来对其值,而包装的Counter属性又只提供了GetValue方法,所以类外部只能对该依赖属性进行读取,而不能对其赋值。此时运行效果如下图所示。

    2.5 附加属性

      WPF中还有一类特殊的属性——附加属性。附加是一种特殊的依赖属性。它允许给一个对象添加一个值,而该对象可能对这个值一无所知。附加属性最常见的例子就是布局容器中DockPanel类中的Dock附加属性和Grid类中Row和Column附加属性。那问题又来了,我们怎样在自己的类中定义一个附加属性呢?其实定义附加属性和定义一般的依赖属性一样没什么区别,只是用RegisterAttached方法代替了Register方法罢了。下面代码演示了附加属性的定义。

    复制代码

    复制代码

    public class AttachedPropertyClass
        {
            // 通过使用RegisterAttached来注册一个附加属性
            public static readonly DependencyProperty IsAttachedProperty =
                DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyClass),
                new FrameworkPropertyMetadata((bool)false));
    
            // 通过静态方法的形式暴露读的操作
            public static bool GetIsAttached(DependencyObject dpo)
            {
                return (bool)dpo.GetValue(IsAttachedProperty);
            }
    
            public static void SetIsAttached(DependencyObject dpo, bool value)
            {
                dpo.SetValue(IsAttachedProperty, value);
            }
        }

    复制代码

    复制代码

      在上面代码中,IsAttached就是一个附加属性,附加属性没有采用CLR属性进行封装,而是使用静态SetIsAttached方法和GetIsAttached方法来存取IsAttached值。这两个静态方法内部一样是调用SetValue和GetValue来对附加属性读写的。

    2.6 依赖属性验证和强制

       在定义任何类型的属性时,都需要考虑错误设置属性的可能性。对于传统的CLR属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。然而WPF有其代替的方式,WPF中提供了两种方法来用于验证依赖属性的值。

    • ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为DependencyProperty.Register方法的一个参数。
    • CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。例如某个依赖属性Age的值范围是0到120,在该回调函数中,可以对设置的值进行强制修改,对于不满足条件的值,强制修改为满足条件的值。如当设置为负值时,可强制修改为0。该回调函数可作为PropertyMetadata构造函数参数进行传递。

      当应用程序设置一个依赖属性时,所涉及的验证过程如下所示:

    1. 首先,CoerceValueCallback方法可以修改提供的值或返回DependencyProperty.UnsetValue
    2. 如果CoerceValueCallback方法强制修改了提供的值,此时会激活ValidateValueCallback方法进行验证,如果该方法返回为true,表示该值合法,被认为可被接受的,否则拒绝该值。不像CoerceValueCallback方法,ValidateValueCallback方法不能访问设置属性的实际对象,这意味着你不能检查其他属性值。即该方法中不能对类的其他属性值进行访问。
    3. 如果上面两个阶段都成功的话,最后会触发PropertyChangedCallback方法来触发依赖属性值的更改。

      下面代码演示了基本的流程。

    复制代码

    复制代码

     1 class Program
     2     {
     3         static void Main(string[] args)
     4         {
     5             SimpleDPClass sDPClass = new SimpleDPClass();
     6             sDPClass.SimpleDP = 2;
     7             Console.ReadLine();
     8         }
     9     }
    10 
    11     public class SimpleDPClass : DependencyObject
    12     {
    13         public static readonly DependencyProperty SimpleDPProperty =
    14             DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
    15                 new FrameworkPropertyMetadata((double)0.0,
    16                     FrameworkPropertyMetadataOptions.None,
    17                     new PropertyChangedCallback(OnValueChanged),
    18                     new CoerceValueCallback(CoerceValue)),
    19                     new ValidateValueCallback(IsValidValue));
    20 
    21         public double SimpleDP
    22         {
    23             get { return (double)GetValue(SimpleDPProperty); }
    24             set { SetValue(SimpleDPProperty, value); }
    25         }
    26 
    27         private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    28         {
    29             Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
    30         }
    31 
    32         private static object CoerceValue(DependencyObject d, object value)
    33         {
    34             Console.WriteLine("对值进行限定,强制值: {0}", value);
    35             return value;
    36         }
    37 
    38         private static bool IsValidValue(object value)
    39         {
    40             Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
    41             return true;
    42         }
    43     }

    复制代码

    复制代码

      其运行结果如下图所示:

      从运行结果可以看出,此时并没有按照上面的流程先Coerce后Validate的顺序执行,这可能是WPF内部做了一些特殊的处理。当属性被改变时,首先会调用Validate来判断传入的value是否有效,如果无效就不继续后续操作。并且CoerceValue后面并没有运行ValidateValue,而是直接调用PropertyChanged。这是因为CoerceValue操作并没有强制改变属性的值,而前面对这个值已经验证过了,所以也就没有必要再运行Valudate方法来进行验证了。但是如果在Coerce中改变了Value的值,那么还会再次调用Valudate操作来验证值是否合法。

    2.7  依赖属性的监听

      我们可以用两种方法对依赖属性的改变进行监听。这两种方法是:

      下面分别使用这两种方式来实现下对依赖属性的监听。

      第一种方式:定义一个派生于依赖属性所在的类,然后重写依赖属性的元数据并传递一个PropertyChangedCallback参数即可,具体的实现如下代码所示:

    复制代码

    复制代码

     1 public class MyTextBox : TextBox
     2     {
     3         public MyTextBox()
     4             : base()
     5         {
     6         }
     7 
     8         static MyTextBox()
     9         {
    10             //第一种方法,通过OverrideMetadata
    11             TextProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
    12         }
    13 
    14         private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    15         {
    16             MessageBox.Show("", "Changed");
    17         }
    18     }

    复制代码

    复制代码

      第二种方法:这个方法更加简单,获取DependencyPropertyDescriptor并调用AddValueChange方法为其绑定一个回调函数。具体实现代码如下所示:

    复制代码

    复制代码

     public MainWindow()
            {
                InitializeComponent();
                //第二种方法,通过OverrideMetadata
                DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
                descriptor.AddValueChanged(tbxEditMe, tbxEditMe_TextChanged);
            }
    
            private void tbxEditMe_TextChanged(object sender, EventArgs e)
            {
                MessageBox.Show("", "Changed");
            }

    复制代码

    复制代码

    三、总结

       到这里,依赖属性的介绍就结束了。WPF中的依赖属性通过一个静态只读字段进行定义,并且在静态构造函数中进行注册,最后通过.NET传统属性进行包装,使其使用与传统的.NET属性并无两样。在后面一篇文章将分享WPF中新的事件机制——路由事件。

      本文所有源码下载:DependencyPropertyDemo.zip

    展开全文
  • 文章目录前言一、依赖属性的使用方法1.添加派生类(以Button为例)2.修改样式模板3.前台应用二、依赖属性的使用方法1.添加普通类2.修改模板3.前台使用总结 前言 在wpf的控件中,有很多属性没有暴露出来给前台设置,...
  •  WPF数据绑定的资源,可以是CRL对象;可以绑定CLR对象的属性,属性或索引器;绑定数据的解释是通过Microsoft ....继承至依赖对象的对象,则通过定义依赖属性实现绑定。 这两者的区别在于: 针对的对象不同,一个
  • 依赖属性 在wpf中,所有支持绑定的属性本质上都是封装后的依赖属性,也就是说,只有依赖属性才可以进行绑定 下面演示了在Button按钮上为Content属性设置了一个绑定语法, 如下所示: <Button Content="{Binding ...
  • 由于刚接触WPF不久,经验不足,走了很多弯路,比如本次遇到的设置自定义控件依赖属性无效问题,该依赖属性没有在自定义控件前台直接使用,只是作为一个标识通过后端代码来控制前端部分控件,依赖属性定义看起来没啥问题,...
  • 一、什么是依赖属性依赖属性是一种可以自已没有值,但是可以通过Binding方式,从数据源(依赖别人的数据)获得值的属性 二、为什么要有依赖属性 传统的CLR属性 public class Person { private string _Name; ...
  • WPF依赖属性详解

    2021-04-15 22:22:24
    在讲解依赖属性之前, 首先我们熟悉一下WPF当中的绑定(Binding), 可能你曾用过WPF中绑定的语法。 下面演示了在Button按钮上为Content属性设置了一个绑定语法, 如下所示: 当你在Content属性按下F12转到定义时,可以...
  • WPF之依赖属性

    2021-06-26 23:24:26
    在学习WPF的过程中,依赖属性(有些教程称之为“依赖项属性,我喜欢称之为”依赖属性)的概念一直搞不明白,实在不明白微软为何要引入这么个难懂的东西。 然后翻书,逛帖子,看源码,总算弄懂了一点了。 先贴出...
  • 我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴。 相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学依赖属性的...
  • 什么是依赖属性依赖属性是属性的全新实现,它是在C#属性的基础上包装而成。 从源码盘它 我们从system.windows 源码,可以注意到因为DependencyProperty类没有公有 的构造函数。反而只能使用静态的...
  • 一:依赖属性 1、定义依赖属性 意义:支持WPF的特性功能:动画、数据绑定、样式 2、步骤: 声明:static,readonly,名称以Property结尾 注册:DependencyProperty.Register 包装:GetValue()| SetValue() 《-由...
  • MVVM框架的依赖属性

    2021-05-27 19:24:09
    MVVM框架的依赖属性 在MVVM框架中,最为重要的就是数据绑定,依赖属性和依赖对象,下面我们来了解一下MVVM的依赖属性,下面是自己定义的命令: 定义的CommmandBase命令继承基类ICommand,并且实现ICommand的命令...
  • 剖析WPF依赖属性

    2021-07-20 00:01:40
    这节来讲一下WPF中的依赖属性 (Dependency Property)。【了解属性和字段】我们知道,属性是面向对象语言中用来封装字段的外衣,它像是字段对外界的桥梁,我们...
  • 初步了解WPF依赖属性

    2021-01-08 01:50:18
    依赖属性 在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性。但必须明确,在WPF中我们大多数都在使用依赖属性,而不是使用属性。依赖属性重要性在于,在...
  • 04_依赖属性

    2021-11-01 19:49:38
    依赖属性是标准.NET属性的全新实现,是专门为 WPF 创建的。依赖属性可支持WPF中的样式设置、数据绑定、继承、动画及默认值。 创建依赖属性 第一步时定义表示属性的对象,它是 DependencyProperty 类的实例。属性信息...
  • WPF——依赖属性

    2020-12-28 23:58:47
    依赖属性 简介 依赖属性就是自己本身没有值,可以通过binding获取到数据源的值。具有依赖属性的对象称为依赖对象,依赖对象包含一般的控件(TextBox.text或ListBox.ItemsSourceProperty就是一个典型的依赖属性),也...
  • 当计算属性依赖的内容发生变更时,才会重新执行计算。 我相信这时候你还不能很好的理解这句话的意思,所以需要通过实际的代码来进行演示。 编写基本页面和准备变量 <!DOCTYPE html> <html lang="en"> &...
  • WPF-依赖属性

    2021-03-07 22:00:29
    根据约定,定义依赖属性的字段的名称是在普通属性的末尾处加上单词“Property”,根据这种约定,可从实际属性中区分出依赖属性的定义。字段的定义中使用only字段修饰,也就意味着只能在静态构造函数中对其进行...
  • 前面分析了,注入依赖的构造参数依赖,这节简单分析下属性依赖。 一、属性依赖是什么? 某个属性依赖某个值,我们要把这个值赋值给这个属性 二、怎么描述一个属性依赖 属性值,属性名。定义一个类来表示这...
  • SpringBoot启动过程中是默认支持循环依赖(即A类中有B属性,B类中有A属性),笔者还不清楚为什么会发生循环依赖报错。 3 代码场景 发生报错的代码场景如下所示: public class X { @Autowrie private A a; @...
  • 区分继承属性和综合属性,这很重要(画依赖图) 看不懂????!! 例如产生式1 v属性规规矩矩按照原本符号位置(N.v在右部,S1.v在右部(等号右边啦)S2.v在右部(…啦),和产生式一致),而f属性则不是,S1.f...
  • WPF 依赖属性

    2021-12-15 15:50:15
    属性和事件是.NET抽象模型的核心部分, 而WPF中则用更高级的依赖属性(Dependency Property)代替了原来.NET中的属性.依赖属性主要应用于自定义元素中,用于为自定义元素注入自定义的属性. 1、定义依赖属性,他是...
  • 依赖中引入了主要用于管理依赖关系的作用范围,目前依赖项的作用域可以使用五个值,compile、runtime、test、provided和system compile:依赖项的默认作用范围,即当没有指定依赖项的scope时默认使用compile。...
  • 函数依赖

    2021-04-17 14:13:50
    若对于R(U)的任意一个可能的关系r,r中不可能存在两个元组在X上的属性值相等而在Y上的属性值不相等,则称X函数确定Y或Y函数依赖于X ,记作X->Y,X称为决定因素,即唯一的X确定唯一的Y 如果X->Y且Y->X,则记...
  • 这一次,一定要搞懂SpringBean的依赖注入和初始化流程图Bean实例的创建Bean的依赖注入怎么解决循环依赖 Bean的依赖注入和初始化 依赖注入(DI)是spring的另一大重要的特性。依赖注入主要干的活就是注册bean的依赖...
  • Servlet应用支持在 Cookie 中配置 SameSite 属性属性可通过server.session.cookie.same-site属性来配置,共有三个可选值: Strict 严格模式,必须同站请求才能发送 cookie Lax 宽松模式,安全的跨站请求可以发送...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,033,356
精华内容 413,342
关键字:

依赖属性