1. WPF中的依赖属性

  • 依赖属性是专门基于WPF创建的。在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使用方法与普通的属性是相同的。

1.1 依赖属性提供的属性功能

1.2 依赖属性优先级列表

运行时值分配给依赖项属性时,属性系统使用的明确优先级顺序,由高到低为:

  1. 属性系统强制
  2. 活动动画或具有保留行为的动画
  3. 本地值
  4. TemplatedParent 模板属性值
  5. 隐式样式
  6. 样式触发器
  7. 模板触发器
  8. 样式 setter 值
  9. 默认样式,也称为 主题样式
  10. 继承。 子元素的某些依赖属性从父元素继承其值。 因此,可能不需要在整个应用程序中设置每个元素的属性值。
  11. 依赖项属性元数据中的默认值 依赖属性可以在该属性的属性系统注册过程中设置默认值。 继承依赖属性的派生类可以重写依赖属性元数据 (包括基于每个类型) 的默认值。 对于继承的属性,父元素的默认值优先于子元素的默认值。 因此,如果未设置可继承属性,则将使用根或父元素的默认值,而不是子元素的默认值。

1.3 附加属性

附加属性允许子元素为父元素中定义的属性指定唯一值。 常见方案是一个子元素,它指定其父元素在 UI 中的呈现方式。 例如, DockPanel.Dock是附加属性,因为它在 的子元素上 DockPanel设置,而不是本身 DockPanel

2. 依赖属性的使用

2.1 定义依赖属性

  • 定义一个名叫Name的依赖属性,根据命名约定,依赖属性以属性名称加Property来命名
  • 依赖属性的所有者必须继承自DependencyObject
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty;
}

2.2 注册依赖属性

  • 使用DependencyProperty.Register()静态方法对依赖属性进行注册
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(People));
}

参数说明:

  • 第一个参数表示要注册的依赖属性的名称
  • 第二个参数表示要注册的依赖属性的类型
  • 第三个参数表示要注册的依赖属性的所有者
  • ......(DependencyProperty.Register()提供了多种重载方式,其他参数参考文档即可,最少需要上面3个参数)

注册方法的定义:

public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{ }
  • 第四个参数为依赖属性元数据,具体见下文讲解

  • 第五个参数为一个回调函数

原理说明:

上面定义了一个string类型的依赖属性Name, 在WPF的源代码中, 其实是生成了一个key/value存储在Hashtable里面。

  • 生成key的代码片段

  • 添加到Hashtable

2.3 添加属性包装器

  • 创建属性包装器时应当只包含对GetValue()SetValue()方法的调用,不应当添加任何验证属性值、引发事件等额外的代码,因为WPF中的其他功能可能会忽略属性封装器,直接调用GetValue()SetValue()方法
public class People : DependencyObject
{ public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
} public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(People));
}

2.4 依赖属性元数据

  • PropertyMetadata类存储属性系统使用的大多数元数据

  • 在实现新的依赖项属性时,可以通过使用方法的 Register重载来设置其元数据。

定义如下:

public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)
{
}

参数说明:

  • 更改默认值,这是一个常见方案。
  • 验证回调:更改或添加属性,更改回调
  • 强制回调:可以用来修正属性值

示例说明:

  • 设置了Name依赖属性的默认值为“元数据”
public class People : DependencyObject
{ public string Name
{
get { return (string)GetValue(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. 依赖属性的继承

  • 属性值继承是依赖属性值从父元素传播到包含属性的元素树中的子元素的机制

  • AllowDrop在基类上 UIElement 实现,因此,派生自 UIElement 的每个控件上也存在该依赖项属性。 WPF 启用依赖项属性的值继承,使用户可以轻松地在父元素上设置属性值一次,并使该属性值传播到元素树中的子代元素。

3.1 定义一个可继承的依赖属性

public class UCStackPanel : StackPanel
{ public DateTime NowDate
{
get { return (DateTime)GetValue(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 (DateTime)GetValue(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. 只读依赖属性

创建只读依赖项属性的过程与创建读/写依赖项属性的方式很类似,其中包括:

  • 注册只读属性时,调用RegisterReadOnly而不是 Register
  • 实现 CLR 属性包装时,请确保它没有公共 set 访问器。
  • RegisterReadOnly 返回 DependencyPropertyKey 而不是DependencyPropertyDependencyPropertyKey将存储在非公共类成员中。
public class UCLabel : Label
{
public UCLabel():base()
{
SetValue(AgePropertyKey, 108);
} public int Age
{
get { return (int)GetValue(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. 附加属性

  • 附加属性允许子元素为父元素中定义的属性指定唯一值,常见方案是一个子元素,它指定其父元素在 UI 中的呈现方式

  • DockPanel.Dock 是附加属性,因为它在 的子元素上DockPanel设置,而不是本身 DockPanel

  • 附加属性是 XAML 概念

  • 依赖属性是 WPF 概念

  • 遵循 WPF 属性命名约定,通过命名标识符字段来区分字段和它们表示的属性 <property name>Property

  • 提供静态 Get<property name>Set<property name> 访问器方法,使属性系统能够访问附加属性。

public class PassWordExtension
{
public static string GetPassWord(DependencyObject obj)
{
return (string)obj.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 依赖项属性:

  • 集合类型依赖属性不会自动报告其子属性中的更改。 因此,如果要绑定到集合,则绑定可能不会报告更改,使某些数据绑定方案失效。 但是,如果将 用于 FreezableCollection 依赖属性类型,则正确报告对集合元素属性的更改,并且绑定将正常工作。

  • 若要在依赖对象集合中启用子属性绑定 FreezableCollection,请使用集合类型 ,具有任何派生类 DependencyObject 的类型约束。

下面的示例声明一个类 Aquarium ,该类包含 FreezableCollection 类型约束为 的 FrameworkElement。 传递给RegisterReadOnly(String, Type, Type, PropertyMetadata)方法的PropertyMetadata中不包含默认集合值,而是使用 类构造函数将默认集合值设置为新的 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. 属性回调(监控依赖属性)

  • 对依赖属性的改变进行监听
  • 使用RegisterAttachedRegister方法时,传入一个带回调函数(propertyChangedCallback)的元数据
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"/>

参数说明:

  • DependencyObject d表示哪个依赖对象使用了此依赖属性,这里是PasswordBox
  • DependencyPropertyChangedEventArgs e中存储了需要的参数,例如:老的值、新的值等

8. 属性验证

WPF .NET (依赖项属性回调和)

8.1 验证回调

  • 在注册依赖属性时传入ValidateValueCallback类型的回调
  • ValidateValueCallback返回一个bool值,返回false时会触发异常
  • ValidateValueCallback不能访问设置属性的实际对象,意味着不能检查其它属性值(一次只能访问一个属性)
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 强制回调

  • 使用RegisterAttachedRegister方法时,传入一个带回调函数(coerceValueCallback)的元数据
  • 可以通过回调函数coerceValueCallback对属性值进行调整就叫强制回调,也叫属性强制
  • coerceValueCallback传递两个参数,该数值将要应用到的对象以及准备使用的数值
  • 可以通过强制回调coerceValueCallback处理相互关联的属性,例如ScrollBar中的MaximunMinimumValue属性,使Minimum属性必须小于Maximun属性,Value属性必须位于两者之间等等
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. 使用案例

  • 以密码框PasswordBox为例,PasswordBoxPassword属性不是依赖属性,不支持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 (bool)d.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 (string)obj.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 = (string)e.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>

11. 参考资料:

WPF中的依赖属性的更多相关文章

  1. (原创)2. WPF中的依赖属性之二

    1 依赖属性 1.1 依赖属性最终值的选用 WPF属性系统对依赖属性操作的基本步骤如下: 第一,确定Base Value,对同一个属性的赋值可能发生在很多地方.还用Button的宽度来进行举例,可能在 ...

  2. 在WPF中使用依赖注入的方式创建视图

    在WPF中使用依赖注入的方式创建视图 0x00 问题的产生 互联网时代桌面开发真是越来越少了,很多应用都转到了浏览器端和移动智能终端,相应的软件开发上的新技术应用到桌面开发的文章也很少.我之前主要做W ...

  3. WPF学习笔记——依赖属性(Dependency Property)

    1.什么是依赖属性 依赖属性是一种可以自己没有值,并且通过Binding从数据源获得值(依赖在别人身上)的属性,拥有依赖属性的对象被称为"依赖对象". 依赖项属性通过调用 Regi ...

  4. [No000012D]WPF(5/7)依赖属性

    介绍 WPF带来了很多传统 Windows 应用程序没有的新特性和选择.我们已经讨论了一些 WPF 的特性,是时候更进一步介绍其他特性了.当你读完这个系列之前的文章,我希望你已经或多或少地了解了 WP ...

  5. WPF 精修篇 依赖属性

    原文:WPF 精修篇 依赖属性 依赖属性使用场景 1. 希望可在样式中设置属性. 2. 希望属性支持数据绑定. 3. 希望可使用动态资源引用设置属性. 4. 希望从元素树中的父元素自动继承属性值. 5 ...

  6. ReferentialConstraint 中的依赖属性映射到由存储生成的列

    ReferentialConstraint 中的依赖属性映射到由存储生成的列 这个问题是由于从表中的外键关系建立错误(可能是由于误改),查看从表的所有外键关系,即可找到问题所在. 问题: 什么是从表? ...

  7. Entity Framework问题:ReferentialConstraint 中的依赖属性映射由存储生成的列

    原文:Entity Framework问题:ReferentialConstraint 中的依赖属性映射由存储生成的列 今天在采用Entity Framework 的Database First反向以 ...

  8. dotnetcore3.1 WPF 中使用依赖注入

    dotnetcore3.1 WPF 中使用依赖注入 Intro 在 ASP.NET Core 中默认就已经集成了依赖注入,最近把 DbTool 迁移到了 WPF dotnetcore 3.1, 在 W ...

  9. WPF中的依赖项属性

    Form cnblogs 桂素伟 随着WPF的推广,不得不重新拾起WPF来,因为这块的产品越来越多. 只能跟着MSDN来学了,所以想是在这里记录下学习的过程和对知识的理解. 先从最基本的吧,依赖项属性 ...

随机推荐

  1. Spring 的 jdbcTemplate 操作

    1.Spring框架是一站式框架 (1)针对 JavaEE 三层,每一层都有解决技术 (2)在 dao 层,使用 jdbcTemplate 2.Spring对不同的持久化层的技术都进行了封装 (1)j ...

  2. 学习Apache(四)

    介绍 Apache HTTP 服务器被设计为一个功能强大,并且灵活的 web 服务器, 可以在很多平台与环境中工作.不同平台和不同的环境往往需要不同 的特性,或可能以不同的方式实现相同的特性最有效率. ...

  3. leedcode_13 罗马数字转整数

    罗马数字包含以下七种字符: I, V, X, L,C,D 和 M. 字符 数值I 1V 5X 10L 50C 100D 500M 1000例如, 罗马数字 2 写做 II ,即为两个并列的 1 .12 ...

  4. Issues with position fixed & scroll(移动端 fixed 和 scroll 问题)

    转载请注明英文原文及译文出处 原文地址:Issues with position fixed & scrolling on iOS 原文作者:Remy Sharp译文地址:移动端 fixed ...

  5. 从零到有模拟实现一个Set类

    前言 es6新增了Set数据结构,它允许你存储任何类型的唯一值,无论是原始值还是对象引用.这篇文章希望通过模拟实现一个Set来增加对它的理解. 原文链接 用在前面 实际工作和学习过程中,你可能也经常用 ...

  6. javaweb之连接数据库

    最近做完了一个图书系统的增删改查,想着来总结一下这几个月的所学内容. 一.首先你需要在电脑上安装上mysql或者sql server(本文以mysql为例) mysql官网:MySQL :: Begi ...

  7. python修改Gsettings的配置文件

    GSettings 的配置文件是 xml 格式的,文件需以 .gschema.xml 结尾,文件名通常与 id 相同.配置文件安装在 /usr/share/glib-2.0/schemas/ 目录下, ...

  8. 搭建Vue小页面

    学习链接:https://blog.csdn.net/zhenzuo_x/article/details/81013475 环境搭建: 浏览器:Chrome IDE:VS Code或者WebStorm ...

  9. python---使用pipreqs及遇到的问题

    pipreqs简介 ​ 项目开发的过程中, 避免不了搭建和部署开发环境, 而搭建和部署开发环境需要项目依赖的python第三方包, 如何获取一个项目中所需依赖的python第三方包, 这就需要使用pi ...

  10. JavaSE常用类之File类

    File类 只用于表示文件或目录的信息,不能对文件内容进行访问. java.io.File类∶代表文件和目录.在开发中,读取文件.生成文件.删除文件.修改文件的属性时经常会用到本类. File类不能访 ...