WPF dependent attributes and additional attributes (explained in more usage per)

Turn: https: //www.cnblogs.com/zhili/p/WPFDependencyProperty.html

I. Introduction

  Recently feeling decadent, I have not learned to write blog posts, out of guilt, today strong force themselves to start updating WPF series. Although recently saw a WPF technology is old men of the article, but I still can not stop the systematic study of WPF. Today continue to share one of the most important knowledge in WPF - dependent properties.

Second, a comprehensive analysis of the dependency property

  Hear dependency properties, naturally think of the concept in C # attributes. C # attributes is the core of the abstract model, which is based specifically on WPF dependency property created. In WPF library implementation, dependency properties using ordinary C # attributes packaging so that we can use dependency properties through before in the same way, but we must be clear, in WPF most of us are using dependency properties, rather than the use of property. The importance of dependency property that the WPF core features, such as animation, data binding, and styles need to rely on the use of property. Since the introduction of WPF dependency properties, naturally there is reason its introduction. WPF dependency property mainly has the following three advantages:

  • Dependency attribute to the attribute change notification, limited verification functions. This allows us to more easily implement applications, while significantly reducing the amount of code. Before many functions need to write a lot of code to achieve, you can easily implement in WPF.
  • Save memory: In WinForm, each attribute UI controls have given initial value, so that each control will save a copy of the same initial values in memory. The WPF dependency property solves this problem by using a hash table to achieve internal storage mechanism, for the same attribute values of a plurality of identical controls only save a copy. More details about how to save memory dependency property reference: WPF dependency properties is how to save memory
  • Providing support for various objects: a value dependent attribute can be set by a number of ways. It can be used with expressions, style and binding to set the value of the dependency property.

Attribute is defined dependent 2.1

  The above describes the benefits depend on the properties brought about by this time, the question again, how their own definition of a dependent attribute it? C # is defined attributes all too familiar. The following C # rewrite attribute-dependent manner to introduce attributes defined by the dependent property. Here is the definition of a property:

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

  In the above properties before the dependent attribute is rewritten, the following steps are summarized below dependency property definitions:

  1. Let the type where the dependency properties inherited from DependencyObject class.
  2. Use declare a public static DependencyProperty variable, the variable is the real dependency property.
  3. In the type of static constructor by Register completion method relies metadata attributes register.
  4. Providing a package dependency property attribute by attribute this to complete the read and write operations dependency properties.

  The above four steps below the Name property to be rewritten to a dependency property specific implementation code as follows:

Copy the code
@ 1 so DependencyObject type inherits class 
    public class the Person: DependencyObject 
    { 
        // declare a static readonly 2. DependencyProperty field 
        public static Readonly DependencyProperty NameProperty; 
       
        static the Person () 
        { 
            // definition register 3. dependency property 
            nameProperty = DependencyProperty .Register ( "the Name", typeof (String), typeof (the Person), 
                new new PropertyMetadata ( "Hard Learning", OnValueChanged)); 
        } 

        // wrapper 4. properties, to read and set the dependency we have just registered with it properties 
        public String the Name 
        { 
            GET {return (String) the GetValue (NameProperty);} 
            SET {the SetValue (NameProperty, value);} 
        }

        static void OnValueChanged Private (DependencyObject dpobj, DependencyPropertyChangedEventArgs E) 
        { 
            // only when a change occurs callback method 
        } 

    }
Copy the code

  As seen from the above code, dependency property is performed by calling the reading and writing of DependencyObject SetValue and GetValue dependent properties . It uses a hash table to store the corresponding value of the attribute HashCode Key is, the value (Value) is registered DependencyPropery; C # in the wrapper class is a private attribute field, may be operated by the field read and write attributes. Summarized as follows: Attribute packaging field, WPF using attribute dependency properties packaging.

2.2 dependent on the priority attribute

   WPF dependency property allows setting value in multiple places, naturally relates to the problem of dependency property gets priority value. For example the following XMAL code, we set the background color of the button in three places, and that eventually button reads the values ​​that set it? Is Green, Yellow or Red?

Copy the code
<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>
Copy the code

  The background color above the button is Green. The reason why the background color Green, because of a dependency property WPF each access, it will in the following order from a high value in the end of the process. DETAILED priority as shown below:

  In the above XAML, the local value of the button is Green, custom Style Trigger set for Red, Style Setter customized settings for Yellow, since the highest priority where the local value, so buttons background color, or a Green is the value. If at this time the value of the local Green then removed, the background color of the button at this time is not Yellow Red. Here Despite Style Trigger priority higher than the Style Setter, but because at this time of IsMouseOver Style Trigger property is false, that is, the mouse is not over the button, once the mouse over the button, and the color of the button on to Red. At this time will reflect the Style Trigger priority higher than the Style Setter priority. So figure above priority is the ideal situation, often need specific analysis.

2.3 dependency property inheritance

  Dependency attribute can be inherited, i.e., the settings of the parent element will be automatically passed to all the child elements. The following code demonstrates the dependency property inheritance.

Copy the code
<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.= The FontSize "36" /> 
        <the StatusBar> Statusbar not inherited from the Window FontSize </ StatusBar>
    </StackPanel>
</Window>
Copy the code

Operating results of the above code is shown below:

  In the above XAML code. Window.FontSize settings affect all child elements inside the font size, which is dependent on inherited property. Label is not defined as a first FontSize, so it inherits Window.FontSize value. However, once a child element provides explicitly set, this inheritance will be interrupted, so that the value for the second Label Window.FontSize no longer work.

  At this point, you may have found the problem: StatusBar does not explicitly set FontSize value, but it does not inherit the font size value Window.FontSize, but keep the default value of the system. Well, this is what causes it? In fact, it leads to the question: Not all elements are supported property value inheritance, as StatusBar, Tooptip and Menu controls. In addition, StatusBar and other controls intercepted inherited from the parent element to the property, and the property will not affect the child element of the StatusBar control. For example, if we add a StatusBar Button. Then the Button's FontSize property change will not occur, which is the default value.

  Introduced in front of dependency property inheritance, then how do we rely on custom property to be inherited by other controls it? By AddOwer may depend on the properties inherited methods. Specific implementation code is shown below:

Copy the code
 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     }
Copy the code

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

Copy the code
<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>
Copy the code

  上面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而已。下面代码实现了一个只读依赖属性。

Copy the code
 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     }
Copy the code

  对应的XAML代码为:

Copy the code
<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>
Copy the code

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

2.5 附加属性

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

Copy the code
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);
        }
    }
Copy the code

  在上面代码中,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方法来触发依赖属性值的更改。

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

Copy the code
 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     }
Copy the code

  其运行结果如下图所示:

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

2.7  依赖属性的监听

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

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

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

Copy the code
 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     }
Copy the code

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

Copy the code
 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");
        }
Copy the code

三、总结

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

  This article download all the source code: DependencyPropertyDemo.zip

Guess you like

Origin www.cnblogs.com/lsgsanxiao/p/11330655.html