1. WPF中的依赖属性
依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使用方法与普通的属性是相同的。
1.1 依赖属性提供的属性功能
- 资源
数据绑定
样式
动画
元数据重写
属性值继承
WPF 设计器集成
1.2 依赖属性优先级列表
-
属性系统强制
活动动画或具有保留行为的动画。
本地值。
TemplatedParent 模板属性值。
隐式样式。
样式触发器。
模板触发器。
样式 setter 值。
默认样式,也称为 主题样式。
继承。 子元素的某些依赖属性从父元素继承其值。 因此,可能不需要在整个应用程序中设置每个元素的属性值。
依赖项属性元数据中的默认值 依赖属性可以在该属性的属性系统注册过程中设置默认值。 继承依赖属性的派生类可以重写依赖属性元数据 (包括基于每个类型 的默认值。 对于继承的属性,父元素的默认值优先于子元素的默认值。 因此,如果未设置可继承属性,则将使用根或父元素的默认值,而不是子元素的默认值。
1.3 附加属性
附加属性允许子元素为父元素中定义的属性指定唯一值。 常见方案是一个子元素,它指定其父元素在 UI 中的呈现方式。 例如,是附加属性,因为它在 的子元素上 设置,而不是本身 。
2. 依赖属性的使用
2.1 定义依赖属性
定义一个名叫的依赖属性,根据命名约定,依赖属性以属性名称加来命名
依赖属性的所有者必须继承自类
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty;
}
2.2 注册依赖属性
使用静态方法对依赖属性进行注册
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string, typeof(People;
}
参数说明:
第一个参数表示要注册的依赖属性的名称
第二个参数表示要注册的依赖属性的类型
第三个参数表示要注册的依赖属性的所有者
......(提供了多种重载方式,其他参数参考文档即可,最少需要上面3个参数)
注册方法的定义:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback
{
}
第四个参数为依赖属性元数据,具体见下文讲解
原理说明:
生成的代码片段
添加到中
2.3 添加属性包装器
创建属性包装器时应当只包含对和方法的调用,不应当添加任何验证属性值、引发事件等额外的代码,因为WPF中的其他功能可能会忽略属性封装器,直接调用和方法
public class People : DependencyObject
{
public string Name
{
get { return (stringGetValue(NameProperty; }
set { SetValue(NameProperty, value; }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string, typeof(People;
}
2.4 依赖属性元数据
类存储属性系统使用的大多数元数据
定义如下:
public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback
{
}
参数说明:
更改默认值,这是一个常见方案。
验证回调:更改或添加属性,更改回调
强制回调:可以用来修正属性值
示例说明:
设置了依赖属性的默认值为“元数据”
public class People : DependencyObject
{
public string Name
{
get { return (stringGetValue(NameProperty; }
set { SetValue(NameProperty, value; }
}
public static PropertyMetadata metadata = new PropertyMetadata("元数据", propertyChangedCallback, coerceValueCallback;
private static void propertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e
{
}
private static object coerceValueCallback(DependencyObject d, object baseValue
{
return null;
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string, typeof(People, metadata;
}
3. 依赖属性的继承
属性值继承是依赖属性值从父元素传播到包含属性的元素树中的子元素的机制
3.1 定义一个可继承的依赖属性
public class UCStackPanel : StackPanel
{
public DateTime NowDate
{
get { return (DateTimeGetValue(NowDateProperty; }
set { SetValue(NowDateProperty, value; }
}
public static readonly DependencyProperty NowDateProperty =
DependencyProperty.Register("NowDate", typeof(DateTime, typeof(UCStackPanel, new FrameworkPropertyMetadata(DateTime.MinValue,FrameworkPropertyMetadataOptions.Inherits;
}
public class UCButton : Button
{
public DateTime NowDate
{
get { return (DateTimeGetValue(NowDateProperty; }
set { SetValue(NowDateProperty, value; }
}
public static readonly DependencyProperty NowDateProperty =
UCStackPanel.NowDateProperty.AddOwner(typeof(UCButton, new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits;
}
<GroupBox Header="依赖属性继承" FontSize="25" Margin="20 20">
<local:UCStackPanel NowDate="{x:Static sys:DateTime.Now}">
<local:UCButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=NowDate}"/>
</local:UCStackPanel>
</GroupBox>
4. 只读依赖属性
注册只读属性时,调用而不是 。
实现 CLR 属性包装时,请确保它没有公共 访问器。
返回 而不是 。 将存储在非公共类成员中。
public class UCLabel : Label
{
public UCLabel(:base(
{
SetValue(AgePropertyKey, 108;
}
public int Age
{
get { return (intGetValue(AgePropertyKey.DependencyProperty; }
}
public static readonly DependencyPropertyKey AgePropertyKey =
DependencyProperty.RegisterReadOnly("Age", typeof(int, typeof(UCLabel, new PropertyMetadata(88;
}
<GroupBox Header="只读依赖属性" FontSize="25" Margin="20 20">
<local:UCLabel Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Age}"/>
</GroupBox>
5. 附加属性
是附加属性,因为它在 的子元素上设置,而不是本身
依赖属性是 WPF 概念
提供静态 和 访问器方法,使属性系统能够访问附加属性。
public class PassWordExtension
{
public static string GetPassWord(DependencyObject obj
{
return (stringobj.GetValue(PassWordProperty;
}
public static void SetPassWord(DependencyObject obj, string value
{
obj.SetValue(PassWordProperty, value;
}
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string, typeof(PassWordExtension, new PropertyMetadata(string.Empty;
}
6. 集合类型依赖属性
如果属性值是引用类型,应在注册依赖属性的类的构造函数中设置默认值。
依赖属性元数据不应包含默认的引用类型值,因为该值将分配给类的所有实例,从而创建单一实例类。
只读依赖属性:
public class People : DependencyObject
{
private static readonly DependencyPropertyKey InfosPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "Infos",
propertyType: typeof(List<int>,
ownerType: typeof(People,
typeMetadata: new FrameworkPropertyMetadata(
//typeMetadata: new FrameworkPropertyMetadata(new List<int>(
;
public People( => SetValue(InfosPropertyKey, new List<int>(;
public List<int> Infos =>
(List<int>GetValue(InfosPropertyKey.DependencyProperty;
}
People p1 = new People(;
People p2 = new People(;
p1.Infos.Add(1;
p2.Infos.Add(10;
MessageBox.Show($"p1 contains {p1.Infos.Count}\r\n" +
$"p2 contains {p2.Infos.Count}";
p1 contains 1
p2 contains1
读写依赖属性:
public class People : DependencyObject
{
public static readonly DependencyProperty InfosProperty =
DependencyProperty.Register(
name: "Infos",
propertyType: typeof(List<int>,
ownerType: typeof(Aquarium
;
public People( => SetValue(InfosProperty, new List<int>(;
public List<FrameworkElement> Infos
{
get => (List<int>GetValue(InfosProperty;
set => SetValue(InfosProperty, value;
}
}
FreezableCollection 依赖项属性:
若要在依赖对象集合中启用子属性绑定,请使用集合类型,具有任何派生类 的类型约束。
public class Aquarium : DependencyObject
{
// Register a dependency property with the specified property name,
// property type, and owner type.
private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "AquariumContents",
propertyType: typeof(FreezableCollection<FrameworkElement>,
ownerType: typeof(Aquarium,
typeMetadata: new FrameworkPropertyMetadata(
;
// Store the dependency property identifier as a static member of the class.
public static readonly DependencyProperty AquariumContentsProperty =
s_aquariumContentsPropertyKey.DependencyProperty;
// Set the default collection value in a class constructor.
public Aquarium( => SetValue(s_aquariumContentsPropertyKey, new FreezableCollection<FrameworkElement>(;
// Declare a public get accessor.
public FreezableCollection<FrameworkElement> AquariumContents =>
(FreezableCollection<FrameworkElement>GetValue(AquariumContentsProperty;
}
7. 属性回调(监控依赖属性)
对依赖属性的改变进行监听
使用或方法时,传入一个带回调函数()的元数据
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string, typeof(PassWordExtension, new PropertyMetadata(string.Empty, propertyChangedCallback;
private static void propertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e
{
PasswordBox? passwordBox = d as PasswordBox;
if (passwordBox != null
{
passwordBox.Password = e.NewValue?.ToString(;
}
}
<PasswordBox local:PassWordExtension.PassWord="{Binding PassWord, UpdateSourceTrigger=PropertyChanged}" PasswordChar="*" FontSize="25"/>
参数说明:
表示哪个依赖对象使用了此依赖属性,这里是
中存储了需要的参数,例如:老的值、新的值等
8. 属性验证
WPF .NET (依赖项属性回调和
8.1 验证回调
在注册依赖属性时传入类型的回调
返回一个值,返回时会触发异常
不能访问设置属性的实际对象,意味着不能检查其它属性值(一次只能访问一个属性)
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback
示例说明:
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string, typeof(PassWordExtension, new PropertyMetadata(string.Empty, propertyChangedCallback, validateValueCallback;
private static bool validateValueCallback(object value
{
return true;
}
<PasswordBox local:PassWordExtension.PassWord="{Binding PassWord, UpdateSourceTrigger=PropertyChanged}" PasswordChar="*" FontSize="25"/>
8.2 强制回调
使用或方法时,传入一个带回调函数()的元数据
可以通过回调函数对属性值进行调整就叫强制回调,也叫属性强制
传递两个参数,该数值将要应用到的对象以及准备使用的数值
可以通过强制回调处理相互关联的属性,例如中的、和属性,使属性必须小于属性,属性必须位于两者之间等等
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata
public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback
9. 使用场景
-
依赖属性
: 当您需要单独创建控件时, 并且希望控件的某个部分能够支持数据绑定时, 你则可以使用到依赖属性。
附加属性: 这种情况很多, 正因为WPF当中并不是所有的内容都支持数据绑定, 但是我们希望其支持数据绑定, 这样我们就可以创建基于自己声明的附加属性,添加到元素上, 让其元素的某个原本不支持数据绑定的属性间接形成绑定关系。例如:为PassWord定义附加属性与PassWord进行关联。例如DataGrid控件不支持SelectedItems, 但是我们想要实现选中多个条目进行数据绑定, 这个时候也可以声明附加属性的形式让其支持数据绑定。
10. 使用案例
以密码框为例,的属性不是依赖属性,不支持MVVM绑定,需要自定义依赖属性来间接支持
使用方法一:
自定义一个帮助类
public class PasswordHelper
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached("Password", typeof(string, typeof(PasswordHelper, new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnPropertyChanged;
public static string GetPassword(DependencyObject d
{
return d.GetValue(PasswordProperty.ToString(;
}
public static void SetPassword(DependencyObject d, string value
{
d.SetValue(PasswordProperty, value;
}
public static readonly DependencyProperty AttachProperty =
DependencyProperty.RegisterAttached("Attach", typeof(bool, typeof(PasswordHelper, new FrameworkPropertyMetadata(default(bool, new PropertyChangedCallback(OnAttached;
public static bool GetAttach(DependencyObject d
{
return (boold.GetValue(AttachProperty;
}
public static void SetAttach(DependencyObject d, bool value
{
d.SetValue(AttachProperty, value;
}
static bool _isUpdating = false;
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e
{
PasswordBox password = d as PasswordBox;
password.PasswordChanged -= Password_PasswordChanged;
if (!_isUpdating
password.Password = e.NewValue?.ToString(;
password.PasswordChanged += Password_PasswordChanged;
}
private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e
{
PasswordBox password = d as PasswordBox;
password.PasswordChanged += Password_PasswordChanged;
}
private static void Password_PasswordChanged(object sender, RoutedEventArgs e
{
PasswordBox passwordBox = sender as PasswordBox;
_isUpdating = true;
SetPassword(passwordBox, passwordBox.Password;
_isUpdating = false;
}
}
<PasswordBox local:PasswordHelper.Password="{Binding PassWord, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
local:PasswordHelper.Attach="True" PasswordChar="*" FontSize="25"/>
使用方法二:
使用行为来进行支持
/// <summary>
/// 增加Password扩展属性
/// </summary>
public static class PasswordBoxHelper
{
public static string GetPassword(DependencyObject obj
{
return (stringobj.GetValue(PasswordProperty;
}
public static void SetPassword(DependencyObject obj, string value
{
obj.SetValue(PasswordProperty, value;
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached("Password", typeof(string, typeof(PasswordBoxHelper, new PropertyMetadata("", OnPasswordPropertyChanged;
private static void OnPasswordPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e
{
PasswordBox box = sender as PasswordBox;
string password = (stringe.NewValue;
if (box != null && box.Password != password
{
box.Password = password;
}
}
}
/// <summary>
/// 接收PasswordBox的密码修改事件
/// </summary>
public class PasswordBoxBehavior : Behavior<PasswordBox>
{
protected override void OnAttached(
{
base.OnAttached(;
AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
}
private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e
{
PasswordBox passwordBox = sender as PasswordBox;
string password = PasswordBoxHelper.GetPassword(passwordBox;
if (passwordBox != null && passwordBox.Password != password
PasswordBoxHelper.SetPassword(passwordBox, passwordBox.Password;
}
protected override void OnDetaching(
{
base.OnDetaching(;
AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
}
}
<PasswordBox local:PasswordBoxHelper.Password="{Binding PassWord, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
PasswordChar="*" FontSize="25">
<i:Interaction.Behaviors>
<local:PasswordBoxBehavior/>
</i:Interaction.Behaviors>
</PasswordBox>