WPF 入门笔记 - 05 - 依赖属性

如果预计中的不幸没有发生的话,我们就会收获意外的喜悦。 --人生的智慧 - 叔本华

WPF属性系统

这一部分是中途加的,直接依赖属性有点迷糊😪,正好有了绑定的基础,理解起来还一些。

WPF提供一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能。这些服务通常统称为WPF属性系统。 由WPF属性系统支持的属性称为依赖属性。

WPF中,属性可以分为以下几类:

  1. CLR属性(CLR Properties):CLR属性是指使用C#或其他.NET语言在代码中定义的普通属性,通常用于表示类的内部状态或行为,并不具备依赖属性的高级特性。

  2. 相关属性(Related Properties):相关属性指的是一组彼此关联的属性,它们一起控制某个方面的控件或元素的外观或行为。例如,FontSizeFontFamilyBackground是相关属性,它们一起定义了文本的字体大小和字体系列,具体内容可查阅WPF 入门笔记 - 03 - 样式基础及模板 - 相关属性章节

     <DockPanel Background="White">
       …
     </ DockPanel>
    
  3. 附加属性(Attached Properties):附加属性是一种特殊类型的依赖属性,它可以附加到任何元素上,而不仅仅是它所属的类型。附加属性允许在不修改元素的原始类型定义的情况下,为元素添加额外的属性。常见的例子是Grid.RowGrid.Column属性,它们用于指定元素在Grid布局中所在的行和列。

    <Grid Name="MyGrid" Background="Wheat"
    	  ShowGridLines="False">
      <Grid.ColumnDefinitions>
    	<ColumnDefinition/>
    	<ColumnDefinition/>
    	<ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
    	<RowDefinition/>
    	<RowDefinition/>
    	<RowDefinition/>
      </Grid.RowDefinitions>
      <TextBox  Name ="InputText"  Grid.Column ="0" Grid.Row ="0"
    		   Grid.ColumnSpan ="9" FontSize ="12" FontWeight =
    		   "DemiBold" Margin ="5,2,10,3"/>
      <Button Name="B7" Click="DigitBtn_Click"  Grid.Column="1"
    		  Grid.Row="2" Margin ="2">7</Button>
      <Button Name="B8" Click="DigitBtn_Click"  Grid.Column="1"
    		  Grid.Row="2" Margin ="2">8</Button>
      <Button Name="B9" Click="DigitBtn_Click"  Grid.Column="1"
    		  Grid.Row="2" Margin ="2">9</Button>
    </Grid>
    
  4. 依赖属性(Dependency Properties):依赖属性是WPF中的一种特殊类型的属性,具有依赖项对象和依赖项属性的特性。它们支持数据绑定、样式、动画、值继承等高级特性,并具有属性系统提供的更强大的功能。依赖属性在WPF中广泛使用,用于控件的布局、外观、行为等方面。

依赖属性

依赖属性(Dependency Properties)是WPF中的一项关键特性,它具有一些附加的功能和特性,使其在数据绑定样式应用动画属性值继承等方面更加强大和灵活。它被视为一种具有依赖关系的属性,可以在没有明确值的情况下依赖于其他对象或数据源。当使用数据绑定时,依赖属性可以从数据源获取值,并在数据源值发生变化时自动更新。

依赖属性的依赖关系和值的改变过程很复杂,尤其在涉及多个依赖属性之间的相互依赖时。但是WPF的属性系统提供了强大的机制和框架,以管理和处理这些依赖关系,确保属性值的正确传递和更新,这些功能都包含在强大的DependencyProperty类中。

需要注意的是,依赖属性的存在并不意味着所有属性都具有依赖关系,只有通过属性注册和属性元数据定义为依赖属性的属性才具有这种特性。但是,依赖属性基本应用在了WPF的**所有需要设置属性的元素。

所谓依赖,主要应用在以下地方:

1、双向绑定。有了这个,依赖项属性不用写额外的代码,也不用实现什么接口,它本身就俱备双向绑定的特性,比如把员工对象的姓名绑定到文本框,一旦绑定,只要文本框中的值发生改变,依赖项属性员工姓名也会跟着变化,反之亦然;

2、触发器。比如一个按钮背景是红色,我想让它在鼠标停留在它上面是背景变成绿色,而鼠标一旦移开,按钮恢复红色。

在传统的Windows编程中,你一定会想办法弄一些事件,或者委托来处理,还要写一堆代码。但是有了依赖项属性,你将一行代码都不用写,所有的处理均由WPF属性系统自动处理,而且触发器只是临时改变属性的值,当触完成时,属性值自动被“还原”。

3、附加属性。附加属性也是依赖项属性,它可以把A类型的的某些属性推迟到运行时根据B类型的具体情况来进行设置,而且可以同时被多个类型对象同时维护同一个属性值,但每个实例的属性值是独立的。

4、A属性改变时,也同时改变其它属性的值,比如TogleButton按下的同时,弹出下拉框。

与传统的CLR属性和面向对象相比依赖属性有很多新颖之处,其中包括:

  • 新功能:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。

  • 节约内存:在WinForm中,我们知道控件的属性很多并且通常都必须有初始值的,在新建控件对象时为每一个属性存储一个字段将是对内存的巨大浪费。WPF依赖属性很好地解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。【依赖属性最终会存放在一个静态的全局HashTable中】

  • 支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合ExpressionStyleAnimation等可以给我们带来很强的开发体验.

说了这么多到底怎么理解依赖属性呢?

依赖属性就好比家庭通讯录中每个成员的联系方式。每当有人的联系方式发生变化时,你需要通知其他家庭成员告诉他们有人更换了联系方式,让他们可以更新他们的通讯录,以保持信息一致。

初识依赖属性

回到正题,我们来看个小例子,上篇笔记中有提到过绑定中的目标属性必须是依赖属性,就举个绑定的例子顺便回顾一下绑定:

TextBox控件的Text属性为例,依赖属性的特点是可以通过数据绑定与其他对象进行绑定,实现属性值的自动更新和同步。下面是一个简单的例子:

<StackPanel>
    <TextBox x:Name="textBox" Text="Hello" />
    <TextBlock Text="{Binding ElementName=textBox, Path=Text}" />
</StackPanel>

在这个例子中,我们有一个TextBox控件和一个TextBlock控件。TextBoxText属性被设置为"Hello",而TextBlockText属性通过数据绑定与TextBoxText属性绑定。这意味着当TextBox的文本发生变化时,TextBlock的文本也会自动更新。

这种绑定关系是通过依赖属性的特性实现的,转到Text属性的定义,可以发现有一个名为TextProperty的静态只读字段,字段类型是DependencyProperty。该字段是用来标识Text依赖项属性的,DependencyPropertyWPF中用于定义依赖属性的类,它包含了属性的元数据信息以及属性的访问方法。

image-20230616104215362

❗❗❗需要注意的是,依赖属性是一个类的静态字段,只能是DependencyProperty类型的字段

看一个不是依赖属性的例子,出于安全考虑,在WPF中,PasswordBox控件的Password属性不支持数据绑定的,查看PasswordBox的定义可以看到并没有类型是DependencyPropertyPasswordProperty字段,写个例子看一下是不是不支持绑定,为了好理解,我们在绑定里把科技和狠活都整上:

<StackPanel>
  <StackPanel>
    <TextBox x:Name="textBox" Text="Hello" />
    <TextBox Text="{Binding ElementName=passWD, Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
    <PasswordBox x:Name="passWD" Password="7355608" />
  </StackPanel>
</StackPanel>

演示

XAML中数据绑定模式是双向的,默认情况下,当源对象的值发生变化时,会自动更新目标对象的值,但实际上PasswordBoxPassword属性本身不是依赖属性,因此它不支持直接的数据绑定。因此,当你修改第一个TextBox的文本时,会将新的文本值传递给passWDPassword属性,反过来,TextBox的文本是不能响应Password属性的变化的。

💬我猜啊,大家可能会有疑惑,为什么Password属性会响应TextBox的文本变化呢?

这就要看你有没有把绑定的概念搞通透了,Password属性会响应TextBox的文本变化时在绑定中做的是什么?是不是源对象属性,它的目标对象是TextBoxText属性对吧🧐

自定义依赖属性

自定义的依赖属性通常定义在自定义的依赖对象(继承自DependencyObject)中,并与其他依赖属性或相关的对象建立依赖关系。

🔖常用的普通属性定义方法:

class BaseClass
{
    private int age;

    public int Age 
    { 
        get { return age; }
        set { age = value; }
    }
}

🔖再来看看依赖属性:

📌在VS中快速创建依赖属性的方法:键入propdp,连续按两下Tab健。

class CustomTextBox:TextBox
{
     public bool IsEmpty
        {
            get { return (bool)GetValue(IsEmptyProperty); }
            set { SetValue(IsEmptyProperty, value); }
        }
    public static readonly DependencyProperty IsEmptyProperty = DependencyProperty.Register(
        "IsEmpty", 	// name
        typeof(bool), 	// propertyType
        typeof(CustomTextBox), 	// ownerType
        new PropertyMetadata(false)	// typeMetadata (defaultValue) 
    );
}

依赖属性的定义通常通过以下几个步骤定义:

  1. 在自定义依赖对象的类中继承DependencyObject,使得该类具有支持依赖属性的功能。

    示例中的CustomTextBox继承自TextBox类,显然TextBox类已经完成了这项工作。

  2. 声明一个静态只读的DependencyProperty字段,用于标识依赖属性,这个字段才是真正的依赖属性。该字段通常使用public static readonly修饰符,命名方式一般为PropertyNameProperty

    示例中的IsEmptyProperty字段。

  3. 使用DependencyProperty.Register方法进行属性注册,将依赖属性与相应的元数据关联起来。该方法接受参数包括属性名称、属性类型、拥有者类型以及可选的属性元数据

  4. 定义公共属性(通常为CLR属性) - 属性封装器,用于封装依赖属性的获取和设置逻辑。在属性的getset访问器中,使用GetValueSetValue方法来操作依赖属性的值。该项不是必要的,我们也可以直接通过使用GetValueSetValue方法来操作依赖属性的值,但是为了代码的简洁,一般通过所定义的属性进行操作。

    示例中的IsEmpty属性。

  5. 可选:根据需要,可以为依赖属性添加属性元数据,如默认值(实例中传递的false)、属性改变回调函数等,以控制依赖属性的行为和特性。

自定义依赖属性的步骤中,最重要的就是依赖属性的注册,定义的公共属性仅仅提供我们访问依赖属性的便捷方法,不写也是可以的,只不过我们得在每次使用依赖属性的时候都调用GetValueSetValue方法完成对于依赖属性的操作。一个依赖属性的注册最全的形式是下面这样子的:

public static DependencyProperty Register(string name, 
                                          Type propertyType,
                                          Type ownerType, 
                                          PropertyMetadata typeMetadata,
                                          ValidateValueCallback validateValueCallback);

 第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数是一个验证值的回调委托,第四个PropertyMetadata是我们上面提到的属性的元数据,上面示例中我们将PropertyMetadata作为依赖属性的初值,事实上它还有很多可以实现强大功能的重载,转到定义可以看到:

image-20230619103124604

其他的PropertyChangedCallbackCoerceValueCallbackPropertyMetadata构造函数中的两个回调参数,它们分别用于属性值变化和值强制转换时的回调操作:

  • PropertyChangedCallback

    • 当依赖属性的值发生变化时,PropertyChangedCallback会被调用。这个回调函数允许我们在属性值发生变化时执行一些自定义的逻辑或操作。

    • 通过在回调函数中编写逻辑,我们可以根据新的属性值执行一些特定的行为,例如更新界面上的相关元素、触发其他事件或通知其他对象。

    • 这个回调函数的签名通常是void MyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e),其中参数d表示拥有该属性的对象,参数e包含了旧值和新值等相关信息。

  • CoerceValueCallback

    • 当依赖属性的值需要强制转换时,CoerceValueCallback会被调用。这个回调函数允许我们在属性值被设置之前对其进行强制转换或限制。
    • 通过在回调函数中编写逻辑,我们可以对属性值进行验证、限制范围、自动修正或其他转换操作。
    • 这个回调函数的签名通常是object MyCoerceValueCallback(DependencyObject d, object baseValue),其中参数d表示拥有该属性的对象,参数baseValue表示属性的基本值(还未进行强制转换的值),回调函数应该返回经过转换后的值。

这两个回调函数在自定义依赖属性时非常有用,它们使我们能够在属性值变化和强制转换时进行自定义操作,以满足特定的需求,如验证、更新界面、修正值等。

📌普通属性是直接对类的一个私有字段进行封装,读取值的时候,直接读取这个字段;

📌依赖属性则是通过调用继承自DependencyObjectGetValue()SetValue()来进行操作,这也是为什么依赖属性只能是DependencyProperty类型的原因。它实际存储在DependencyProperty的一个HashTable中,键(Key)就是该属性的HashCode值,值(Value)是我们注册的DependencyProperty

image-20230618155614887

📌属性封装器不应该是验证数据或引发事件的正确位置,它仅仅提供对依赖属性的便捷访问;进行验证数据或引发事件应使用依赖项属性的回调函数。

只读依赖属性

在定义普通属性时,我们可以设置属性为只读的来防止外界对属性的恶意更改。同样在WPF中也可以设置只读的依赖属性,比如我们常见的IsMouseOver就是一个只读的依赖属性。

如何创建一个只读的依赖属性呢?定义方式和一般依赖属性的定义方式差不多,只不过需要用DependencyProperty.RegisterReadonly替换DependencyProperty.Register

比如我们把上面的IsEmpty定义成一个只读的依赖属性,用于标识TextBox输入框是否为空:

class CustomTextBox : TextBox
{
    public bool IsEmpty
    {
        get { return (bool)GetValue(IsEmptyProperty); }
    }

    public static readonly DependencyProperty IsEmptyProperty;
    public static readonly DependencyPropertyKey IsEmptyPropertyKey;

    static CustomTextBox()
    {
        IsEmptyPropertyKey=DependencyProperty.RegisterReadOnly(
            "IsEmpty",
            typeof(bool),
            typeof(TextBox),
            new PropertyMetadata(false)
        );
        IsEmptyProperty = IsEmptyPropertyKey.DependencyProperty;
    }

}

定义只读的依赖属性与可以读写的依赖属性主要有以下几处不同:

  • 注册方法是RegisterReadOnly,且返回值类型是DependencyPropertyKey
  • 为了保证取值的一致性,需要同时创建一个静态公开DependencyProperty字段IsEmptyProperty(注意名称符合依赖属性的规范),其值是IsEmptyPropertyKey.DependencyProperty,
  • 如果需要开放CLR属性的包装器,需要限制set的访问权限。

示例中的注册写在了自定义类的静态无参构造函数里,当然也可以不这么写,直接在类里面实现。

使用依赖属性

实际开发场景中,依赖属性应该在当你需要单独创建控件时, 并且希望控件的某个部分能够支持数据绑定时使用。

学会定义依赖属性后该怎么去用呢?回到我们上边定义的CustomBoxText类,我们在Xaml里声明一下看看:

<Window x:Class="WPFDemo.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:WPFDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <local:CustomTextBox Text="Casstiel" IsEmpty="True" />
        </StackPanel>
    </Grid>
</Window>

image-20230618171405617

只读的依赖属性在Xaml中是不能访问到的。

以上内容就是实现一个简单的依赖属性的方法演示。大家可以发现,我们定义的依赖属性其实没有任何作用,我们仅仅是在TextBox中给它新增了一个IsEmpty的依赖属性,它除了多出来的一个IsEmpty的配置项就和普通的TextBox就没别的区别了。

如果我们想要让IsEmpty成为一个货真价实的依赖属性,可以实实在在地告诉我们文本框是否为空,并根据不同情况做出不同地反应该怎么做呢❓

改内容请查看下文:依赖属性的触发及更新

附加属性

附加属性(Attached Properties)是一种特殊类型的依赖属性,它可以附加到其他对象上并为其提供额外的属性。与普通的依赖属性不同,附加属性没有直接的所有者对象,而是通过"附加"到其他对象上。这使得你可以在不修改对象类定义的情况下,为其添加额外的属性。最常见的附加属性地例子就是Grid.RowGrid.Column属性,用于指定元素在Grid布局中所在的行和列。

相较于依赖属性,附加属性有以下优势:

  1. 扩展性:附加属性允许我们为任何类添加额外的属性,即使这些类不是直接派生自DependencyObject。这使得我们可以为现有的类添加自定义的属性,而无需修改它们的源代码。
  2. 无侵入性:使用附加属性可以避免对现有类进行修改,从而避免破坏现有代码或增加额外的复杂性。我们可以通过外部扩展属性的方式,为类添加新的功能和数据。
  3. 可重用性:附加属性可以在不同的类之间共享和重复使用。例如,可以为多个控件添加相同的附加属性,以实现一致的行为或外观。

📌在VS中快速创建依赖属性的方法:键入propa,连续按两下Tab健。

class MyAttachedProperty
{
    public static bool GetIsHighlighted(DependencyObject obj)
    {
        return (bool)obj.GetValue(IsHighlightedProperty);
    }

    public static void SetIsHighlighted(DependencyObject obj, bool value)
    {
        obj.SetValue(IsHighlightedProperty, value);
    }

    public static readonly DependencyProperty IsHighlightedProperty =
        DependencyProperty.RegisterAttached(
        "IsHighlighted",
        typeof(bool),
        typeof(MyAttachedProperty),
        new PropertyMetadata(false)
    );
}

在这个例子中,我们定义了一个名为IsHighlighted的附加属性,它是一个bool类型的属性。可以看出,创建附加属性其实和普通的依赖属性是差不多的,只不过所有者类型的限制和属性获取/设置方法不同:

  • 属性的所有者类型可以是任何类,包括非派生自DependencyObject的类。
  • 使用DependencyProperty.RegisterAttached方法来注册附加属性,并通过该方法返回的DependencyProperty对象来获取和设置属性值。
  • 附加属性的获取和设置方法需要显式地定义为静态方法,并接受一个DependencyObject参数。

假设我们有一个TextBlock控件,我们想要将IsHighlighted属性附加到其子元素上,以指示是否要突出显示该子元素。我们可以在XAML中使用附加属性语法来设置和获取该属性的值,如下所示:

<Window x:Class="WPFDemo.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:WPFDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <StackPanel>
            <TextBlock local:MyAttachedProperty.IsHighlighted="False" Text="{Binding RelativeSource={RelativeSource Self},Path=(local:MyAttachedProperty.IsHighlighted)}"/>
        </StackPanel>
    </Grid>
</Window>

image-20230618180828044

依赖属性 or 附加属性

依赖属性和附加属性在WPF中都是用于为对象添加自定义属性的机制,它们有着不同的用途和适用场景。

依赖属性适用于为自定义控件或自定义数据对象定义属性,并允许这些属性具备数据绑定、样式化、动画等功能。依赖属性通常定义在继承自DependencyObject的类中,并且可以通过属性元数据和属性注册进行进一步的配置。使用依赖属性可以使对象具备更强大的属性系统,允许属性值的变化通知其他对象进行相应的更新。

附加属性则用于向现有的WPF控件添加自定义属性,扩展其功能,而无需修改原始控件的类定义。附加属性可以附加到任何对象上,而不仅仅是DependencyObject的子类。通过附加属性,我们可以为控件添加额外的属性,以满足特定的需求,例如布局、样式、行为等。附加属性通常定义在静态类中,并使用RegisterAttached方法进行注册。

选择使用依赖属性还是附加属性取决于你的需求和场景:

  • 如果你需要为自定义控件或数据对象定义属性,并希望能够使用数据绑定、样式化、动画等功能,那么应该选择依赖属性。
  • 如果你想向现有的WPF控件添加自定义属性,扩展其功能,并在XAML中进行配置,而不修改原始控件的类定义,那么应该选择附加属性。

在实际开发中,需要根据具体的需求来选择合适的属性机制。有时候,依赖属性和附加属性可以结合使用,以实现更复杂的功能和交互。

依赖属性的触发及更新

这里提供几种可行思路,大家可以自行探索:

  • 比较简单的通过样式(Style)和触发器(Triggers)完成
  • 在注册依赖属性时添加属性的回调方法
  • 在使用定义的依赖属性的控件注册相应的事件进而做出处理
  • 。。。。。。

用我们之前的IsEmpty做一个演示,首先,在CustomTextBox类中定义IsEmpty依赖属性和属性更改回调方法:

public class CustomTextBox : TextBox
{
    public static readonly DependencyProperty IsEmptyProperty =
        DependencyProperty.Register("IsEmpty", typeof(bool), typeof(CustomTextBox),
            new PropertyMetadata(true,OnIsEmptyChanged));

    public bool IsEmpty
    {
        get { return (bool)GetValue(IsEmptyProperty); }
        set { SetValue(IsEmptyProperty, value); }
    }

    private static void OnIsEmptyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        CustomTextBox textBox = (CustomTextBox)d;
        textBox.UpdateTextBoxStyle();
    }

    private void UpdateTextBoxStyle()
    {
        if (IsEmpty)
        {
            // 设置默认样式
            Style = null;
        }
        else
        {
            // 设置绿色背景样式
            Style = FindResource("GreenTextBoxStyle") as Style;
        }
    }
}

注册依赖属性的时候,元数据我们加一个PropertyMetadata属性变化的回调,在回调里调整样式。接下来,在XAML中定义CustomTextBox控件,并为其创建背景变化样式:

<Window.Resources>
  <Style x:Key="ColorTextBoxStyle" TargetType="local:CustomTextBox">
    <Setter Property="Background" Value="Green"/>
    <Setter Property="Foreground" Value="AntiqueWhite"/>
  </Style>
</Window.Resources>

<Grid>
    <local:CustomTextBox x:Name="customTextBox" Width="200" Height="30" TextChanged="customTextBox_TextChanged"/>
</Grid>

最后,通过注册TextBox TextChanged事件,根据文本框的内容更新IsEmpty属性的值:

private void customTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    CustomTextBox textBox = (CustomTextBox)sender;
    textBox.IsEmpty = string.IsNullOrEmpty(textBox.Text);
}

这样,根据IsEmpty属性的值的变化,就可以动态地改变CustomTextBox的样式了:

演示

属性的优先级

在WPF中,属性设置的优先级由以下顺序决定(从高到低):

  1. 直接设置值:如果属性通过直接赋值方式进行设置,优先级最高。例如,通过在XAML中显式设置属性值或通过代码为属性赋值。
  2. 样式(Style):样式可以通过Setter元素为属性设置值,并且可以将样式应用于多个元素。在应用样式时,Setter元素中的属性值将覆盖直接设置的值。
  3. 模板(Template):模板可以通过控件的外观定义,包括属性的默认值。如果在模板中为属性设置了值,它将覆盖直接设置的值和样式中的值。
  4. 数据绑定(Data Binding):使用数据绑定可以将属性绑定到数据源,以实现动态更新属性值的功能。如果属性被绑定到数据源,数据源提供的值将覆盖直接设置的值、样式中的值和模板中的值。
  5. 触发器(Triggers):触发器可以根据特定的条件来改变属性的值。当满足触发器中定义的条件时,属性将使用触发器中指定的值进行设置。触发器可以覆盖直接设置的值、样式中的值、模板中的值和数据绑定的值。
  6. 继承(Inheritance):一些属性可以从父元素继承其值。如果属性没有在当前元素上设置值,它将继承自父元素。如果没有父元素设置该属性的值,则将使用属性的默认值。
  7. 默认值(Default Value):每个属性都有一个默认值,在没有其他方式设置属性值时将使用默认值。

注意:这只是属性设置优先级的一般规则,实际情况可能会因控件类型、继承关系、样式和模板等因素而有所不同。

这部分需要在实际开发过程中理解了,比如下面的这段xaml代码:

<Window x:Class="WpfApp2.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:local="clr-namespace:WpfApp2"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="WPFDemo"
        Width="800"
        Height="450"
        Loaded="Window_Loaded"
        WindowStartupLocation="CenterScreen"
        mc:Ignorable="d">
    <Grid>
        <Button x:Name="myButton" Background="#007acc">
            <Button.Style>
                <Style TargetType="{x:Type Button}">
                    <Setter Property="Background" Value="Black" />
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="Red" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Button.Style>
            Click
        </Button>
    </Grid>
</Window>

大家觉得Button在鼠标悬停的时候会不会变成红色呢?

演示

Done

WPF的依赖属性是一项强大的功能,它允许我们创建可扩展、灵活和可重用的UI组件。通过依赖属性,我们可以实现属性的数据绑定、样式化、动画化以及属性值的有效验证和转换。在本文中,我们介绍了几个关键概念和用法,包括初始依赖属性、自定义依赖属性、只读依赖属性以及附加属性。WPF为我们提供了一组丰富的预定义的依赖属性,它们可以直接应用于WPF控件,并具有默认的行为和特性。这些初始依赖属性涵盖了常见的属性需求,例如宽度、高度、可见性等,可以极大地简化开发过程。

自定义依赖属性需要我们根据特定需求创建的属性,通过继承DependencyObject和使用DependencyProperty类进行声明和注册。自定义依赖属性使我们能够为自定义控件或现有控件添加额外的属性,并利用WPF的数据绑定和其他功能。附加属性是一种特殊的依赖属性,它允许将属性附加到其他对象上。通过附加属性,我们可以在不修改对象继承层次结构的情况下,向对象添加额外的属性。附加属性通常用于为特定控件或面板指定特定的行为或属性设置。

在使用依赖属性时,可以利用数据绑定、样式、触发器和动画等特性,实现属性值的自动同步、响应式行为和动态交互。属性的优先级机制确保了属性之间的正确顺序和生效顺序,从而实现属性的继承、覆盖和合成。

希望本文能为你提供有关依赖属性的基本内容和指导为你提供帮助。

posted @ 2023-06-19 10:48  PixelKiwi  阅读(1893)  评论(0编辑  收藏  举报