WPF 桌面端开发 8-DependencyProperty

前言

DependencyProperty 用在哪儿?

  • Resources(资源)
  • Data binding(数据绑定)
  • Styles(样式)
  • Animations(动画)
  • Metadata overrides(元数据覆盖)
  • Property value inheritance(值继承)
  • WPF Designer integration(WPF 设计集成)

Resources(资源)和 Data binding(数据绑定)

ResourcesData binding中,XAML 提供了这样一种语法,来为属性赋值:

1
2
<Button Background="{DynamicResource MyBrush}"/>
<Button Content="{Binding XPath=Team/@TeamName}"/>

这种用{}括起来配上DynaminResource或者Binding关键字,这在 WPF 中叫做”Markup Extension“。

这种语法非常好用,不过如果你之前没有接触过 WPF 一定会觉得这种语法很奇怪。

如何使用这种语法不是我们现在要讨论的内容,现在的关键是如果你希望用 Markup Extension 来为属性赋值,那么这个属性必须是Dependency Property

Styles(样式)

Styles 中,WPF 为我们提供了一个叫Setter的工具来为属性赋值,比如这样:

1
2
3
<Style x:Key="GreenButtonStyle">
<Setter Property="Control.Background" Value="Green"/>
</Style>

Animations(动画)

在 WPF 的动画中,你可以使用一些方法将一个 Animation 对象应用在某些属性上,然后 WPF 会使用一个 Clock 让属性值进行变化从而产生动画效果。看这个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Button>
I am animated
<Button.Background>
<SolidColorBrush x:Name="AnimBrush"/>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimBrush"
Storyboard.TargetProperty="Color"
From="Red" To="Green" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>

注意 ColorAnimation 小节,定义了 TargetProperty 是”Color”,然后让 Color 从”Red”到”Greed”变化,同时设定了其他一些参数。动画过程不多介绍,你需要记住的是,如果你希望对某个属性应用动画,那么这个属性必须是 DependencyProperty

Metadata overrides(元数据覆盖)

这个可能比较难理解。这么说吧,每个属性都有自己的 Metadata(元数据),在里面我们可以存放比如属性的默认值,当然真实的元数据里还包含了更多的信息。具体的就不多讨论了,我们只需要了解,DependencyProperty 可以支持元数据的覆盖。在某些时候,这是非常有用的,之后会提到。

Property value inheritance(值继承)

Property Value Inheritance,属性值继承。这是 DependencyProperty 实现的很重要的一个功能,很多介绍 DependencyProperty 的文章都会用这个功能来引入 DependencyProperty。举个非常常见的例子,假设我们布局时,包含了这么一个关系:

Window => Grid => Button

这形成了一颗“逻辑树”,很容易理解,Window 里套了个 Grid,Grid 里有个 Button。

现在我们把 Window 的 Fontsize 也就是字体大小改变,你会发现在 WPF 中 Button 的字体大小也会相应变化。这在 WInform 中是做不到的。
也许你觉得没什么,仅仅是一个值的继承而已,但是如果你深入想想,Window 包含 Fontsize 这个属性,Button 也有,这很好,但是 Grid 并没有包含 Fontsize 属性啊,这个值又是怎么继承到 Button 上的呢?

答案就是利用 Dependency Property 提供的继承功能。

WPF Designer integration(WPF 设计集成)

最后所谓的WPF Designer integration,是指如果你在自定义控件中使用了 Dependency Property,那么 VS 或者 Blend 的属性编辑器中就会自动出现相应的属性。

WPF 中 DependencyProperty 到底实现了什么?

高效的属性值存储
如果你对.net 的属性以及元数据等概念了解比较深,也许你第一反应就是 DependencyProperty 封装了很多反射功能。其实不是这样的。

如果不论是Markup Extension还是Setter,还是Animation,我们仅仅通过 XAML 或程序中的一些字符串就完成了对属性的访问,都使用反射实现,会十分损耗性能。

DependencyProperty 在底层封装了高效的 Hash 算法来解决通过字符串获得值的问题。
因此,有了 DependencyProperty,我们可以放心的使用Markup Extension,Setter等等一系列令人激动的功能了,这影响的功能还不只这些,WPF 中实现的数据绑定,动画等等强大功能,都靠这个了。

属性的继承
正如上面Window => Grid => Button的例子,在 WPF 的逻辑树中,我们将使用 DependencyProperty 来完成属性的继承。

自动的进行重新布局

这可能不太好解释,如果你了解过 WPF 的 layout 机制可能会比较容易理解。
WPF 中并没有传统窗体重绘的机制,所谓“重绘”,是通过 DependencyProperty 的自动更新属性值来进行的。
当 WPF 中某些视觉元素的属性变化时,WPF 系统能够通过 DependencyProperty 自动进行重新的”Measure”来确定自己的尺寸大小是否发生了变化,或者自动”Arrange”来确定是否要重新排列自己子元素的位置,又或者自动”Render”来重新绘制元素的图形。
举个例子,我们通常把绘制 Button 的背景色代码写在 Button 控件的 OnRender 事件中,当一个 Button 的 Background 属性值变化时,通过 DependencyProperty 可以引起一个自动 Render 的过程,又比如,一个 Grid 对象的行数属性或者列数属性发生改时,Grid 应该能够自动重新排列他的子元素。

属性值的验证和强制值

DependencyProperty 还实现了验证输入值的功能,并且当这些值不满足验证条件时,强制的为属性赋一个满足条件的值。

属性改变通知
DependencyProperty 实现的另外一个功能是自动的属性改变通知,也就是当某个属性的值发生变化时执行某个函数或是触发某个事件。

这些功能都通过一定的机制被 DependencyProperty 完美的实现了,当我们需要使用这些功能的时候,我们只需要自定义一个 DependencyProperty 来实现就行了

WPF 的所有 UI 控件都是依赖对象。

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student : DependencyObject
{

public string Name
{
get { return (string)GetValue(NameProperty); } //依赖属性和附加属性定义的不同
set { SetValue(NameProperty, value); }
}

public static readonly DependencyProperty NameProperty =
DependencyProperty.Register(
"Name",
typeof(string),
typeof(Student)
);
}