WPF10绑定属性
目录
- 1. WPF属性系统
- 1.1. CLR属性(CLR Properties)
- 1.2. 相关属性(Related Properties)
- 1.3. 附加属性(Attached Properties)
- 1.4. 依赖属性(Dependency Properties)
- 2. 依赖属性
- 2.1. 定义
- 2.2. 应用场景
- 2.3. 理解
- 2.3.1. 初识依赖属性
- 2.3.2. 自定义依赖属性
- 2.3.3. 使用依赖属性
- 2.3.4. 依赖属性的触发和更新
1. WPF属性系统
在WPF中,属性可以分为以下几类:
1.1. CLR属性(CLR Properties)
CLR属性是指使用C#或其他.NET语言在代码中定义的普通属性,通常用于表示类的内部状态或行为,并不具备依赖属性的高级特性。
比如下面的Human
类:
class Human
{
private int age;
public int Age
{
get { return age; }
set
{
if (value >= 0 && value <= 100)
{ age = value; }
else
{
throw new OverflowException("Age overflow.");
}
}
}
}
1.2. 相关属性(Related Properties)
相关属性指的是一组彼此关联的属性,它们一起控制某个方面的控件或元素的外观或行为。例如,FontSize
和FontFamily
、Background
是相关属性,它们一起定义了文本的字体大小和字体系列。
<DockPanel Background="White">
…
</ DockPanel>
1.3. 附加属性(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>
1.4. 依赖属性(Dependency Properties)
依赖属性是WPF中的一种特殊类型的属性,具有依赖项对象和依赖项属性的特性。它们支持数据绑定、样式、动画、值继承等高级特性,并具有属性系统提供的更强大的功能。依赖属性在WPF中广泛使用,用于控件的布局、外观、行为等方面。
2. 依赖属性
2.1. 定义
依赖属性(Dependency Properties)是WPF中的一项关键特性,它具有一些附加的功能和特性,使其在数据绑定、样式应用、动画和属性值继承等方面更加强大和灵活。它被视为一种具有依赖关系的属性,可以在没有明确值的情况下依赖于其他对象或数据源。当使用数据绑定时,依赖属性可以从数据源获取值,并在数据源值发生变化时自动更新。
依赖属性的依赖关系和值的改变过程很复杂,尤其在涉及多个依赖属性之间的相互依赖时。但是WPF的属性系统提供了强大的机制和框架,以管理和处理这些依赖关系,确保属性值的正确传递和更新,这些功能都包含在强大的DependencyProperty
类中。
需要注意的是,依赖属性的存在并不意味着所有属性都具有依赖关系,**只有通过属性注册和属性元数据定义为依赖属性的属性才具有这种特性。**但是,依赖属性基本应用在了WPF的所有需要设置属性的元素。
2.2. 应用场景
-
双向绑定
有了这个,依赖项属性不用写额外的代码,也不用实现什么接口,它本身就俱备双向绑定的特性,比如把员工对象的姓名绑定到文本框,一旦绑定,只要文本框中的值发生改变,依赖项属性员工姓名也会跟着变化,反之亦然; -
触发器
比如一个按钮背景是红色,我想让它在鼠标停留在它上面是背景变成绿色,而鼠标一旦移开,按钮恢复红色。
在传统的Windows编程中,你一定会想办法弄一些事件,或者委托来处理,还要写一堆代码。但是有了依赖项属性,你将一行代码都不用写,所有的处理均由WPF属性系统自动处理,而且触发器只是临时改变属性的值,当触完成时,属性值自动被“还原”。
-
附加属性
附加属性也是依赖项属性,它可以把A类型的的某些属性推迟到运行时根据B类型的具体情况来进行设置,而且可以同时被多个类型对象同时维护同一个属性值,但每个实例的属性值是独立的。 -
A属性改变时,也同时改变其它属性的值,比如TogleButton按下的同时,弹出下拉框。
与传统的CLR属性和面向对象相比依赖属性有很多新颖之处,其中包括:
新功能:加入了属性变化通知,限制、验证等等功能,这样就可以使我们更方便的实现我们的应用,同时也使代码量大大减少了,许多之前不可能的功能都可以轻松的实现了。
-
节约内存:在WinForm中,我们知道控件的属性很多并且通常都必须有初始值的,在新建控件对象时为每一个属性存储一个字段将是对内存的巨大浪费。WPF依赖属性很好地解决了这个问题,它内部使用高效的稀疏存储系统,仅仅存储改变了的属性,即默认值在依赖属性中只存储一次。【依赖属性最终会存放在一个静态的全局HashTable中】
-
支持多个提供对象:我们可以通过多种方式来设置依赖属性的值。同时其内部可以储存多个值,配合Expression、Style、Animation等可以给我们带来很强的开发体验.
2.3. 理解
依赖属性就好比家庭通讯录中每个成员的联系方式。每当有人的联系方式发生变化时,你需要通知其他家庭成员告诉他们有人更换了联系方式,让他们可以更新他们的通讯录,以保持信息一致。
2.3.1. 初识依赖属性
WPF已经给我们实现了很多控件的很多依赖属性,比如:
以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类型的字段
2.3.2. 自定义依赖属性
自定义的依赖属性通常定义在自定义的依赖对象(继承自DependencyObject)中,并与其他依赖属性或相关的对象建立依赖关系。
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
表示属性的基本值(还未进行强制转换的值),回调函数应该返回经过转换后的值。
这两个回调函数在自定义依赖属性时非常有用,它们使我们能够在属性值变化和强制转换时进行自定义操作,以满足特定的需求,如验证、更新界面、修正值等。
2.3.3. 使用依赖属性
实际开发场景中,依赖属性应该在当你需要单独创建控件时, 并且希望控件的某个部分能够支持数据绑定时使用。
学会定义依赖属性后该怎么去用呢?回到我们上边定义的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>
以上内容就是实现一个简单的依赖属性的方法演示。大家可以发现,我们定义的依赖属性其实没有任何作用,我们仅仅是在TextBox中给它新增了一个IsEmpty的依赖属性,它除了多出来的一个IsEmpty的配置项就和普通的TextBox就没别的区别了。这多少太学院派了。
如果我们想要让IsEmpty成为一个货真价实的依赖属性,可以实实在在地告诉我们文本框是否为空,并根据不同情况做出不同地反应该怎么做呢❓
2.3.4. 依赖属性的触发和更新
这里提供几种可行思路:
- 比较简单的通过样式(Style)和触发器(Triggers)完成
- 在注册依赖属性时添加属性的回调方法
- 在使用定义的依赖属性的控件注册相应的事件进而做出处理
- 。。。。。。
这里我们用回调方法来做演示,在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的样式了。