1、什么是依赖属性
在 WPF 中我们常用到的属性大部分都是依赖属性,它是 WPF 许多重要功能的基础,包括动画、数据绑定以及样式等。如下是一段简单的数据绑定的代码:
<Label Content="{Binding UserName,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
其中 Content 就是一个依赖属性。我们在 VS 中按下 F12 查看 Content,可以看到这样一句代码:
public static readonly DependencyProperty ContentProperty;
在WPF当中, 所有支持类似绑定的属性本质上它都是封装后的依赖属性。也就是说, 只有依赖属性才可以进行绑定。
属性和依赖属性对比
依赖属性可以说是 WPF 对 .NET 中属性的更高级的实现方式,它有着许多属性没有的功能,如属性变化通知、限制、验证等,但同时它又以类似属性的方式进行了封装,使我们在 WPF 中使用可以像访问属性一样访问依赖属性。它们的定义形式分别如下:
标准属性:
private string userName; public string UserName { get { return userName; } set { userName = value; } }
依赖属性
public string UserName { get { return (string)GetValue(UserNameProperty); } set { SetValue(UserNameProperty, value); } } public static readonly DependencyProperty UserNameProperty = DependencyProperty.Register("UserName", typeof(string), typeof(ownerclass), new PropertyMetadata(string.Empty));
其实,大部分情况下其实 WPF 开发并不需要自己创建依赖属性,因为常用的依赖属性在 WPF 的框架中都已经定义并封装好了,基本使用都可以满足。
2、依赖属性的创建与使用
2.1、定义依赖属性
总共分为四步(在 VS 中输入 propdp,然后按下两次 Tab 键可以快速创建)
- 让依赖属性的所在类型继承自DependencyObject类。
- 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
- 在类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。
- 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
// 1. 使类型继承DependencyObject类 public class User : DependencyObject { // 2. 声明一个静态只读的DependencyProperty 字段 public static readonly DependencyProperty UserNameProperty = // 3. 注册定义的依赖属性 DependencyProperty.Register("UserName", typeof(string), typeof(ownerclass), new PropertyMetadata(string.Empty, OnValueChanged)); // 4. 属性包装器,通过它来读取和设置我们刚才注册的依赖属性 public string UserName { get { return (string)GetValue(UserNameProperty); } set { SetValue(UserNameProperty, value); } } private static void OnValueChanged(DependencyObject dpobj, DependencyPropertyChangedEventArgs e) { // 当只发生改变时回调的方法 } }
- 依赖属性是通过调用DependencyObject的GetValue和SetValue来对依赖属性进行读写的。
- C#中的属性是类私有字段的封装,可以通过对该字段进行操作来对属性进行读写。
- 属性是字段的包装,WPF中使用属性对依赖属性进行包装。
2.2、依赖属性的优先级
示例:
<Window x:Class="DPSample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Button x:Name="myButton" Background="Green" Width="100" Height="30"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="Background" Value="Yellow"/> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Red" /> </Trigger> </Style.Triggers> </Style> </Button.Style> Click Me </Button> </Grid> </Window>
在上面XAML中,按钮的本地值设置的是Green,自定义Style Trigger设置的为Red,自定义的Style Setter设置的为Yellow,由于这里的本地值的优先级最高,所以按钮的背景色或者的是Green值。
2.3、依赖属性的继承
依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。
<Window x:Class="Custom_DPInherited.DPInherited" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" FontSize="18" Title="依赖属性的继承"> <StackPanel > <Label Content="继承自Window的FontSize" /> <Label Content="显式设置FontSize" TextElement.FontSize="36"/> <StatusBar>Statusbar没有继承自Window的FontSize</StatusBar> </StackPanel> </Window>
Window.FontSize设置会影响所有内部子元素字体大小,这就是依赖属性的继承。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。
另外,并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。StatusBar等控件截获了从父元素继承来的属性,并且该属性也不会影响StatusBar控件的子元素。
2.4、使用场景
1、设计自己定义的 WPF 元素,需要创建依赖属性
2、为原本不支持数据绑定、动画或其他 WPF 功能的部分代码添加这些功能时,也需要创建依赖项属性
3、附加属性
WPF中还有一类特殊的属性——附加属性。附加是一种特殊的依赖属性。它允许给一个对象添加一个值,而该对象可能对这个值一无所知。
附加属性最常见的例子就是布局容器中DockPanel类中的Dock附加属性和Grid类中Row和Column附加属性。
public class AttachedPropertyClass { // 通过使用RegisterAttached来注册一个附加属性 public static readonly DependencyProperty IsAttachedProperty = DependencyProperty.RegisterAttached("IsAttached", typeof(bool), typeof(AttachedPropertyClass), new FrameworkPropertyMetadata((bool)false)); // 通过静态方法的形式暴露读的操作 public static bool GetIsAttached(DependencyObject dpo) { return (bool)dpo.GetValue(IsAttachedProperty); } public static void SetIsAttached(DependencyObject dpo, bool value) { dpo.SetValue(IsAttachedProperty, value); } }
在上面代码中,IsAttached就是一个附加属性,附加属性没有采用CLR属性进行封装,而是使用静态SetIsAttached方法和GetIsAttached方法来存取IsAttached值。这两个静态方法内部一样是调用SetValue和GetValue来对附加属性读写的。