依赖属性背后的框架机制是什么?

20

我已经阅读了几本关于依赖属性的书籍,但它们都有一个共同点,就是只告诉我们如何实现它们(使用 static readonly DependencyProperty 等),但并没有告诉我们它们从内部如何工作。

我的意思是,它们是以静态方式实现的,但仍适用于所有对象。另一个困惑的点是附加属性。

是否有任何教程可以以简单的方式解释所有这些概念?

4个回答

33

我的依赖属性工作模型:

任何 DependencyObject 类都会实现两个特殊的属性。一个是该类的静态属性,它是一个 DependencyProperty 对象字典。每个类实例可以查找该字典以查找有关每个 DependencyProperty 的元信息 - 属性的名称、类型、在 get 和 set 时必须调用的任何回调函数,它如何参与属性继承等等。当您注册依赖属性时,您正在将一个条目添加到这个字典中。

另一个属性是一个实例属性:它是一个字典,由 DependencyProperty 键入,包含每个 DependencyProperty 的本地 (如果已设置)。

您在 CLR 属性的设置器和获取器中实现的 SetValueGetValue 方法基本上是强制执行的延迟计算。它们不是在后备字段中存储和检索属性的值,而是在值字典中存储和检索属性的值。

依赖属性的魔力在于 GetValueSetValue 实际上做什么。

GetValue会在对象的值字典中查找属性的值。如果找不到,它会调用父元素的GetValue以获取父元素认为的值。例如,在窗口中创建TextBox时,任何查看TextBoxFontFamily的内容实际上都是调用了GetValue 。除非您明确设置了字体,否则其字典中没有该属性的条目。因此,GetValue会向父元素请求该值。父元素可能已经设置或未设置FontFamily,如果未设置,则它的GetValue调用返回其父级的值。依此类推,直到到达Window对象并找到实际的FontFamily值。
如果在TextBox上设置了FontFamily,则SetValue会将该值存储在值字典中。下一次需要获取该TextBoxFontFamily值时,GetValue会在字典中找到该值并返回,因此不需要再次询问父元素。
如果在Window上设置FontFamilySetValue不仅会更新Window的值字典中的值,还会触发一次属性更改事件,所有依赖于该属性的东西都会听到这个事件。(这就是为什么它们被称为依赖属性的原因)。如果依赖于该属性的对象本身也是依赖属性,则会触发其自己的属性更改事件。这就是为什么更改Window上的FontFamily会更改窗口中每个控件的字体,并促使WPF重新呈现更改的控件。

附加属性使用相同的方法。任何可以拥有附加属性的对象都有一个字典,其中存储了附加属性的值。当在XAML中设置CheckBoxGrid.Column时,实际上只是向该CheckBox的字典中添加一个条目。当Grid需要知道CheckBox所在的列时,会从该字典中查找对应的值。当将对象的Grid.IsSharedSizeScope设置为True时,该对象的字典将包含一个新属性 - 一个包含每个SharedSizeKey的宽度/高度的字典。

我强调这是我的思维模型。我没有花时间用反编译工具查看RegisterGetValueSetValue的实际实现方式以确定它们的确切工作方式。我可能对细节有所错误。但这是一个能够准确预测这些行为的模型,所以它足够好。

将属性值存储在字典中的概念对于C#程序员来说非常奇怪。然而这对于Python程序员来说已经司空见惯。在Python中,所有类属性 - 实际上是所有对象 - 都存储在字典中,因此您可以通过属性访问器或查找它们来获取它们的值。依赖属性和附加属性只是.NET从Java那里盗取了一切值得盗取的东西之后,正在从Python掠夺的另一种方式。(或者从Python掠夺的地方)学习Python使我成为了一个更好的C#程序员;我向还没有学过Python的任何C#开发人员推荐它。


Robert,非常感谢您的精彩解释。我现在可以想象它们是如何工作的了。我无法正确理解附加属性。这是我所理解的:复选框具有由静态属性键入的字典。该字典可以为复选框具有的所有属性拥有键入值。但是,它还可以包含由其他对象(Grid)的静态属性(Grid.Column)键入的其他值。当Grid想要了解其位置时,它会查找由Grid.Column和Grid.Row键入的条目并使用它。因此,即使不在Grid内部,我们也可以为Grid.Column添加条目。 - Akshay J
3
我认为这是正确的,虽然 "That dictionary can have keyed values for all the properties that the CheckBox has" 应该改成 "dependency properties"。此外,我应该指出,当我写上面的帖子时,我错误地认为附加属性是一种类似于依赖属性的机制;实际上,附加属性 依赖属性。 - Robert Rossney

6
这是一个关于依赖属性的教程,链接为:http://www.wpftutorial.net/DependencyProperties.html。简单来说, DependencyProperty 对象在静态字段中的原因是它代表属性的描述而不是属性的值。每个 DependencyObject 都有一个从 DependencyProperty 对象到其值的映射。这也是附加属性的工作原理。由于每个 DependencyObject 都存储了从任何 DependencyProperty 到值的映射,因此任何类型都可以创建新的 DependencyProperty 并将其设置在任何现有的 DependencyObject 上。

2
您可以看到以下非常基本的依赖属性示例,它创建了一个自定义控件文本框,其中空格是不允许的,这意味着用户无法在文本框中输入空格。
1)创建一个名为ValidatedTextBox的类,并在该类文件中编写以下代码:
public class ValidatedTextBox : TextBox  
{  
    public ValidatedTextBox()  
    {
    }

    public static readonly DependencyProperty IsSpaceAllowedProperty =  
        DependencyProperty.Register("IsSpaceAllowed", typeof(bool), typeof(ValidatedTextBox));  

    public bool IsSpaceAllowed
    {
        get { return (bool)base.GetValue(IsSpaceAllowedProperty); }
        set { base.SetValue(IsSpaceAllowedProperty, value); }
    }

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        base.OnPreviewKeyDown(e);
        if (!IsSpaceAllowed && (e.Key == Key.Space))
        {
            e.Handled = true;
        }
    }
}

2) 现在将上述控件用于你的 .XAML 文件中

a) 添加自定义控件文本框的命名空间,如下所示:

xmlns:CustomControls="clr-namespace: ValidatedTextBox;assembly= ValidatedTextBox "  
b) 现在,使用自定义控件文本框如下:
<CustomControls:ValidatedTextBox IsSpaceAllowed="False" x:Name="MyTextBox" /> 

它将创建一个自定义控件文本框,不允许空格。因此,基本上依赖属性允许添加功能,扩展任何控件的功能。


2

是的,我已经访问过它了,它很有帮助,但我还在等待其他人的帮助。 - Akshay J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接