【WPF】依赖属性、附加属性

阅读本章之前需要的知识

属性的本质

工厂设计模式

 https://docs.microsoft.com/zh-cn/dotnet/desktop/wpf/properties/dependency-properties-overview?view=netdesktop-6.0

依赖属性DependencyProperty的诞生背景

WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来。依赖对象的概念被DependencyObject类所实现,依赖属性的概念则由DependencyProperty类所实现

WPF框架的编程经常和界面打交道,经常遇到的一个情况是某个属性的值的变化会影响到多个其他对象。比如当一个Button的改变大小超过了它的容器,他的容器应该自动调整大小。于是我们考虑在每个属性的set方法中触发一些事件,但很快我们发现现有的功能很难满足我们的需求,至少不能简洁漂亮的满足这些需求。
实际上我们的需求更加复杂,WPF中的数据绑定,XAML语法等很多地方都和属性密切相关,我们迫切需要一种功能更加强大的属性。

于是在WPF中,引入了一种特殊的属性,Dependency Property。依赖属性允许没有自己的字段,可以通过Binding绑定到其它对象的属性或者说数据源上,从而获得值,这种依赖在其它对象上的属性,就是依赖属性,当明确了它的功能,我想大家就不会对依赖二字产生疑惑了,依赖属性可以没有自己的字段,在使用时可以通过Binding从别的对象身上获取,给自己临时创建内存空间,这样不使用就不会有多余内存消耗。这种属性和普通的属性最大不同在于,它的值的来源并不单一。对这种属性的取值和赋值都会能与其他对象有影响,因此能得到很大的灵活性。

依赖属性和依赖对象

DependencyObject和DependencyPorperty两个类是WPF属性系统的核心。
在WPF中,依赖对象的概念被DependencyObject类实现;依赖属性的概念则由DependencyPorperty类实现。
必须使用依赖对象作为依赖属性的宿主,二者结合起来,才能实现完整的Binding目标被数据所驱动。DependencyObject具有GetValue和SetValue两个方法,用来获取/设置依赖属性的值。
DependencyObject是WPF系统中相当底层的一个基类,如下:

 

 

 继承树上可以看出,WPF的所有UI控件都是依赖对象。

依赖属性分类

依赖属性可以分为:

依赖属性:宿主的是类本身,因为不需要传入宿主,所以用普通的clr属性封装就可以了。普通的属性是无参函数。

附加依赖属性:宿主是其他类,附加需要传入宿主和值,必须要用有参函数封装。因此依赖属性和附加依赖属性看起来有点不一样,其实是一样的。

赖项属性的应用

 WPF中各个功能的实现都离不开依赖项属性的支持,如绑定,模板,动画,属性值的优先级

动态资源——Background="{DynamicResource MyBrush}"
数据绑定——Background="{Binding MyBrush}"
样式——<Setter Property="Background" Value="Red"/>
动画——<ColorAnimation Storyboard.TargetName="MyButton" Storyboard.TargetProperty="Background" From="Red" To="Blue"/>
元数据重写——通过依赖属性的OverrideMetadata来重写父类的属性默认行为
属性值继承——继承元素树父级的属性值(DataContext,FontSize)
WPF设计器集成——使用WPF设计器时,可以在属性窗口编辑属性值

赖项属性的命名规则:

字段总是与属性同名,但其后面追加了 Property 后缀。 有关此约定及其原因的详细信息,

 .net中的属性

Public Class MyClass{
        private int index;
        Public int Index{
            get{
                return index;
             }
            set{
               if(属性变更时){
                  //有效性检查
                      //处理或激发事件通知外部处理
                   }
            }
      }
}

 自定义依赖属性

自定义依赖属性分成4个步骤如下:

第一步:让自己的类继承自 DependencyObject基类。

在WPF中,几乎所有的UI元素都继承自DependencyObject,这个类封装了对依赖属性的存储及 访问等操作,使用静态类型与依赖属性的内部存储机制相关。WPF处理依赖属性不再像普通.NET属性那样将属性值存储到一个私有变量中,而是DependencyObject有一个实例数组EffectiveValueEntry[]用于保存每个实例的依赖属性值,使用一个字典 型的变量来存放用户显示设置的值。DependencyObject 提供过GetValue()和SetValue()方法来操作依赖属性值

第二步骤:定义依赖属性

       依赖属性的定义必须使用 public static 声明一个 DependencyProperty的变量,并且有一个Property作为后缀,该变量才是真正的依赖属性,WPF有一种特殊属性,叫附加属性,需要直接访问依赖属性的方法才能实现,所以依赖属性是public 的。 例如下面的代码定义了一个依赖属性NameProperty:

 public static DependencyProperty NameProperty;//WPF有一种特殊属性,叫附加属性,需要直接访问依赖属性的方法才能实现,所以依赖属性是public 的。

第三步骤:注册依赖属性,将类的依赖属性注册到依赖属性系统中,依赖属性系统 有一个hashtable,负责统一管理所有的wpf类的依赖属性。

     为使属性成为依赖属性,必须在属性系统维护的表中注册该属性,并为属性指定一个唯一标识符。此唯一标识符会用作后续属性系统操作的限定符。 这些操作可能是内部操作,也可能使用你自己的代码调用属性系统 API。  在静态构造函数中向属性系统注册依赖属性,并获取对象引用。依赖属性是通过调用DependencyProperty.Register静态方法创建,该方法需要传递一个属性 名称,这个名称非常重要,在定义控件Style和Template的时候,Setter的Property属性填入的值就是注册依赖属性时使用的名称。propertyType指明了依赖属性实际的类型,ownerType指明了是哪个类注册了此依赖属性,最后typeMetadata存放了一些依赖属 性的元信息,包括依赖属性使用的默认值,还有属性值发生变更时的通知函数。例如,下面的代码注册了依赖属性。

Register(String, Type, Type)     
Register(String, Type, Type, PropertyMetadata)     
Register(String, Type, Type, PropertyMetadata, ValidateValueCallback)

DependencyProperty.Register的参数说明
第一个参数为string类型,表示指明以哪个CLR属性作为这个依赖属性的包装器。就是代码"MyProperty"
第二个参数指明此依赖属性用来存储什么样的值。依赖属性未被显示赋值时,若读取之则获得此默认值,不设置此值会抛出异常。
第三个参数用来指明此依赖属性的宿主是什么类型,或者说DependencyProperty.Register方法要将这个依赖属性注册到哪个类型上

第四个参数 是 PropertyMetadata(子类FrameworkPropertyMetadata)类型,用来指定依赖属性的功能例如:默认值、属性值变更后的操作(回调函数)、继承、双向绑定、是否可以绑定数据等。
PropertyMetada都有哪些呢?常见的主要有FrameworkPropertyMetadata,UIPropertyMetadata以及PropertyMetadata。他们的继承关系是F->U->P。以最复杂的来说,FrameworkPropertyMetadata都提供了哪些功能呢?

第五个参数是是一个delegate,用来验证数据的有效性。 给依赖属性赋值时候的有效值验证。当属性变化后,PropertyChangeCallback最终被调用。这里Validate和Coerce的顺序有些乱,并没有完全依照前面谈到的Coerce->Validate的顺序。WPF对属性赋值进行了优化,当属性被修改时,首先会调用Validate来判断传入的值是否有效,如果无效就不调用后面的操作,以提高性能。从这里也可以看出,CoerceValue后面并没有紧跟着ValidateValue,而是直接调用PropertyChanged了。这是因为前面已经验证过value,如果在Coerce中没有改变value,那么就不用再验证了。如果在Coerce中改变了value,那么这里还会再次调用ValidateValue来验证,Valiate在最后一步的意思是指整个Value赋值的过程中,一定会保证最终值得到验证。

 

 

 

PropertyMetadata/FrameworkPropertyMetadata的参数说明
PropertyMetadata(Object  defaultValue, PropertyChangedCallback, CoerceValueCallback)
FrameworkPropertyMetadata(Object, FrameworkPropertyMetadataOptions, PropertyChangedCallback, CoerceValueCallback, Boolean, UpdateSourceTrigger);
Object是依赖属性的默认值 它的类型就是属性类型,
FrameworkPropertyMetadataOptions是位枚举,设置继承、双向绑定、是否可以绑定数据等
PropertyChangedCallback 属性值变更后的操作
CoerceValueCallback 修改旧值。

 

第四步骤:用属性包装,包装依赖属性

       在前面的三步中,我们完成了一个依赖属性的注册,那么我们怎样才能对这个依赖属性进行读写呢?答案就是提供一个依赖属性的实例化包装属性,通过这个属性来实现具体的读写操作。和CLR属性不同,依赖属性不是直接对私有变量的操纵,而是通过GetValue()和SetValue()方法来操作属性值的,可以使用标准的.NET属性定义语法进行封装,使依赖属性可以像标准属性那样来使用,代码如下。

 

根据前面的四步操作,我们就可以写出下面的代码:

public class Student : DependencyObject
{
    //声明一个静态只读的DependencyProperty字段
    public static readonly DependencyProperty NameProperty;
    static Student()
    {
        //注册我们定义的依赖属性Name
        NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student),
        new PropertyMetadata("名称", OnValueChanged));
    }
    private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        //当值改变时,我们可以在此做一些逻辑处理
    }

    //属性包装器,通过它来读取和设置我们刚才注册的依赖属性
    public string Name
    {
        get { return (string)GetValue(NameProperty); }
        set { SetValue(NameProperty, value); }
    }
}

 

只读依赖属性

         在以前在对于非WPF的功能来说,对于类的属性的封装中,经常会对那些希望暴露给外界只读操作的字段封装成只读属性,同样在WPF中也提供了只读属性的概念,如一些 WPF控件的依赖属性是只读的,它们经常用于报告控件的状态和信息,像IsMouseOver等属性, 那么在这个时候对它赋值就没有意义了。 或许你也会有这样的疑问:为什么不使用一般的.Net属性提供出来呢?一般的属性也可以绑定到元素上呀?这个是由于有些地方必须要用到只读依赖属性,比如 Trigger等,同时也因为内部可能有多个提供者修改其值,所以用.Net属性就不能完成天之大任了。

       那么一个只读依赖属性怎么创建呢?其实创建一个只读的依赖属性和创建一个一般的依赖属性大同小异。不同的地方就是DependencyProperty.Register变成了DependencyProperty.RegisterReadOnly。和前面的普通依赖属性一样,它将返回一个 DependencyPropertyKey。而且只提供一个GetValue给外部,这样便可以像一般属性一样使用了,只是不能在外部设置它的值罢了。

<Window x:Name="mainWindow" x:Class="WpfApp.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:WpfApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Grid>
        <Viewbox>
            <TextBlock Text="{Binding ElementName=mainWindow, Path=Counter }" />
        </Viewbox>
    </Grid>
</Window>

xaml

using System;
using System.Windows;
using System.Windows.Threading;

namespace WpfApp
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            //用SetValue的方法来设置值
            DispatcherTimer timer = new DispatcherTimer(TimeSpan.FromSeconds(1),
                DispatcherPriority.Normal, (object sender, EventArgs e) =>
                {
                    int newValue = Counter == int.MaxValue ? 0 : Counter + 1;
                    SetValue(counterKey, newValue);
                }, Dispatcher);
        }

        //属性包装器,只提供GetValue
        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));
    }
}

附加属性

当你使用XAML属性指定一个附加属性时,只有set动作是适用的。你不能通过XAML直接获得一个属性值,尽管有一些间接的机制来比较值,比如样式中的触发器。

       现在我们再继续探讨另外一种特殊的依赖属性——附加属性。附加属性是一种特殊的依赖属性。这是WPF的特性之一,通俗的理解起来就是,别人有的属性,由于你跟他产生了关系所以你也有了这个属于他的属性。

       附加属性是说一个属性本来不属于某个对象,但由于某种需求而被后来附加上,也就是把对象放入一个特定环境后对象才具有的属性就称为附加属性,附加属性的作 用就是将属性与数据类型解耦,让数据类型的设计更加灵活,举例,一个TextBox被放在不同的布局容器中时就会有不同的布局属性,这些属性就是由布局容 器为TextBox附加上的,附加属性的本质就是依赖属性,二者仅仅在注册和包装器上有一点区别。  

       附加属性是依赖属性的一种特殊形式,它可以让用户在一个元素中设置其他元素的属性。一般来说,附加属性是用于一个父元素定位其他元素布局 的。就像Grid和DockPanel元素就包含附加属性。Grid使用附加属性来指定包含子元素的特定行和列,而DockPanel使用附加属性是来指 定子元素应该停靠在面板中的何处位置。

       附加属性就是自己没有这个属性,在某些上下文中需要就被附加上去。比如StackPanel的Grid.Row属性,如果我们定义StackPanel类时定义一个Row属性是没有意义的,因为我们并不知道一定会放在Grid里,这样就造成了浪费。

例如,下面转场控件的定义使用了Grid的Row属性来将自身定位到特定的行中。

<Grid.RowDefinitions>
    <RowDefinition Height="101*"/>
    <RowDefinition Height="80"/>
    <RowDefinition Height="80"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >

       尽管对于一个普通的WPF开发人员来说,理解依赖和附加属性并不一定是必须的,但是掌握好WPF系统的整个运行机制对于提升WPF应用技术是非常重要的。

       使用附加属性,可以避开可能会防止一个关系中的不同对象在运行时相互传递信息的编码约定。一定可以针对常见的基类设置属性,以便每个对象只需获取和 设置该属性即可。但是,你可能希望在很多情况下这样做,这会使你的基类最终充斥着大量可共享的属性。它甚至可能会引入以下情况:在数百个后代中,只有两个 后代尝试使用一个属性。这样的类设计很糟糕。为了解决此问题,我们使用附加属性概念来允许对象为不是由它自己的类结构定义的属性赋值。在创建对象树中的各 个相关对象之后,在运行时从子对象读取此值。

       最好的例子就是布局面板。每一个布局面板都需要自己特有的方式来组织它的子元素。如Canvas需要Top和left来布 局,DockPanel需要Dock来布局。当然你也可以写自己的布局面板(在上一篇文章中我们对布局进行了比较细致的探讨,如果有不清楚的朋友也可以再 回顾一下)。

       下面代码中的Button 就是用了Canvas的Canvas.Top和Canvas.Left="20" 来进行布局定位,那么这两个就是传说中的附加属性。

<Canvas>
    <Button Canvas.Top="20" Canvas.Left="20" Content="Knights Warrior!"/>
</Canvas>

       定义附加属性的方法与定义依赖属性的方法一致,前面我们是使用DependencyProperty.Register来注册一个依赖属性,只是在注册属性时使用的是RegisterAttach()方法。这个RegisterAttached的参数和 Register是完全一致的,那么Attached(附加)这个概念又从何而来呢?

       其实我们使用依赖属性,一直在Attached(附加)。我们注册(构造)一个依赖属性,然后在DependencyObject中通过 GetValue和SetValue来操作这个依赖属性,也就是把这个依赖属性通过这样的方法关联到了这个DependencyObject上,只不过是 通过封装CLR属性来达到的。那么RegisterAttached又是怎样的呢?

       下面我们来看一个最简单的应用:首先我们注册(构造)一个附加属性

using System.Windows;
using System.Windows.Media;

namespace WpfApp.Services
{
    public class TurnoverManager : DependencyObject
    {
        //通过静态方法的形式暴露读的操作
        public static double GetAngle(DependencyObject obj)
        {
            return (double)obj.GetValue(AngleProperty);
        }

        //通过静态方法的形式暴露写的操作
        public static void SetAngle(DependencyObject obj, double value)
        {
            obj.SetValue(AngleProperty, value);
        }
        //通过使用RegisterAttached来注册一个附加属性
        public static readonly DependencyProperty AngleProperty =
            DependencyProperty.RegisterAttached("Angle", typeof(double), typeof(TurnoverManager), new PropertyMetadata(0.0, OnAngleChanged));

        //根据附加属性中的值,当值改变的时候,旋转相应的角度。
        private static void OnAngleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var element = obj as UIElement;
            if (element != null)
            {
                element.RenderTransformOrigin = new Point(0.5, 0.5);
                element.RenderTransform = new RotateTransform((double)e.NewValue);
            }
        }
    }
}

然后,我们在程序中使用这个我们自己定义的附加属性

<Window x:Class="WpfApp.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:WpfApp.Services"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="500">
    <Grid>
        <Grid.RowDefinitions>
        <RowDefinition Height="313*"/>
        <RowDefinition Height="57*"/>
        </Grid.RowDefinitions>
        <Canvas Grid.Row="0">
            <Ellipse Name="ellipseRed" Fill="Red" Width="100" Height="60" Canvas.Left="56" Canvas.Top="98" 
                     local:TurnoverManager.Angle="{Binding ElementName=sliderAngle, Path=Value}"/>
            <Rectangle Name="ellipseBlue" Fill="Blue" Width="80" Height="80" Canvas.Left="285" Canvas.Top="171" 
                     local:TurnoverManager.Angle="45" />
            <Button  Name="btnWelcome" Content="欢迎光临" Canvas.Left="265" Canvas.Top="48" FontSize="20" 
                     local:TurnoverManager.Angle="60"/>
        </Canvas>
        <WrapPanel Grid.Row="1">
            <Label Content="角度大小" />
            <Slider x:Name="sliderAngle" Minimum="0" Maximum="240" Width="300" />
        </WrapPanel>
    </Grid>
</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 DP
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //MainWindow继承自依赖对象,依赖对象有一个数组EffectiveValueEntry[] 用于保存依赖属性的值
        //正常属性的写法, Came属性的数据保存在MainWindow的EffectiveValueEntry[] 中
        public string Came 
        {
            
            get { return (string)GetValue(AgeProperty); } //AgeProperty 对象不是保存数据,而是保存 Came属性的元数据
            set { SetValue(AgeProperty, value); } 
        }

        //DependencyProperty.Register 工厂模式创建对象,统一管理 对象属性的元数据(属性名称+属性类型+属性owner),例如:Came属性的元数据 
      //DependencyProperty 中有一个全局的hash表用于统一管理 依赖属性
        public static DependencyProperty AgeProperty = DependencyProperty.Register("Came", typeof(string), typeof(MainWindow));

        public MainWindow()
        {
            InitializeComponent();
 //MainWindow继承自依赖对象,依赖对象有一个数组EffectiveValueEntry[] 用于保存依赖属性的值。EffectiveValueEntry中有两个值,
            SetValue(AgeProperty, "rrd");

        }
    }


}



//依赖属性再WPF中的应用
<Window x:Class="DP.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:DP"
        mc:Ignorable="d"
         Name="ThisWin"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <TextBlock Text="{Binding ElementName=ThisWin, Path=Came}" />
    </Grid>
</Window>

 在 XAML 中绑定附加属性

在 XAML 中绑定附加属性的时候需要加上括号和类型的命名空间前缀:

<ListViewItem Content="{Binding (local:DraggableElement.IsDraggable), RelativeSource={RelativeSource Self}}"
              local:DraggableElement.IsDraggable="True" />

对于 WPF 内置的命名空间(http://schemas.microsoft.com/winfx/2006/xaml/presentation 命名空间下),是不需要加前缀的。

<TextBlock x:Name="DemoTextBlock" Grid.Row="1"
           Text="{Binding (Grid.Row), RelativeSource={RelativeSource Self}}" />
跟其他的绑定一样,这里并不需要在 Binding 后面写 Path=,因为 Binding 的构造函数中传入的参数就是赋值给 Path 的。

 

依赖属性优先级列表

依赖属性的很重要的特点就是当你没有为控件的某个依赖属性显示的赋值时,控件会把自己容器的属性值借过来,当成自己的属性,当做自己的属性值。

下面列出了在将运行时值分配给依赖属性时,属性系统所使用的最终优先级顺序。 最高优先级最先列出。

  1. 属性系统强制。 有关强制转换的详细信息,请参阅强制转换和动画

  2. 活动动画或具有 Hold 行为的动画。 若要拥有实用效果,动画必须拥有比基值(无动画)更高的优先级,即使基值进行了本地设置也是如此。 有关详细信息,请参阅强制转换和动画

  3. 本地值。 可以通过“包装器”属性设置本地值,这相当于在 XAML 中设置特性或属性元素,或者使用特定实例的属性调用 SetValue API。 通过绑定或资源设置的本地值会具有与直接设置的值相同的优先级。

  4. TemplatedParent 模板属性值。 如果元素是通过模板创建(ControlTemplateDataTemplate)创建的,则具有 TemplatedParent。 有关详细信息,请参阅 TemplatedParent。 在通过 TemplatedParent 指定的模板中,优先级顺序为:

    1. 触发器。

    2. 属性集(通常通过 XAML 特性进行设置)。

  5. 隐式样式。 仅应用于 Style 属性。 Style 值是具有与元素类型匹配的 TargetType 值的任何样式资源。 样式资源必须存在于页面或应用程序中。 对隐式样式资源的查找不会扩展到主题中的样式资源。

  6. 样式触发器。 样式触发器是显式或隐式样式中的触发器。 样式必须存在于页面或应用程序中。 默认样式中的触发器的优先级较低。

  7. 模板触发器。 模板触发器是直接应用的模板或样式中的模板中的触发器。 样式必须存在于页面或应用程序中。

  8. 样式资源库值。 样式资源库值是样式中通过 Setter 应用的值。 样式必须存在于页面或应用程序中。

  9. 默认样式,也称为主题样式。 有关详细信息,请参阅默认(主题)样式。 在默认样式中,优先级顺序如下:

    1. 活动触发器。

    2. 资源库。

  10. 继承。 子元素的某些依赖属性从父元素继承其值。 因此,可能不需要在整个应用程序中对每个元素设置属性值。 有关详细信息,请参阅属性值继承

  11. 依赖属性元数据中的默认值。依赖属性可以在该属性的属性系统注册期间设置默认值。 继承依赖属性的派生类可以选择按照类型重写依赖属性元数据(包括默认值)。 有关详细信息,请参阅依赖属性元数据。 对于继承的属性,父元素的默认值优先于子元素的默认值。 因此,如果未设置可继承属性,则会使用根或父级的默认值,而不是子元素的默认值。

 

 Base Value、LocalValue与EffectiveValue

还未弄明白

https://www.cnblogs.com/Zhouyongh/archive/2009/10/20/1586278.html

https://blog.csdn.net/rendaweibuaa/article/details/9672993

DependencyProperty类刨析

WPF中所有对象的依赖属性都统一保存在Hashtable中,统一管理。

 //用于保存属性依赖属性  key:Name.hashcode()^ower.hashcode()  value:依赖属性
private static Hashtable PropertyFromName = new Hashtable();
public static readonly object UnsetValue = new NamedObject("DependencyProperty.UnsetValue");//判断值是不是DependencyProperty.UnsetValue,如果是,则清除依赖属性的值,所以我们要想对依赖属性设置空值,不要用null,要用DependencyProperty.UnsetValue
//用于保存依赖属性的列表,用于遍历,由此可见,DependencyProperty的properties中保存的是所有依赖属性创建时的数据,也就是为什么WPF每个依赖属性都可以恢复到默认值,即使你改变了该依赖属性的值很多次。
internal static ItemStructList<DependencyProperty> RegisteredPropertyList = new ItemStructList<DependencyProperty>(768);
private static int GlobalIndexCount;
private Flags _packedData;//_packedData=GlobalIndexCount++;每个依赖属性实列都有一个全局索引,相当身份ID。

//通过该属性将DependencyProperty静态类中的依赖属性和实列依赖对象的中的依赖属性值链接到一起。effectiveValueEntry[X].PropertyIndex=dp.GlobalIndex=targetIndex;
public int GlobalIndex
{
    get { return (int) (_packedData & Flags.GlobalIndexMask); }
}



private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
 {
//其他代码
       FromNameKey key = new FromNameKey(name, ownerType);
       // Create property
       DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);

      // Build key
            lock (Synchronized)
            {
                PropertyFromName[key] = dp;
            }
  //其他代码
}
//构造函数私有,保证外界不会对它进行实例化 
private DependencyProperty(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
{
  //其他代码
  lock (Synchronized)
       {
          packedData = (Flags) GetUniqueGlobalIndex(ownerType, name);
          RegisteredPropertyList.Add(this);
         }
 
//其他代码
}

internal static int GetUniqueGlobalIndex(Type ownerType, string name)
{
   //其他代码
   return GlobalIndexCount++;
  //其他代码
}
private class FromNameKey
 {
       public FromNameKey(string name, Type ownerType)
        {
                _name = name;
                _ownerType = ownerType;
                _hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();
         }
//其他代码
}

 

posted @ 2022-05-28 09:46  小林野夫  阅读(2436)  评论(1编辑  收藏  举报
原文链接:https://www.cnblogs.com/cdaniu/