(2)WPF深入解析依赖属性

摘自

https://www.cnblogs.com/zhili/tag/WPF/

1、依赖属性的解析

  C#中属性是抽象模型的核心部分,而依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性,WPF中的依赖属性主要有以下三个优点:

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

1.1 自定义依赖属性

  下面通过把C#属性进行改写成依赖属性的方式来介绍依赖属性的定义,依赖属性是控件的特征,一般情况下不需要用户自己定义,不要和DataContext下用于绑定的属性混淆了。

  依赖属性是属性的包装,下面先看属性的定义:

public class Person
{
    public string Name { get; set; }
}

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

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

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

public class Person : DependencyObject
{
    // 声明一个静态只读的DependencyProperty 字段
    public static readonly DependencyProperty nameProperty;
static Person() { // 注册定义的依赖属性 nameProperty = DependencyProperty.Register( "Name", //依赖属性的包装器 typeof(string), //依赖属性的存储值类型 typeof(Person), //依赖属性的宿主类型 new PropertyMetadata("Learning Hard",OnValueChanged)); //指定依赖属性的初始值及对应的回调函数 } //包装器,通过它来读取和设置我们刚才注册的依赖属性 public string Name { get { return (string)GetValue(nameProperty); } set { SetValue(nameProperty, value); } } private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e) { // 当依赖属性值变化时,调用回调的方法 } }

1.2 依赖属性的优先级

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

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

 

1.3 依赖属性的继承

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

 

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

  StatusBar没有显式设置FontSize值,但它的字体大小没有继承Window.FontSize的值,而是保持了系统的默认值。这是因为并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件等控件就不支持继承。

  通过AddOwer的方法,可以使自定义依赖属性继承父控件的指定属性。

1.4 只读依赖属性

  在C#属性中,我们可以通过设置只读属性来防止外界恶意更改该属性值,同样,在WPF中也可以设置只读依赖属性。如IsMouseOver就是一个只读依赖属性。下面代码实现了一个只读依赖属性。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // 内部使用SetValue来设置值
        SetValue(counterKey, 8);
    }

    // 属性包装器,只提供GetValue,你也可以设置一个private的SetValue进行限制。
    public int Counter
    {
        get { return (int)GetValue(counterKey.DependencyProperty); }
    }

    // 使用RegisterReadOnly来代替Register来注册一个只读的依赖属性
    private static readonly DependencyPropertyKey counterKey =
        DependencyProperty.RegisterReadOnly("Counter",
        typeof(int),
        typeof(MainWindow),
        new PropertyMetadata(0));
}

1.5 依赖属性强制和验证

  在对依赖属性设置新值的时候,有可能需要对值进行某种转换(强制),也需要对新值进行验证防止出错(如阈值限制)。WPF提供了CoerceValueCallback和ValidateValueCallback两种方法来处理这种情况:

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

  1. 首先,CoerceValueCallback方法可以修改新值或返回DependencyProperty.UnsetValue
  2. 如果CoerceValueCallback方法强制修改了提供的值,此时会激活ValidateValueCallback方法进行验证,如果该方法返回为true,表示该值合法;否则非法。
  3. 如果上面两个阶段都成功的话,最后会触发PropertyChangedCallback方法来触发依赖属性值的更改。

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

class Program
    {
        static void Main(string[] args)
        {
            SimpleDPClass sDPClass = new SimpleDPClass();
            sDPClass.SimpleDP = 2;
            Console.ReadLine();
        }
    }

    public class SimpleDPClass : DependencyObject
    {
        public static readonly DependencyProperty SimpleDPProperty =
            DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
                new FrameworkPropertyMetadata((double)0.0,
                    FrameworkPropertyMetadataOptions.None,
                    new PropertyChangedCallback(OnValueChanged),
                    new CoerceValueCallback(CoerceValue)),
                    new ValidateValueCallback(IsValidValue));

        public double SimpleDP
        {
            get { return (double)GetValue(SimpleDPProperty); }
            set { SetValue(SimpleDPProperty, value); }
        }

        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Console.WriteLine("当值改变时,我们可以做的一些操作,具体可以在这里定义: {0}", e.NewValue);
        }

        private static object CoerceValue(DependencyObject d, object value)
        {
            Console.WriteLine("对值进行限定,强制值: {0}", value);
            return value;
        }

        private static bool IsValidValue(object value)
        {
            Console.WriteLine("验证值是否通过,返回bool值,如果返回True表示验证通过,否则会以异常的形式暴露: {0}", value);
            return true;
        }
    }

1.6  依赖属性的监听

  当依赖属性放生改变后,我们想要“监听”到属性的变化,并作出相应变化。(OnValueChanged也是对依赖属性的一种监听

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

public class MyTextBox : TextBox
{
    public MyTextBox()
        : base()
    {
    }
    
    static MyTextBox()
    {
        //第一种方法,通过OverrideMetadata
        TextProperty.OverrideMetadata(typeof(MyTextBox), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
    }
    
    private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        MessageBox.Show("", "Changed");
    }
}

第二种方法:这个方法更加简单,获取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");
}

 

posted @ 2020-08-10 17:29  代码吸血虫  阅读(274)  评论(0)    收藏  举报