如果预计中的不幸没有发生的话,我们就会收获意外的喜悦。 --人生的智慧 - 叔本华
这一部分是中途加的,直接依赖属性有点迷糊😪,正好有了绑定的基础,理解起来还一些。
WPF
提供一组服务,这些服务可用于扩展公共语言运行时 (CLR) 属性的功能。这些服务通常统称为WPF
属性系统。 由WPF
属性系统支持的属性称为依赖属性。
在WPF
中,属性可以分为以下几类:
CLR
属性(CLR Properties):CLR
属性是指使用C#
或其他.NET
语言在代码中定义的普通属性,通常用于表示类的内部状态或行为,并不具备依赖属性的高级特性。
相关属性(Related Properties):相关属性指的是一组彼此关联的属性,它们一起控制某个方面的控件或元素的外观或行为。例如,FontSize
和FontFamily
、Background
是相关属性,它们一起定义了文本的字体大小和字体系列,具体内容可查阅WPF 入门笔记 - 03 - 样式基础及模板 - 相关属性章节。
<DockPanel Background="White"> … </ DockPanel>
附加属性(Attached Properties):附加属性是一种特殊类型的依赖属性,它可以附加到任何元素上,而不仅仅是它所属的类型。附加属性允许在不修改元素的原始类型定义的情况下,为元素添加额外的属性。常见的例子是Grid.Row
和Grid.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>
依赖属性(Dependency Properties):依赖属性是WPF
中的一种特殊类型的属性,具有依赖项对象和依赖项属性的特性。它们支持数据绑定、样式、动画、值继承等高级特性,并具有属性系统提供的更强大的功能。依赖属性在WPF
中广泛使用,用于控件的布局、外观、行为等方面。
依赖属性(Dependency Properties)是WPF
中的一项关键特性,它具有一些附加的功能和特性,使其在数据绑定、样式应用、动画和属性值继承等方面更加强大和灵活。它被视为一种具有依赖关系的属性,可以在没有明确值的情况下依赖于其他对象或数据源。当使用数据绑定时,依赖属性可以从数据源获取值,并在数据源值发生变化时自动更新。
依赖属性的依赖关系和值的改变过程很复杂,尤其在涉及多个依赖属性之间的相互依赖时。但是WPF
的属性系统提供了强大的机制和框架,以管理和处理这些依赖关系,确保属性值的正确传递和更新,这些功能都包含在强大的DependencyProperty
类中。
需要注意的是,依赖属性的存在并不意味着所有属性都具有依赖关系,只有通过属性注册和属性元数据定义为依赖属性的属性才具有这种特性。但是,依赖属性基本应用在了WPF
的**所有需要设置属性的元素。
所谓依赖,主要应用在以下地方:
1、双向绑定。有了这个,依赖项属性不用写额外的代码,也不用实现什么接口,它本身就俱备双向绑定的特性,比如把员工对象的姓名绑定到文本框,一旦绑定,只要文本框中的值发生改变,依赖项属性员工姓名也会跟着变化,反之亦然;
2、触发器。比如一个按钮背景是红色,我想让它在鼠标停留在它上面是背景变成绿色,而鼠标一旦移开,按钮恢复红色。
在传统的Windows
编程中,你一定会想办法弄一些事件,或者委托来处理,还要写一堆代码。但是有了依赖项属性,你将一行代码都不用写,所有的处理均由WPF
属性系统自动处理,而且触发器只是临时改变属性的值,当触完成时,属性值自动被“还原”。
3、附加属性。附加属性也是依赖项属性,它可以把A类型的的某些属性推迟到运行时根据B类型的具体情况来进行设置,而且可以同时被多个类型对象同时维护同一个属性值,但每个实例的属性值是独立的。
4、A属性改变时,也同时改变其它属性的值,比如TogleButton
按下的同时,弹出下拉框。
与传统的CLR
属性和面向对象相比依赖属性有很多新颖之处,其中包括:
新功能:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。
节约内存:在WinForm
中,我们知道控件的属性很多并且通常都必须有初始值的,在新建控件对象时为每一个属性存储一个字段将是对内存的巨大浪费。WPF
依赖属性很好地解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。【依赖属性最终会存放在一个静态的全局HashTable
中】
支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合Expression
、Style
、Animation
等可以给我们带来很强的开发体验.
说了这么多到底怎么理解依赖属性呢?
依赖属性就好比家庭通讯录中每个成员的联系方式。每当有人的联系方式发生变化时,你需要通知其他家庭成员告诉他们有人更换了联系方式,让他们可以更新他们的通讯录,以保持信息一致。
回到正题,我们来看个小例子,上篇笔记中有提到过绑定中的目标属性必须是依赖属性,就举个绑定的例子顺便回顾一下绑定:
以TextBox
控件的Text
属性为例,依赖属性的特点是可以通过数据绑定与其他对象进行绑定,实现属性值的自动更新和同步。下面是一个简单的例子:
<StackPanel> <TextBox x:Name="textBox" Text="Hello" /> <TextBlock Text="{Binding ElementName=textBox, Path=Text}" /> </StackPanel>
在这个例子中,我们有一个TextBox
控件和一个TextBlock
控件。TextBox
的Text
属性被设置为"Hello",而TextBlock
的Text
属性通过数据绑定与TextBox
的Text
属性绑定。这意味着当TextBox
的文本发生变化时,TextBlock
的文本也会自动更新。
这种绑定关系是通过依赖属性的特性实现的,转到Text
属性的定义,可以发现有一个名为TextProperty
的静态只读字段,字段类型是DependencyProperty
。该字段是用来标识Text依赖项属性的,DependencyProperty
是WPF
中用于定义依赖属性的类,它包含了属性的元数据信息以及属性的访问方法。
❗❗❗需要注意的是,依赖属性是一个类的静态字段,只能是
DependencyProperty
类型的字段
看一个不是依赖属性的例子,出于安全考虑,在WPF
中,PasswordBox
控件的Password
属性不支持数据绑定的,查看PasswordBox
的定义可以看到并没有类型是DependencyProperty
的PasswordProperty
字段,写个例子看一下是不是不支持绑定,为了好理解,我们在绑定里把科技和狠活都整上:
<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
中数据绑定模式是双向的,默认情况下,当源对象的值发生变化时,会自动更新目标对象的值,但实际上PasswordBox
的Password
属性本身不是依赖属性,因此它不支持直接的数据绑定。因此,当你修改第一个TextBox
的文本时,会将新的文本值传递给passWD
的Password
属性,反过来,TextBox
的文本是不能响应Password
属性的变化的。
💬我猜啊,大家可能会有疑惑,为什么
Password
属性会响应TextBox
的文本变化呢?这就要看你有没有把绑定的概念搞通透了,
Password
属性会响应TextBox
的文本变化时在绑定中做的是什么?是不是源对象属性,它的目标对象是TextBox
的Text
属性对吧🧐
自定义的依赖属性通常定义在自定义的依赖对象(继承自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) ); }
依赖属性的定义通常通过以下几个步骤定义:
在自定义依赖对象的类中继承DependencyObject
,使得该类具有支持依赖属性的功能。
示例中的CustomTextBox
继承自TextBox
类,显然TextBox
类已经完成了这项工作。
声明一个静态只读的DependencyProperty
字段,用于标识依赖属性,这个字段才是真正的依赖属性。该字段通常使用public static readonly
修饰符,命名方式一般为PropertyNameProperty
。
示例中的IsEmptyProperty
字段。
使用DependencyProperty.Register
方法进行属性注册,将依赖属性与相应的元数据关联起来。该方法接受参数包括属性名称、属性类型、拥有者类型以及可选的属性元数据。
定义公共属性(通常为CLR
属性) - 属性封装器,用于封装依赖属性的获取和设置逻辑。在属性的get
和set
访问器中,使用GetValue
和SetValue
方法来操作依赖属性的值。该项不是必要的,我们也可以直接通过使用GetValue
和SetValue
方法来操作依赖属性的值,但是为了代码的简洁,一般通过所定义的属性进行操作。
示例中的IsEmpty
属性。
可选:根据需要,可以为依赖属性添加属性元数据,如默认值(实例中传递的false
)、属性改变回调函数等,以控制依赖属性的行为和特性。
自定义依赖属性的步骤中,最重要的就是依赖属性的注册,定义的公共属性仅仅提供我们访问依赖属性的便捷方法,不写也是可以的,只不过我们得在每次使用依赖属性的时候都调用GetValue
和SetValue
方法完成对于依赖属性的操作。一个依赖属性的注册最全的形式是下面这样子的:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback);
第一个参数是该依赖属性的名字,第二个参数是依赖属性的类型,第三个参数是该依赖属性的所有者的类型,第五个参数是一个验证值的回调委托,第四个PropertyMetadata
是我们上面提到的属性的元数据,上面示例中我们将PropertyMetadata
作为依赖属性的初值,事实上它还有很多可以实现强大功能的重载,转到定义可以看到:
其他的PropertyChangedCallback
和CoerceValueCallback
是PropertyMetadata
构造函数中的两个回调参数,它们分别用于属性值变化和值强制转换时的回调操作:
PropertyChangedCallback
:
当依赖属性的值发生变化时,PropertyChangedCallback
会被调用。这个回调函数允许我们在属性值发生变化时执行一些自定义的逻辑或操作。
通过在回调函数中编写逻辑,我们可以根据新的属性值执行一些特定的行为,例如更新界面上的相关元素、触发其他事件或通知其他对象。
这个回调函数的签名通常是void MyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
,其中参数d
表示拥有该属性的对象,参数e
包含了旧值和新值等相关信息。
CoerceValueCallback
:
CoerceValueCallback
会被调用。这个回调函数允许我们在属性值被设置之前对其进行强制转换或限制。object MyCoerceValueCallback(DependencyObject d, object baseValue)
,其中参数d
表示拥有该属性的对象,参数baseValue
表示属性的基本值(还未进行强制转换的值),回调函数应该返回经过转换后的值。这两个回调函数在自定义依赖属性时非常有用,它们使我们能够在属性值变化和强制转换时进行自定义操作,以满足特定的需求,如验证、更新界面、修正值等。
📌普通属性是直接对类的一个私有字段进行封装,读取值的时候,直接读取这个字段;
📌依赖属性则是通过调用继承自
DependencyObject
的GetValue()
和SetValue()
来进行操作,这也是为什么依赖属性只能是DependencyProperty
类型的原因。它实际存储在DependencyProperty
的一个HashTable
中,键(Key)就是该属性的HashCode
值,值(Value)是我们注册的DependencyProperty
。
📌属性封装器不应该是验证数据或引发事件的正确位置,它仅仅提供对依赖属性的便捷访问;进行验证数据或引发事件应使用依赖项属性的回调函数。
在定义普通属性时,我们可以设置属性为只读的来防止外界对属性的恶意更改。同样在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>
只读的依赖属性在
Xaml
中是不能访问到的。
以上内容就是实现一个简单的依赖属性的方法演示。
大家可以发现,我们定义的依赖属性其实没有任何作用,我们仅仅是在TextBox
中给它新增了一个IsEmpty
的依赖属性,它除了多出来的一个IsEmpty
的配置项就和普通的TextBox
就没别的区别了。
如果我们想要让IsEmpty
成为一个货真价实的依赖属性,可以实实在在地告诉我们文本框是否为空,并根据不同情况做出不同地反应该怎么做呢❓
这里提供几种可行思路,具体不做演示了,大家可以自行探索:
附加属性(Attached Properties)是一种特殊类型的依赖属性,它可以附加到其他对象上并为其提供额外的属性。与普通的依赖属性不同,附加属性没有直接的所有者对象,而是通过"附加"到其他对象上。这使得你可以在不修改对象类定义的情况下,为其添加额外的属性。最常见的附加属性地例子就是Grid.Row
和Grid.Column
属性,用于指定元素在Grid
布局中所在的行和列。
相较于依赖属性,附加属性有以下优势:
DependencyObject
。这使得我们可以为现有的类添加自定义的属性,而无需修改它们的源代码。📌在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>
依赖属性和附加属性在WPF
中都是用于为对象添加自定义属性的机制,它们有着不同的用途和适用场景。
依赖属性适用于为自定义控件或自定义数据对象定义属性,并允许这些属性具备数据绑定、样式化、动画等功能。依赖属性通常定义在继承自DependencyObject
的类中,并且可以通过属性元数据和属性注册进行进一步的配置。使用依赖属性可以使对象具备更强大的属性系统,允许属性值的变化通知其他对象进行相应的更新。
附加属性则用于向现有的WPF
控件添加自定义属性,扩展其功能,而无需修改原始控件的类定义。附加属性可以附加到任何对象上,而不仅仅是DependencyObject
的子类。通过附加属性,我们可以为控件添加额外的属性,以满足特定的需求,例如布局、样式、行为等。附加属性通常定义在静态类中,并使用RegisterAttached
方法进行注册。
选择使用依赖属性还是附加属性取决于你的需求和场景:
WPF
控件添加自定义属性,扩展其功能,并在XAML
中进行配置,而不修改原始控件的类定义,那么应该选择附加属性。在实际开发中,需要根据具体的需求来选择合适的属性机制。有时候,依赖属性和附加属性可以结合使用,以实现更复杂的功能和交互。
在WPF中,属性设置的优先级由以下顺序决定(从高到低):
XAML
中显式设置属性值或通过代码为属性赋值。Setter
元素中的属性值将覆盖直接设置的值。注意:这只是属性设置优先级的一般规则,实际情况可能会因控件类型、继承关系、样式和模板等因素而有所不同。
这部分需要在实际开发过程中理解了,比如下面的这段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
在鼠标悬停的时候会不会变成红色呢?
WPF
的依赖属性是一项强大的功能,它允许我们创建可扩展、灵活和可重用的UI
组件。通过依赖属性,我们可以实现属性的数据绑定、样式化、动画化以及属性值的有效验证和转换。在本文中,我们介绍了几个关键概念和用法,包括初始依赖属性、自定义依赖属性、只读依赖属性以及附加属性。WPF
为我们提供了一组丰富的预定义的依赖属性,它们可以直接应用于WPF
控件,并具有默认的行为和特性。这些初始依赖属性涵盖了常见的属性需求,例如宽度、高度、可见性等,可以极大地简化开发过程。
自定义依赖属性需要我们根据特定需求创建的属性,通过继承DependencyObject
和使用DependencyProperty
类进行声明和注册。自定义依赖属性使我们能够为自定义控件或现有控件添加额外的属性,并利用WPF
的数据绑定和其他功能。附加属性是一种特殊的依赖属性,它允许将属性附加到其他对象上。通过附加属性,我们可以在不修改对象继承层次结构的情况下,向对象添加额外的属性。附加属性通常用于为特定控件或面板指定特定的行为或属性设置。
在使用依赖属性时,可以利用数据绑定、样式、触发器和动画等特性,实现属性值的自动同步、响应式行为和动态交互。属性的优先级机制确保了属性之间的正确顺序和生效顺序,从而实现属性的继承、覆盖和合成。
希望本文能为你提供有关依赖属性的基本内容和指导为你提供帮助。