码迷,mamicode.com
首页 > 其他好文 > 详细

依赖属性

时间:2021-05-24 12:44:16      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:button   oid   tar   tao   value   stat   btn   delegate   需要   

MSDN中有一句话:One of the primary architectural philosophies used in building WPF was a preference for properties over methods or Depenevents.
这句话的意思就是WPF的主要设计思想之一是侧重属性远胜于方法和事件,能用属性解决问题,坚决不用方法和事件。

WPF依赖属性

两类一树:DependencyProperty & DependencyObject;LogicTree(逻辑树)

WPF的依赖属性与传统的.NET属性有何不同

  1. 依赖属性的值不是从字段内存中读取,而是实时计算得到(支持绑定,默认值,继承,资源,样式,动画等)(DependencyProperty)

  2. WPF提供了专门的读写依赖属性的方法(DependencyObject)

  3. 实现了依赖属性与数据类型(宿主)的解耦,可以动态的为对象扩展(增加)依赖属性(Attached Property)

依赖属性的实现原理

定义一个属性

  1. 必备的信息:类型,默认值,宿主
  2. 非必备信息:合法性检查,属性值改变时触发事件(封装)

CLR

CLR定义属性的方法:

class MyButton
{
    public Brush Background { get; set; } = new SolidColorBrush(Colors.Transparent);
}

CLR读写属性的方法:

MyButton myButton = new MyButton();
// 读
Console.WriteLine($"按钮的背景色:{myButton.Background}");
// 写
myButton.Background = new SolidColorBrush(Colors.Red);

WPF

WPF有个叫DependencyProperty的类,它用以抽象依赖属性。为依赖对象抽象类定义一个依赖属性,即是用一个DependencyProperty实例表示一个依赖属性的过程;依赖属性“背后”的类型,默认值,宿主,合法性检验,属性值改变时触发事件都是DependencyProperty的构造参数。
模型:DependencyProperty实例 <=> 类的依赖属性
针对类的某个依赖属性DP,该类的所有实例读写依赖属性DP,只能用专门的实例方法读写【表示依赖属性DP的那个DependencyProperty实例】,这也是拥有依赖属性的类必须要继承DependencyObject的原因。

class MyButton : DependencyObject
{
    // Using a DependencyProperty as the backing store for Brush.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty BrushProperty =
        DependencyProperty.Register("Brush", typeof(Brush), typeof(MyButton), new PropertyMetadata(new SolidColorBrush(Colors.Transparent)));

    public static void Main()
    {
        MyButton myButton = new MyButton();
        // 读
        myButton.GetValue(MyButton.BrushProperty);
        // 写
        myButton.SetValue(MyButton.BrushProperty, new SolidColorBrush(Colors.Red));
    }
}

为什么BrushProperty是static?

因为所有实例读取属性时,需要的是同一个DependencyProperty实例。假设我们不搞成static,每次实例化都会Register一次,返回的不是同一个DP实例。即使Register内部实现了只会注册和实例化一次,那么static更合理,保证整个应用程序只有一个栈中的引用指向DP实例,通过类名即可拿到DP实例。

定义依赖属性

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);
  1. name CLR属性包装器的名称
  2. propertyType 此依赖属性的“背后”类型
  3. ownerType 拥有此依赖属性的类型
  4. validateValueCallback 回调函数,为依赖属性赋值时,会调用此函数检验新值的合法性
  5. PropertyMetadata是FrameworkPropertyMetadata的基类,FrameworkPropertyMetadata的一些关键成员如下:
  • DefaultValue 属性的默认值
  • AffectsArrange,AffectsMeasure,AffectsParentArrange,AffectsParentMeasure 默认值为false,设置为true时,属性值变化,会重新布局该元素。
  • AffectsRender 默认值为false,设置为true时,属性值变化,会重新绘制该元素。
  • Inherits 默认值为false,设置为true时,此属性能支持UI树继承。
  • CoerceValueCallback 回调函数,在验证依赖属性之前尝试纠正属性值。返回DependencyProperty.Unset表示拒绝更新值。
  • PropertyChangedCallback,回调函数属性值发生变化时被调用.

CoerceValueCallback,ValidateValueCallback PropertyChangedCallback

public delegate object CoerceValueCallback(DependencyObject d, object baseValue);
public delegate bool ValidateValueCallback(object value);
public delegate void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e);

当为依赖属性赋值时,首先,执行CoerceValueCallback,主要目的是矫正属性值,返回一个Object,可以返回DependencyProperty.UnsetValue拒绝本次赋值。然后,执行ValidateValueCallback,true则接受本次赋值,false,则拒绝本次赋值。最后,调用PropertyChangedCallback。

ValidateValueCallback和CoerceValueCallback的区别和关系
ValidateValueCallback的实参是CoerceValueCallback的返回值,CoerceValueCallback能获知对象的其他的属性值,而ValidateValueCallback只能知道被修改的属性即将被赋予的值。

读写依赖属性

        MyButton myButton = new MyButton();
        // 读
        myButton.GetValue(MyButton.BrushProperty);
        // 写
        myButton.SetValue(MyButton.BrushProperty, new SolidColorBrush(Colors.Red));

CLR属性包装器

我们可以利用GetValue或SetValue读写实例的依赖属性,但是我们也可以简单封装一下提供等价的CLR属性,我们通过CLR属性读写依赖属性。

public Brush Brush
{
    get { return (Brush)GetValue(BrushProperty); }
    set { SetValue(BrushProperty, value); }
}

注意

不要在CLR属性包装器的访问器中,添加属性值的合理性逻辑检查或属性值改变要触发的事件,因为用户仍旧可以绕过CLR属性包装器利用GetValue和SetValue读写依赖属性。

为什么要提供CLR属性包装器?

  1. 可以在XAML中通过CLR属性操作依赖属性,如绑定,赋值,资源等
  2. 依赖对象可以作为数据源,依赖属性可以作为Path
  3. 依赖属性是天生合格的数据源,属性值变化后具有通知能力

深入理解CorceValueCallback

    public class Gauge : Control
    {
        //注册CurrentReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
        public static readonly DependencyProperty CurrentReadingProperty = DependencyProperty.Register(
            "CurrentReading",
            typeof(double),
            typeof(Gauge),
            new FrameworkPropertyMetadata(
                Double.NaN,
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnCurrentReadingChanged),
                new CoerceValueCallback(CoerceCurrentReading)
            ),
            new ValidateValueCallback(IsValidReading)
        );

        //注册MaxReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
        public static readonly DependencyProperty MaxReadingProperty = DependencyProperty.Register(
            "MaxReading",
            typeof(double),
            typeof(Gauge),
            new FrameworkPropertyMetadata(
                double.NaN,
                FrameworkPropertyMetadataOptions.None,
                new PropertyChangedCallback(OnMaxReadingChanged),
                new CoerceValueCallback(CoerceMaxReading)
            ),
            new ValidateValueCallback(IsValidReading)
        );

        //注册MinReading依赖属性,并添加PropertyChanged、CoerceValue、ValidateValue的回调委托
        public static readonly DependencyProperty MinReadingProperty = DependencyProperty.Register(
        "MinReading",
        typeof(double),
        typeof(Gauge),
        new FrameworkPropertyMetadata(
            double.NaN,
            FrameworkPropertyMetadataOptions.None,
            new PropertyChangedCallback(OnMinReadingChanged),
            new CoerceValueCallback(CoerceMinReading)
        ),
        new ValidateValueCallback(IsValidReading));

        public Gauge() : base()
        {
        }

        //属性包装器,通过它来暴露CurrentReading的值
        public double CurrentReading
        {
            get { return (double)GetValue(CurrentReadingProperty); }
            set { SetValue(CurrentReadingProperty, value); }
        }

        //属性包装器,通过它来暴露MaxReading的值
        public double MaxReading
        {
            get { return (double)GetValue(MaxReadingProperty); }
            set { SetValue(MaxReadingProperty, value); }
        }

        //属性包装器,通过它来暴露MinReading的值
        public double MinReading
        {
            get { return (double)GetValue(MinReadingProperty); }
            set { SetValue(MinReadingProperty, value); }
        }

        //验证value是否有效,如果返回True表示验证通过,否则会提示异常
        public static bool IsValidReading(object value)
        {
            Double v = (Double)value;
            return (!v.Equals(Double.NegativeInfinity) && !v.Equals(Double.PositiveInfinity));
        }

        //在CoerceCurrentReading加入强制判断赋值
        private static object CoerceCurrentReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double current = (double)value;
            if (current < g.MinReading) current = g.MinReading;
            if (current > g.MaxReading) current = g.MaxReading;
            return current;
        }

        //在CoerceMaxReading加入强制判断赋值
        private static object CoerceMaxReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double max = (double)value;
            if (max < g.MinReading) max = g.MinReading;
            return max;
        }

        //在CoerceMinReading加入强制判断赋值
        private static object CoerceMinReading(DependencyObject d, object value)
        {
            Gauge g = (Gauge)d;
            double min = (double)value;
            if (min > g.MaxReading) min = g.MaxReading;
            return min;
        }

        //当CurrentReading值改变的时候,调用MinReading和MaxReading的CoerceValue回调委托
        private static void OnCurrentReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MinReadingProperty);
            d.CoerceValue(MaxReadingProperty);
        }

        //当MaxReading值改变的时候,调用MinReading和CurrentReading的CoerceValue回调委托
        private static void OnMaxReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MinReadingProperty);
            d.CoerceValue(CurrentReadingProperty);
        }

        //当OnMinReading值改变的时候,调用CurrentReading和MaxReading的CoerceValue回调委托
        private static void OnMinReadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            d.CoerceValue(MaxReadingProperty);
            d.CoerceValue(CurrentReadingProperty);
        }
    }
<Window x:Class="WpfApp3.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfApp3" 
    WindowStartupLocation="CenterScreen" 
    Title="Callback_Validation_DPs" Height="400" Width="400">
    <StackPanel Orientation="Vertical">
        <local:Gauge x:Name="gauge1" MaxReading="100" MinReading="0" Height="20"/>
        <StackPanel Orientation="Horizontal" Height="60">
            <Label Content="当前值为 : "/>
            <Label Background="Yellow" BorderBrush="Black" BorderThickness="1" 
                   IsEnabled="False" Content="{Binding ElementName=gauge1, Path=CurrentReading}" Height="25" VerticalAlignment="Top" />
        </StackPanel>
        <Button x:Name="btnSetBelowMin" Content="设置为 -100" 
                Click="btnSetBelowMin_Click"/>
        <Button x:Name="btnSetAboveMax" Content="设置为 1000" 
                Click="btnSetAboveMax_Click"/>
        <Button x:Name="btnTest" Content="Test" Click="btnTest_Click"/>
    </StackPanel>
</Window>

技术图片

当相互作用的几个依赖属性其中一个发生变化时,在它的PropertyChangeCallback中调用受它影响的依赖属性的CoerceValue,这样才能保证相互作用关系的正确性。拿此例来说,无论我们对Max,Min,Current的赋值顺序如何,都能保证三者的值的合理性。CorceValueCallback虽然强制纠正了我们要赋给依赖属性的原始值,但是这个原始值仍旧会被WPF保存,在条件成立后,再次调用CorceValueCallback依赖属性的值会从纠正值变成原始值。
单独调用CorceValueCallback虽然也可以改变依赖属性的值,但不会触发PropertyChangedCallback。

依赖属性的继承

class MyButton : Button
{
    public int Demo
    {
        get { return (int)GetValue(DemoProperty); }
        set { SetValue(DemoProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Demo.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DemoProperty =
        DependencyProperty.Register("Demo", typeof(int), typeof(MyButton), new FrameworkPropertyMetadata(2021) {Inherits=true});
}
class MyStackPanel: StackPanel
{
    public int Demo
    {
        get { return (int)GetValue(DemoProperty); }
        set { SetValue(DemoProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Demo.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty DemoProperty = MyButton.DemoProperty.AddOwner(typeof(MyStackPanel),new FrameworkPropertyMetadata(0) { Inherits = true});
}
  1. inherits必须设置成true
  2. AddOwner时可以重新设置元数据
  3. PropertyChanged会叠加,DefaultValue会重写

截断原因:

  1. Inherits = false
  2. 其他更高优先级设置了依赖属性值
  3. Frame,StatusBar就像一个包围圈,屏蔽圈外的继承作用

获取依赖属性值的途径的优先级

  1. CoerceValueCallBack强制更改的值
  2. 动画
  3. 本地
  4. XAML或CS中的直接赋值,包括资源和数据绑定
  5. 模板父类
  6. 样式触发器(Style Trigger)
  7. 模板触发器(Template Trigger)
  8. 样式设置器(Style Setter)
  9. 模板设置器(Template Setter)
  10. 继承值
  11. 默认值

Tips

如果为依赖属性赋予了本地值,根据优先级原则,其他的样式,动画,数据绑定,触发器都会被忽略掉,怎么干掉本地值呢?

从DependencyObject中继承的ClearValue

public void ClearValue(DependencyProperty dp);

myElement.ClearValue(FrameworkElement.MarginProperty);

WPF继承链

  1. object
  2. DispatcherObject
  3. DependencyObject
  4. Visual
  5. UIElement
  6. FrameworkElement
  7. Control

WPF的属性系统的优势

WPF控件的属性值由实时的逻辑计算得到,而非存储在字段内存,WPF为什么这么做呢?

  1. 更改通知
  2. 支持传统的读写依赖属性
  3. 依赖在其他对象上,即数据绑定
  4. UI树的属性值继承
  5. 默认值
  6. Style,资源,动画
  7. 属性值变化时也影响其他属性
  8. 节省内存

附加属性 (Attached Property)

附加属性的本质

附加属性的作用就是让属性与数据类型解耦,我们可以使用附加属性动态扩展类的属性。

为什么要有附加属性

放置到DockPanel中的控件,必须具备属性Dock(Dock.Top,Dock.Buttom,Dock.Left,Dock.Right),才能被DockPanel读到控件的布局信息以正确布局。

放置到Grid中的控件,必须具备属性Row,Column才能被Grid读到控件的布局信息以正确布局。

放置到Canvas中的控件,必须具备属性Canvas.Top,Canvas.Left才能被Canvas读到控件的布局信息以正确布局。

每个控件都可能放到各种布局容器中,那么每个控件必须都必须带有Dock,Row,Column,Canvas.Top等属性。这样做看似没有任何毛病,但是某个控件,如果没有被放置到Grid,那么它的Row,Column就没有什么作用了,只是占有内存。所以,我们可以为对象动态添加属性,在对象需要的时候,添加属性,在对象不需要的时候,不添加即可。

定义附加属性

  1. 附加属性通过RegiesterAttached注册
  2. 附加属性没有普通的.NET属性包装器,通过静态方法实现读取和赋值
  3. 任何附加属性可被添加进任何对象,即使对象可能不需要也用不到这个附加属性
  4. 附加属性并非应用到定义它的那个类
class Person : DependencyObject
{

}

class School: DependencyObject
{

    public static int GetGrade(DependencyObject obj)
    {
        return (int)obj.GetValue(GradeProperty);
    }

    public static void SetGrade(DependencyObject obj, int value)
    {
        obj.SetValue(GradeProperty, value);
    }

    // Using a DependencyProperty as the backing store for Grade.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty GradeProperty =
        DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new PropertyMetadata(0));
}

// 使用附加属性
Person person = new Person();
School.SetGrade(person, 7);  

附加属性的应用

<Grid>
    <Button Grid.Column="0" Grid.Row="0"/>
</Grid>
Button button = new Button();
Grid.SetRow(button, 0);
Grid.SetColumn(button, 0);
<Button Grid.Column="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=Self}}"/>
btn.SetBinding(Grid.Row,new Binding("Value"){source = slider});

依赖属性

标签:button   oid   tar   tao   value   stat   btn   delegate   需要   

原文地址:https://www.cnblogs.com/LiuwayLi/p/14773822.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!