zoukankan      html  css  js  c++  java
  • [No000012D]WPF(5/7)依赖属性

    介绍

    WPF带来了很多传统 Windows 应用程序没有的新特性和选择。我们已经讨论了一些 WPF 的特性,是时候更进一步介绍其他特性了。当你读完这个系列之前的文章,我希望你已经或多或少地了解了 WPF 的体系结构,边框,效果,转换,标记扩展等。

    在这篇文章中,我将介绍一种新的支撑 WPF 属性系统的属性系统。我们将介绍通过全新的依赖属性你可以怎么容易地创建自定义回调,附加属性,应用动画和样式等。最后,我将讨论前文中遗留的绑定选择问题。我希望你能喜欢这篇文章,就像这个系列中其他的文章一样。

    新的属性系统

    好吧,看到这个标题你可能觉得有点经验。是的, WPF 带来了一种全新的定义控件属性的技术。新的属性单元是依赖属性,包装类也就是可以创建以来属性的类叫做 DependencyObject 。我们使用它来把一个依赖属性注册进属性系统来确保对象包含了这个属性,然后我们可以随时获得或设置这些属性的值。我们甚至可以使用普通 CLR 属性来包装一个依赖属性然后使用 GetValue 和 SetValue 来获取或设置传入的值。

    这个几乎和 CLR 属性系统相同。所以,新的属性系统的好处是什么呢?让我们来看一下依赖属性和 CLR 属性的区别。

    为了使用依赖属性,你的类必须继承自 DependencyObject ,它定义了一个观察者来保存定义在 DependencyObject中的新的属性系统。

    依赖属性和 CLR 属性的区别

    CLR 属性仅仅只是一个私有变量的包装器。它使用 Get/Set 方法来获取或设置变量的值。坦白地说, CLR 属性仅仅只是提供了一块可以在属性 get 或 set 时执行你写的代码的地方。因此 CLR 属性系统是非常直接了当的。

    而另一边,依赖属性系统的能力是巨大的。依赖属性的想法是通过外部扩展值来计算属性的值。外部扩展输入可以是样式,主题,系统属性,动画等等。所以,你可以说一个依赖属性和 WPF 引入的大部分内置属性一起工作。

    mbos.JPG

    依赖属性的优势

    从事实上来说,依赖属性有比普通 CLR 属性更多的优势。在创建我们自己的依赖属性前让我们先看一些:

    1. 属性值继承:属性值继承意味着可以在继承中重写依赖属性,这样最后值将由最高优先级的确定。
    2. 数据验证:当属性值改变的时候,我们可以自动触发属性验证。
    3. 参与动画:依赖属性可以是动画的。WPF 动画有许多功能来在间隔时间内改变值。定义一个依赖属性,你就可以让这个属性支持动画。
    4. 参与样式:样式是定义控件的元素。我们可以在依赖属性上使用 Style Setter 。
    5. 参与模板:模板是定义元素整体结构的元素。通过定义依赖属性,我们可以在模板中使用它。
    6. 回调:在依赖属性中可以有回调,这样如果属性有任何改变都回触发回调。
    7. 资源:依赖属性可以使用资源。所以在 XAML 中,你可以定义在依赖属性中使用的资源。
    8. 重写元数据:你可以使用 PropertyMetaData 来定义依赖属性的某些行为。这样就能重写元数据来实现继承而不要求必须重新定义或实现整个属性定义。
    9. 设计支持:依赖属性受到 Visual Studio 设计器的支持。你可以在设计器的属性窗口中看到一个控件的依赖属性列表。

    上面这些特性中的一些只有依赖属性支持。动画,样式,模板,属性值继承等只能使用依赖属性加入。如果在这些情况下使用 CLR 属性,编译器将产生错误。

    怎样定义一个依赖属性

    终于到了写代码的时候了,让我们看一下可以怎样定义一个依赖属性:

    • public static readonly DependencyProperty MyCustomProperty =
    • DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1));
    • public string MyCustom
    • {
    • get
    • {
    • return this.GetValue(MyCustomProperty) as string;
    • }
    • set
    • {
    • this.SetValue(MyCustomProperty, value);
    • }
    • }

    我在上面的代码中定义了一个简单的依赖属性。你可能觉得奇怪为什么一个依赖属性剧场被定义为 static 的。是的,跟你一样,第一次看到的时候我也被吓到了。但是当我了解了依赖属性后,我开始知道为什么一个依赖属性是在类级别的了,你可以说 Class A 有一个属性 B。所以属性 B 将被类 A 的所有对象拥有。依赖属性这样就为类 A 创建了一个对所有这些属性的观察者并且保存在这里。所以注意到依赖属性应该使用 static 声明是非常重要的。

    依赖属性的命名习俗是他应该跟传入的第一个参数同名。在我们的例子中,我们将在程序中使用的包装器 MyCustom 的名字应该传入作为 Register 方法的第一个参数,同时依赖属性的名字是在原来包装器的名字上添加一个 Property 后缀。在我们的例子中,依赖属性的名字是 MyCustomProperty 。如果你不这样做,程序中的某些方法将出现异常。

    同样应该注意的是不要在包装器重写自己的逻辑代码,因为它不会在属性被调用的时候每次都被调用。它会自己在内部调用 GetValue 和 SetValue 。如果你想在依赖属性被访问的时候执行自己的逻辑,应该使用回调。

    为属性定义元数据

    定义好最简单的依赖属性之后,让我们来对它做一些增强。为了给依赖属性添加元数据,我们使用类 PropertyMetaData 的对象。如果你是在一个 FrameworkElement 中而不是像我这样在 UserControl 或 Window 中,你应该使用 FrameworkMetaData 而不是 PropertyMetaData。让我们看一下代码:

    • static FrameworkPropertyMetadata propertymetadata =
    • new FrameworkPropertyMetadata("Comes as Default",
    • FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
    • FrameworkPropertyMetadataOptions.Journal,new
    • PropertyChangedCallback(MyCustom_PropertyChanged),
    • new CoerceValueCallback(MyCustom_CoerceValue),
    • false, UpdateSourceTrigger.PropertyChanged);
    • public static readonly DependencyProperty MyCustomProperty =
    • DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1),
    • propertymetadata, new ValidateValueCallback(MyCustom_Validate));
    • private static void MyCustom_PropertyChanged(DependencyObject dobj,
    • DependencyPropertyChangedEventArgs e)
    • {
    • //当依赖属性改变的时候将被调用
    • MessageBox.Show(string.Format(
    • "Property changed is fired : OldValue {0} NewValue : {1}", e.OldValue, e.NewValue));
    • }
    • private static object MyCustom_CoerceValue(DependencyObject dobj, object Value)
    • {
    • //当依赖属性的值被重新计算时调用,返回最近设置的值。
    • MessageBox.Show(string.Format("CoerceValue is fired : Value {0}", Value));
    • return Value;
    • }
    • private static bool MyCustom_Validate(object Value)
    • {
    • //对给依赖属性设置的值进行自定义的验证
    • //根据验证通过与否返回 true 或 false
    • MessageBox.Show(string.Format("DataValidation is Fired : Value {0}", Value));
    • return true;
    • }
    • public string MyCustom
    • {
    • get
    • {
    • return this.GetValue(MyCustomProperty) as string;
    • }
    • set
    • {
    • this.SetValue(MyCustomProperty, value);
    • }
    • }

    这段代码稍微有点复杂了。我们定义了一个 FrameworkMetaData 并且我们已经为依赖属性设置了初始值为 Comes as Default ,所以如果我们不改变依赖属性的值,它将一直把这个值作为默认值。 FrameworkPropertyMetaDataOption 可以用来为依赖属性计算元数据的类型。让我们看一下枚举的一些选项:

    • AffectsMeasure:调用对象放置处的 Layout 元素的 AffectsMeasure
    • AffectsArrange:调用布局元素的 AffectsArrange
    • AffectsParentMeasure:调用父元素的 AffectsMeasure
    • AffectsParentArrange:调用父元素的 AffectsArrange
    • AffectsRender:当值改变的时候重新渲染控件
    • NotDataBindable:禁用数据绑定
    • BindsTwoWayByDefault:默认地,数据绑定是 OneWay(单向) 的。使用这个让你的属性默认是双向绑定的。
    • Inherits:确保子控件从基类继承值。

    你可以使用 | 来应用多个选项,就像标志那样。

    当属性值被改变时将调用 PropertyChangedCallback 。CoerceValue 将会在值被改变之前被调用。意思就是当 CoerceValue 被调用后返回的值将会被赋给属性。在 CoerceValue 被调用前,验证代码块将先执行,这样就确保了传入给属性的值是否合法。在验证中,你需要返回 true 或者 false 。如果返回值是 false ,运行时将产生一个异常。所以你运行上面的代码后, MessageBox 将以下面的顺序返回:

    • ValidateCallback:你需要放入验证传入的数据也就是参数 Value 的逻辑代码。返回 True 将采用这个值, false 将抛出一个异常。
    • CoerceValue:可以根据传入的值修改或者改变值。它同样接收依赖属性作为一个参数。你可以在 CoerceValue 中调用依赖属性关联的 CoerceValueCallback 。
    • PropertyChanged:这是当值被修改之后你最后看到的消息框。你可以从 DependencyPropertyChangedEventArgs 中获得 OldValue 和 NewValue 。

    关于集合类型的依赖属性需要注意的

    CollectionType 依赖属性是当你想维护一个集合的依赖属性。我们在项目中经常需要这样做。一般来说,当你创建了一个依赖属性并且传入了初始值,初始值不会是你创建的每个对象实例的初始值。反而,依赖属性的初始值应该是注册的类型的初始值。这样,如果你想创建一个集合的依赖对象并且你希望你的对象有自己的初始值,你需要给每个单独的集合项分配初始值而不是使用元数据定义。举个例子:

    • public static readonly DependencyPropertyKey ObserverPropertyKey = DependencyProperty.RegisterReadOnly("Observer", typeof(ObservableCollection<Button>),typeof(MyCustomUC),new FrameworkPropertyMetadata(new ObservableCollection<Button>()));
    • public static readonly DependencyProperty ObserverProperty =
    • ObserverPropertyKey.DependencyProperty;
    • public ObservableCollection<Button> Observer
    • {
    • get
    • {
    • return (ObservableCollection<Button>)GetValue(ObserverProperty);
    • }
    • }

    在上面的代码中,我使用 RegisterReadonly 声明了一个 DependencyPropertyKey 。ObservableCollection 实际上是一个集合的最后是一个依赖对象的 Button 。

    现在如果你使用这个集合,你将发现当你创建 UserControl 的对象,它们中的每一个都是指向同一个依赖属性而不是由自己的依赖属性。根据定义,每个依赖属性根据类型来分配内存,如果对象2创建了依赖属性的一个实例,它将覆盖对象1的集合。因此这个对象将像一个单例类一样。为了处理这种情况,你需要在类的新实例创建的时候重新设置集合。因为属性是只读的,你需要使用 DependencyPropertyKey 调用方法 SetValue 创建新的实例。

    1. public MyCustomUC()
    2. {
    3. InitializeComponent();
    4. SetValue(ObserverPropertyKey, new ObservableCollection<Button>());
    5. }

    对每个实例来说,每个集合都将被重置因此你可以给每个创建的 UserControl 一个唯一的集合。

    属性值继承

    依赖属性支持属性值继承。通过定义,当你创建了一个自己的依赖属性,你可以容易地使用依赖属性关联的 AddOwner 方法为所有的子控件继承依赖属性。

    每个依赖属性都有 AddOwner 方法,用来创建一个对另外一个已经定义的依赖属性的连接。比如你有一个依赖对象 A ,它有一个属性叫做 Width 。如果你想依赖对象 B 的值继承 A 的值。

    1. public class A :DependencyObject
    2. {
    3. public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(int), typeof(A), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
    4. public int Height
    5. {
    6. get
    7. {
    8. return (int)GetValue(HeightProperty);
    9. }
    10. set
    11. {
    12. SetValue(HeightProperty, value);
    13. }
    14. }
    15. public B BObject { get; set; }
    16. }
    17. public class B : DependencyObject
    18. {
    19. public static readonly DependencyProperty HeightProperty;
    20. static B()
    21. {
    22. HeightProperty = A.HeightProperty.AddOwner(typeof(B),new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
    23. }
    24. public int Height
    25. {
    26. get
    27. {
    28. return (int)GetValue(HeightProperty);
    29. }
    30. set
    31. {
    32. SetValue(HeightProperty, value);
    33. }
    34. }
    35. }

    在上面的代码中,你可以看到,类 B 使用 AddOwner 继承了依赖属性 Height 而不是在类中声明同样的。这样当 A 声明的时候,如果声明 A 的高度,它将自动传给被继承的对象 B 。

    这跟普通的对象一样。当你声明了窗口的前景色,它将自动继承给所有的子元素,因此每个控件都有了同样的前景色。

    更新:

    虽然所有的依赖属性都可以使用属性值继承,但实际上只有附加属性 AttachedProperties 是有效的。我现在才知道属性值继承只有当属性作为附加属性时才有效。如果你同时给附加的属性设置了默认值和 FrameworkMetaData.Inherits ,子元素将自动继承父元素的属性值并且子元素可以修改值。查看 MSDN 获得详情。所以我上面的例子其实并不太合适,但是你看完下一节之后应该可以很容易自己创建一个。

    附加属性

    附加属性是另外一个有趣的概念。附加属性允许你在对象外面给对象附加一个属性,并且用这个对象来定义属性的值。有点迷惑?好的,让我们来看一个例子:

    假设你有一个 DockPanel ,其中包含一些你想显示的控件。现在 DockPanel 注册了一个附加属性:

    1. public static readonly DependencyProperty DockProperty = DependencyProperty.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel),new FrameworkPropertyMetadata(Dock.Left, new PropertyChangedCallback(DockPanel.OnDockChanged)),new ValidateValueCallback(DockPanel.IsValidDock));

    可以看出,上面的代码中 DockProperty 作为附加属性 Attached 定义在 DockPanel 中。我们使用 RegisterAttached 方法来注册附加的依赖属性。这样任何 DockPanel 的子 UI 元素将获的附加的 Dock 属性从而可以自定义其值并能自动传播到 DockPanel 上。

    下面让我们来声明一个附加依赖属性:

    1. public static readonly DependencyProperty IsValuePassedProperty = DependencyProperty.RegisterAttached("IsValuePassed", typeof(bool), typeof(Window1),new FrameworkPropertyMetadata(new PropertyChangedCallback(IsValuePassed_Changed)));
    2. public static void SetIsValuePassed(DependencyObject obj, bool value)
    3. {
    4. obj.SetValue(IsValuePassedProperty, value);
    5. }
    6. public static bool GetIsValuePassed(DependencyObject obj)
    7. {
    8. return (bool)obj.GetValue(IsValuePassedProperty);
    9. }

    这里我定义了一个值为 IsValuePassed 的依赖属性。对象被绑定到 Window1 上,所以你可以从任何 UI 元素上传递值给 Window1 。

    所以在我的代码中, UserControl 可以传递值给窗口:

    1. <local:MyCustomUC x:Name="ucust" Grid.Row="0" local:Window1.IsValuePassed="true"/>

    在上面的代码中你可以看到 IsValuePassed 的值可以在外部 UserControl 被设置,同时值会传递给窗口。如你所见,我在对象中添加了两个静态方法 get 和 set 。这可以用来确保值是从适当的对象传递过来的。例如,你想从一个 Button 上传递这个值,你可以这样写:

    1. private void Button_Click(object sender, RoutedEventArgs e)
    2. {
    3. Window1.SetIsValuePassed(this, !(bool)this.GetValue(IsValuePassedProperty));
    4. }

    同样的方式,DockPanel 定义了 SetDock 方法。

    总结

    总的来说,依赖属性是一个你应该在编写 WPF 应用之前就了解的最重要也是最有意思的概念。有很多场景下你需要定义依赖属性。在这篇文章中,我已经带你基本了解了依赖属性每个方面。

    WPF Tutorial - Dependency Property

    Introduction

    WPF comes with a lot of new features and alternatives that the normal Windows applications do not have. As we have already discussed some of the features of WPF, it's time to go a bit further to introduce other new features. After reading the previous articles of this tutorial, I hope you are more or less familiar with WPF architecture, borders, Effects, Transformation, Markup extensions, etc.

    So in this article, I am going to introduce you to a new property system that underlays the WPF property system. We will go further to introduce how easily you can use these properties to produce custom callbacks, create attached properties, apply animations and styles, etc. using the all new Dependency properties. Finally, we will discuss about Binding alternatives that were left behind in the previous article to finish it totally. I hope you will like this article as well as you did for all the tutorials that I provided.

    A New Property System

    Well, you must have been surprised to see this title. Yes, WPF comes with a completely new technique of defining a property of a control. The unit of the new property system is a Dependency property and the wrapper class which can create a Dependency property is called a DependencyObject. We use it to register a Dependency property into the property system to ensure that the object contains the property in it and we can easily get or set the value of those properties whenever we like. We even use normal CLR property to wrap around a dependency property and use GetValue and SetValue to get and set values passed within it.

    This is almost the same as CLR property system. So, what are the advantages of the new property system. Let's see the difference between Dependency property and CLR property.

    To work with dependency property, you must derive the class from DependencyObject as the whole observer which holds the new Property System is defined within the DependencyObject.

    Difference between Dependency Property and CLR Property

    CLR property is just a wrapper around private variables. It uses Get Set methods to retrieve and store value of a variable into it. So to be frank with you, a CLR property gives you only one block in which you can write code to invoke whenever a property is get or set. Hence CLR property system is fairly straightforward.

    On the other hand, the capabilities of Dependency property system is huge. The idea of Dependency property is to compute the value of the property based on the value of other external inputs. The external inputs might be styles, themes, system properties, animations, etc. So, you can say a dependency property works with most of the WPF inbuilt features that were introduced.

    mbos.JPG

    Advantages of Dependency Property

    As a matter of fact, a Dependency Property has lots of advantages over the normal CLR properties. Let's discuss the advantages a bit before we create our own dependency property:

    1. Property Value Inheritance: By Property Value Inheritance, we mean that value of a Dependency property can be overridden in the hierarchy in such a way that the value with highest precedence will be set ultimately.
    2. Data Validation: We can impose Data Validation to be triggered automatically whenever the property value is modified.
    3. Participation in Animation: Dependency property can animate. WPF animation has lots of capabilities to change value at an interval. Defining a dependency property, you can eventually support Animation for that property.
    4. Participation in Styles: Styles are elements that define the control. We can use Style Setters on Dependency property.
    5. Participation in Templates: Templates are elements that define the overall structure of the element. By defining Dependency property, we can use it in templates.
    6. DataBinding: As each of the Dependency properties itself invoke INotifyPropertyChanged whenever the value of the property is modified, DataBinding is supported internally. To read more about INotifyPropertyChangedplease read [^]
    7. CallBacks: You can have callbacks to a dependency property, so that whenever a property is changed, a callback is raised.
    8. Resources: A Dependency property can take a Resource. So in XAML, you can define a Resource for the definition of a Dependency property.
    9. Metadata overrides: You can define certain behavior of a dependency property using PropertyMetaData. Thus, overriding a metadata form a derived property will not require you to redefine or re implementing the whole property definition.
    10. Designer Support: A dependency property gets support from Visual Studio Designer. You can see all the dependency properties of a control listed in the Property Window of the Designer.

    In these, some of the features are only supported by Dependency Property. Animation, Styles, Templates, Property value Inheritance, etc. could only be participated using Dependency property. If you use CLR property instead in such cases, the compiler will generate an error.

    How To Define a Dependency Property

    Now coming to the actual code, let's see how you could define a Dependency Property.

    public static readonly DependencyProperty MyCustomProperty = 
    DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1));
    public string MyCustom
    {
        get
        {
            return this.GetValue(MyCustomProperty) as string;
        }
        set
        {
            this.SetValue(MyCustomProperty, value);
        }
    }

    In the above code, I have simply defined a Dependency property. You must have been surprised why a dependency property is to be declared as static. Yes, like you, even I was surprised when I first saw that. But later on, after reading about Dependency property, I came to know that a dependency property is maintained in class level, so you may say Class A to have a property B. So property B will be maintained to all the objects that class A have individually. The Dependency property thus creates an observer for all those properties maintained by class A and stores it there. Thus it is important to note that a Dependency property should be maintained as static.

    The naming convention of a dependency property states that it should have the same wrapper property which is passed as the first argument. Thus in our case, the name of the Wrapper "MyCustom" which we will use in our program should be passed as the first argument of the Register method, and also the name of the Dependency property should always be suffixed with Property to the original Wrapper key. So in our case, the name of the Dependency Property is MyCustomProperty. If you don't follow this, some of the functionality will behave abnormally in your program.

    It should also be noted that you should not write your logic inside the Wrapper property as it will not be called every time the property is called for. It will internally call the GetValue and SetValue itself. So if you want to write your own logic when the dependency property is fetched, there are callbacks to do them.

    Defining Metadata for Properties

    After defining the most simplest Dependency property ever, let's make it a little enhanced. To Add metadata for a DependencyProperty, we use the object of the class PropertyMetaData. If you are inside a FrameworkElement as I am inside a UserControl or a Window, you can use FrameworkMetaData rather than PropertyMetaData. Let's see how to code:

    static FrameworkPropertyMetadata propertymetadata = 
    new FrameworkPropertyMetadata("Comes as Default",
     FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | 
    FrameworkPropertyMetadataOptions.Journal,new 
    PropertyChangedCallback(MyCustom_PropertyChanged),
    new CoerceValueCallback(MyCustom_CoerceValue),
    false, UpdateSourceTrigger.PropertyChanged);
    
    public static readonly DependencyProperty MyCustomProperty = 
    DependencyProperty.Register("MyCustom", typeof(string), typeof(Window1),
    propertymetadata, new ValidateValueCallback(MyCustom_Validate));
    
    private static void MyCustom_PropertyChanged(DependencyObject dobj, 
    DependencyPropertyChangedEventArgs e)
    {
      //To be called whenever the DP is changed.
      MessageBox.Show(string.Format(
         "Property changed is fired : OldValue {0} NewValue : {1}", e.OldValue, e.NewValue));
    }
    
    private static object MyCustom_CoerceValue(DependencyObject dobj, object Value)
    {
    //called whenever dependency property value is reevaluated. The return value is the
    //latest value set to the dependency property
    MessageBox.Show(string.Format("CoerceValue is fired : Value {0}", Value));
    return Value;
    }
    
    private static bool MyCustom_Validate(object Value)
    {
    //Custom validation block which takes in the value of DP
    //Returns true / false based on success / failure of the validation
    MessageBox.Show(string.Format("DataValidation is Fired : Value {0}", Value));
    return true;
    }
    
    public string MyCustom
    {
    get
    {
    return this.GetValue(MyCustomProperty) as string;
    }
    set
    {
    this.SetValue(MyCustomProperty, value);
    }
    }

    So this is a little more elaborate. We define a FrameworkMetaData, where we have specified the DefaultValuefor the Dependency property as "Comes as Default", so if we don't reset the value of the DependencyProperty, the object will have this value as default. The FrameworkPropertyMetaDataOptiongives you a chance to evaluate the various metadata options for the dependency property. Let's see the various options for the enumeration.

    • AffectsMeasure: Invokes AffectsMeasure for the Layout element where the object is placed.
    • AffectsArrange: Invokes AffectsArrange for the layout element.
    • AffectsParentMeasure: Invokes AffectsMeasure for the parent.
    • AffectsParentArrange: Invokes AffectsArrange for the parent control.
    • AffectsRender: Rerenders the control when the value is modified.
    • NotDataBindableDatabinding could be disabled.
    • BindsTwoWayByDefault: By default, databinding will be OneWay. If you want your property to have a TwoWay default binding, you can use this.
    • Inherits: It ensures that the child control to inherit value from its base.

    You can use more than one option using | separation as we do for flags.

    The PropertyChangedCallback is called when the property value is changed. So it will be called after the actual value is modified already. The CoerceValue is called before the actual value is modified. That means after the CoerceValue is called, the value that we return from it will be assigned to the property. The validation block will be called before CoerceValue, so this event ensures if the value passed in to the property is valid or not. Depending on the validity, you need to return true or false. If the value is false, the runtime generates an error.

    So in the above application after you run the code, the MessageBox comes for the following:

    • ValidateCallback: You need to put logic to validate the incoming data as Value argument. True makes it to take the value, false will throw the error.
    • CoerceValue: Can modify or change the value depending on the value passed as argument. It also receives DependencyObject as argument. You can invoke CoerceValueCallback using CoerceValuemethod associated with DependencyProperty.
    • PropertyChanged: This is the final Messagebox that you see, which will be called after the value is fully modified. You can get the OldValue and NewValue from the DependencyPropertyChangedEventArgs.

    Note on CollectionType Dependency Property

    CollectionType dependency property is when you want to hold a collection of DependencyObject into a collection. We often require this in our project. In general, when you create a dependency object and pass the default value into it, the value will not be the default value for each instance of the object you create. Rather, it will be the initial value for the Dependency property for the type it is registered with. Thus, if you want to create a collection of Dependency object and want your object to have its own default value, you need to assign this value to each individual item of the dependency collection rather than defining using Metadata definition. For instance:

    public static readonly DependencyPropertyKey ObserverPropertyKey = 
    DependencyProperty.RegisterReadOnly("Observer", typeof(ObservableCollection<Button>), 
    typeof(MyCustomUC),new FrameworkPropertyMetadata(new ObservableCollection<Button>()));
    public static readonly DependencyProperty ObserverProperty = 
         ObserverPropertyKey.DependencyProperty;
    public ObservableCollection<Button> Observer
    {
    get
    {
    return (ObservableCollection<Button>)GetValue(ObserverProperty);
    }
    }

    In the above code, you can see we declare a DependencyPropertyKey using the RegisterReadonly method. The ObservableCollection is actually a collection of Button which is eventually a DependencyObject.

    Now if you use this collection, you will see that when you create object of the Usercontrol, each of them refers to the same Dependency Property rather than having its own dependency property. As by definition, each dependencyproperty allocates memory using its type, so if object 2 creates a new instance of DependencyProperty, it will overwrite the Object 1 collection. Hence the object will act as a Singleton class. To overcome with this situation, you need to reset the collection with a new instance whenever a new object of the class is created. As the property is readonly, you need to use SetValue to crate the new Instance using DependencyPropertyKey.

    public MyCustomUC()
    {
                InitializeComponent();
                SetValue(ObserverPropertyKey, new ObservableCollection<Button>());
    }

    So for each instance, the collection will reset and hence you will see a unique collection for each UserControlcreated.

    PropertyValue Inheritance

    DependencyProperty supports Property Value inheritance. By the definition, after you create a DependencyObject for you, you can easily inherit a DependencyProperty to all its child controls using AddOwner method associated to the DependencyProperty.

    Each of DependencyProperty has AddOwner method, which creates a link to another DependencyPropertythat is already defined. Say you have a DependencyObject A, which has a property called Width. You want the value of the DependencyObject B to inherit the value of A.

    public class A :DependencyObject
    {
    public static readonly DependencyProperty HeightProperty = 
       DependencyProperty.Register("Height", typeof(int), typeof(A),
    new FrameworkPropertyMetadata(0,
    FrameworkPropertyMetadataOptions.Inherits));
    
    public int Height
    {
    get
    {
    return (int)GetValue(HeightProperty);
    }
    set
    {
    SetValue(HeightProperty, value);
    }
    }
    
    public B BObject { get; set; }
    }
    
    public class B : DependencyObject
    {
    public static readonly DependencyProperty HeightProperty;
    
    static B()
    {
    HeightProperty = A.HeightProperty.AddOwner(typeof(B), 
    new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits));
    }
    
    public int Height
    {
    get
    {
    return (int)GetValue(HeightProperty);
    }
    set
    {
    SetValue(HeightProperty, value);
    }
    }
    }

    In the above code, you can see, the class inherits the DependencyProperty Height using AddOwnerwithout re declaring the same in the class. Thus when is declared, if you specify the height of A, it will automatically be transferred to the inherited child object B.

    This is same as normal objects. When you specify the Foreground of a Window, it will automatically inherit to all the child elements, hence the Foreground of each control will behave the same.

    Update

    Even though PropertyValueInhertence is there for any dependency property, it will actually work for AttachedProperties. I came to know that Property Value Inheritance only works when the property is taken as attached. If you set the Default Value for the Attached property and also set FrameworkMetaData.Inherits, the property value will be inherited from Parent to Child automatically and also gives the chance for the Children to modify the content. Check MSDN [^] for more details. So, the example that I put above is not proper for Property Value Inheritance, but you can easily create one to see after you read the next section.

    Attached Properties

    Attached property is another interesting concept. Attached property enables you to attach a property to an object that is outside the object altogether making it define value for it using that object. Seems a little confused, huh? Yes, let's see an example.

    Say you have declared a DockPanel, inside which you want your controls to appear. Now DockPanel registers an AttachedProperty.

    public static readonly DependencyProperty DockProperty = 
    DependencyProperty.RegisterAttached("Dock", typeof(Dock), typeof(DockPanel),
     new FrameworkPropertyMetadata(Dock.Left, 
    new PropertyChangedCallback(DockPanel.OnDockChanged)),
     new ValidateValueCallback(DockPanel.IsValidDock));

    You can see, the DockProperty is defined inside the DockPanel as Attached. We use RegisterAttachedmethod to register attached DependencyProperty. Thus any UIElement children to the DockPanel will get the Dock property attached to it and hence it can define its value which automatically propagates to the DockPanel.

    Let's declare an Attached DependencyProperty:

    public static readonly DependencyProperty IsValuePassedProperty = 
    DependencyProperty.RegisterAttached("IsValuePassed", typeof(bool), typeof(Window1),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(IsValuePassed_Changed)));
    
    public static void SetIsValuePassed(DependencyObject obj, bool value)
    {
    obj.SetValue(IsValuePassedProperty, value);
    }
    
    public static bool GetIsValuePassed(DependencyObject obj)
    {
    return (bool)obj.GetValue(IsValuePassedProperty);
    }

    Here I have declared a DependencyObject that holds a value IsValuePassed. The object is bound to Window1, so you can pass a value to Window1 from any UIElement.

    So in my code, the UserControl can pass the value of the property to the Window.

    <local:MyCustomUC x:Name="ucust" Grid.Row="0" local:Window1.IsValuePassed="true"/>

    You can see above the IsValuePassed can be set from an external UserControl, and the same will be passed to the actual window. As you can see, I have added two static methods to individually Set or Get values from the object. This would be used from the code to ensure we pass the value from code from appropriate objects. Say, you add a button and want to pass values from code, in such cases the static method will help.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
         Window1.SetIsValuePassed(this, !(bool)this.GetValue(IsValuePassedProperty));
    }

    In the similar way, DockPanel defines SetDock method.

    Conclusion

    To conclude, DependencyProperty is one of the most important and interesting concepts that you must know before you work with WPF. There are certain situations, where you would want to declare a DependencyProperty. From this article, I have basically visited each section of the DependencyProperty that you can work. I hope this one has helped you. Thank you for reading. Looking forward to getting your feedback.

  • 相关阅读:
    读书笔记
    JavaScript
    Vue
    读书笔记
    Python
    Python
    概率论07 联合分布
    概率论06 连续分布
    概率论05 离散分布
    概率论04 随机变量
  • 原文地址:https://www.cnblogs.com/Chary/p/No000012D.html
Copyright © 2011-2022 走看看