【WPF学习笔记】依赖属性

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 键可以快速创建)

  1. 让依赖属性的所在类型继承自DependencyObject类。
  2. 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
  3. 在类型的静态构造函数中通过Register方法完成依赖属性的元数据注册。
  4. 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
// 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、依赖属性的优先级

【WPF学习笔记】依赖属性_WPF

示例:

<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来对附加属性读写的。