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 而不是DependencyProperty 。 DependencyPropertyKey将存储在非公共类成员中。
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. 属性回调(监控依赖属性)

  • 对依赖属性的改变进行监听
  • 使用RegisterAttached或Register方法时,传入一个带回调函数(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 强制回调

  • 使用RegisterAttached或Register方法时,传入一个带回调函数(coerceValueCallback)的元数据
  • 可以通过回调函数coerceValueCallback对属性值进行调整就叫强制回调,也叫属性强制
  • coerceValueCallback传递两个参数,该数值将要应用到的对象以及准备使用的数值
  • 可以通过强制回调coerceValueCallback处理相互关联的属性,例如ScrollBar中的Maximun、Minimum和Value属性,使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为例,PasswordBox的Password属性不是依赖属性,不支持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. Linux用户无法使用sudo命令

    新建的用无法使用sudo命令,出现这样的提示: xiaojing is not in the sudoers file. This incident will be reported 原来是新建的用户 ...

  2. Linux基础学习 | gcc、g++的安装和使用

    安装gcc 1.apt-get命令是debain Linux发新版的APT软件包管理工具. dabian.ubuntu.deepin等Linux系统通过以下命令: 安装gcc:Shell输入sudo ...

  3. 用JS写一个计算器(兼容手机端)

    先看成果:1.PC端2. 首先确立html,有哪些东西我们要知道.布局大概的样子在心里有个数 <!DOCTYPE html> <html> <head> <m ...

  4. 微信小程序实时通讯(websocket)问题

    这几天值班忙的不要不要,人工智能这块看的都是零零散散,今天就来写写小程序的实时通讯吧.小程序端://这个是连接 lianjie:function(){ var socketOpen = false / ...

  5. POP3:基于命令行的电子邮件(EMail)在线查看和批量下载工具

    使用该工具可以在不安装outlook和foxmail等邮件客户端的情况下快速下载指定邮箱的邮件,并将下载的邮件以eml格式进行保存. 附: 查看eml格式的邮件可使用 EmlReader 工具,该工具 ...

  6. python-正整数的因子展开式

    [题目描述]编写程序,输出一个给定正整数x(x>1)的质因子展开式. [输入格式]请在一行中输入整数x的值. [输出格式]对每一组输入的x,按以下格式输出x的质因子展开式(假如x的质因子分别为a ...

  7. 手机上无法显示Toast信息

    关于手机上无法显示Toast信息, 是因为手机上的权限没有开, 在应用管理处将所有权限都打开,就可以显示了.

  8. js原生的Ajax

    js原生的Ajax其实就是围绕浏览器内内置的Ajax引擎对象进行学习的,要使用js原 生的Ajax完成异步操作,有如下几个步骤: 1)创建Ajax引擎对象 2)为Ajax引擎对象绑定监听(监听服务器已 ...

  9. 小程序checkbox调整大小(checkbox样式修改)

    .skyCheckbox{ transform: scale(0.7,0.7); -webkit-transform: scale(0.7,0.7); } <label class=" ...

  10. MFC软件国际化的几个问题及其解决方案

    作者:马健 邮箱:stronghorse_mj@hotmail.com主页:https://www.cnblogs.com/stronghorse/ 以前我以为PDG相关软件只会在国内流行,所以发行简 ...