WPF 附加属性
一、简介
官方解释如下:
附加属性是一个 Extensible Application Markup Language (XAML) 概念。 附加属性允许为派生自 DependencyObject 的任何 XAML 元素设置额外的属性/值对,即使该元素未在其对象模型中定义这些额外的属性。 额外的属性可供全局访问。 附加属性通常定义为没有常规属性包装器的依赖属性的专用形式。
其特点有两点:
- 是一种依赖属性,所以也支持绑定
- 附加属性可以用于任意控件上
二、如何声名
VS中为我们提供了代码片段propa,我们只需要输入propa然后连按两次Tab键就可以出现以下代码
public static int GetMyProperty(DependencyObject obj) { return (int)obj.GetValue(MyPropertyProperty); } public static void SetMyProperty(DependencyObject obj, int value) { obj.SetValue(MyPropertyProperty, value); } public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.RegisterAttached("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));
GetMyProperty():获取附加属性MyProperty的值
SetMyProperty():设置附加属性MyProperty的值
DependencyProperty.RegisterAttached():注册附加属性,参数1 "MyProperty"(附加属性名),参数2 typeof(int)(附加属性数据类型),
参数3 typeof(ownerclass)(所属类),参数4 new PropertyMetadata(0)(附加属性默认值为0,也可在PropertyMetadata中注册属性改变时的回调函数)
三、如何使用
举一个例子,当我们在做一个登录界面时,就会用到一个控件PasswordBox,但是我们使用绑定功能想要把用户输入的密码绑定到Model中的Password属性上时,发现PasswordBox的Password属性不支持绑定,并会报错如图3.1所示,将光标放在该属性上按下F12进去查看到Password为string类型,并非依赖属性,所以不支持绑定。


但是在MVVM模式下,最基本的功能就是数据绑定,我们想让PasswordBox支持绑定的方式有很多,可以修改TextBox模板使其绑定在Text上,也可以使用附加属性,这边就举一个使用附加属性的例子。
3.1 使用步骤
1.首先新建一个类命名为PasswordHelper,并使用代码片段propa生成附加属性,并修改属性名为Password注册当属性改变时的回调函数OnPasswordChanged,如下所示
public class PasswordHelper { 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(PasswordHelper), new PropertyMetadata("", new PropertyChangedCallback(OnPasswordChanged))); private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } }
但是是否只需要声名一个附加属性就可以实现绑定了呢,我们接着往下走
前台的绑定方式 local:PasswordHelper.Password="{Binding Password,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}",这个local为PasswordHelper的命名空间,
这里用控件TextBox来监控PasswordBox的附加属性,按钮Button点击后则会以弹框的形式弹出当前Model中的Password的值并修改Password值为"password",
并在附加属性发生改变的回调函数中打上断点,看输入值改变时是否会执行到该断点
<PasswordBox Margin="10 0 0 0" x:Name="pwd" local:PasswordHelper.Password="{Binding Password,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Width="100"/> <TextBox Text="{Binding ElementName=pwd,Path=(local:PasswordHelper.Password),Mode=OneWay}" Width="100"/> <Button Command="{Binding TestCommand}" Content="测试"/>
ViewModel代码 这里使用 CommunityToolkit.Mvvm 中的代码生成器功能
public partial class DP_AP_ViewMode : ObservableObject { [ObservableProperty] private string _password; [RelayCommand] public void Test() { MessageBox.Show(Password); Password= "password"; } }
启动项目,并且在PasswordBox中输入123,发现TextBox中并没有任何反应,并且也没有进入到属性改变的回调函数中,点击按钮,弹出的窗口中输出的内容为空,当执行到Password="password"时,进入到了回调函数中,可以看到newValue值为password,F5继续,TextBox中出现了
password的字样

Password内容为空
执行到Password= "password";时,进入回调
TextBox中出现password
3.2 发现问题并分析原因
现象
此时发现,Password是绑定成功了,前后台修改数据,对于PasswordBox控件都无任何反应
原因
因为PasswordBox的文本输入框对应的是PasswordBox的Password属性,所以无论我们在上面输入什么内容,都无法将输入的内容赋值给我们自己新建的附加属性
3.3 解决问题
在PasswordBox中有一个事件PasswordChanged,当输入内容发生改变时,该事件就会被通知,我们只需要去订阅它,并将当前的Password的值设置给我们的附加属性即可,按照这个思路来实现
1)新建一个附加属性EnableBinding
使用EnableBinding来判断是否可以实现绑定功能,当设置该属性为true时,就订阅PasswordBox的PasswordChanged事件,反正则取消订阅
当password发生改变时,就会执行Box_PasswordChanged(),将当前输入的值通过方法SetPassword()设置给我们的附加属性
public static readonly DependencyProperty EnableBindingProperty = DependencyProperty.RegisterAttached("EnableBinding", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnEnableBindingChanged))); private static void OnEnableBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not PasswordBox box) throw new NotSupportedException(); if ((bool)e.NewValue) { box.PasswordChanged += Box_PasswordChanged; } else { box.PasswordChanged -= Box_PasswordChanged; } } private static void Box_PasswordChanged(object sender, RoutedEventArgs e) { var pass = sender as PasswordBox; SetPassword(pass!, pass!.Password); }
<PasswordBox Margin="10 0 0 0" x:Name="pwd" local:PasswordHelper.EnableBinding="True" local:PasswordHelper.Password="{Binding Password,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Width="100"/>
将EnableBinding设置为True,就可以了
2)缺陷
这样的实现,也只能实现前台数据改变,我们定义的附加属性也在同时改变,后台绑定的Model中的Password也在改变,而Password改变却无法通知到界面上,也就是说我们只实现了从界面到ViewModel的单向通知,后台改变而界面汉武变化,不过这个暂时不影响使用,因为在登录界面,一般我们只会获取密码值,而不会去改变密码值,当然若是需要实现清空功能,就要另当别论了
四、完整的代码
解决了后台改变前台无变化的问题
PasswordHelper.cs
public class PasswordHelper { 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(PasswordHelper), new PropertyMetadata("", new PropertyChangedCallback(OnPasswardChanged))); private static void OnPasswardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not PasswordBox box) throw new NotSupportedException(); if (e.NewValue is null) return; if (!_isupdate) box.Password = e.NewValue.ToString(); } public static bool GetEnableBinding(DependencyObject obj) { return (bool)obj.GetValue(EnableBindingProperty); } public static void SetEnableBinding(DependencyObject obj, bool value) { obj.SetValue(EnableBindingProperty, value); } public static readonly DependencyProperty EnableBindingProperty = DependencyProperty.RegisterAttached("EnableBinding", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnEnableBindingChanged))); private static void OnEnableBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not PasswordBox box) throw new NotSupportedException(); if ((bool)e.NewValue) { box.PasswordChanged += Box_PasswordChanged; } else { box.PasswordChanged -= Box_PasswordChanged; } } static bool _isupdate = false; private static void Box_PasswordChanged(object sender, RoutedEventArgs e) { var pass = sender as PasswordBox; _isupdate = true; SetPassword(pass!, pass!.Password); _isupdate = false; } }
xmal
<PasswordBox Margin="10 0 0 0" x:Name="pwd" local:PasswordHelper.EnableBinding="True" local:PasswordHelper.Password="{Binding Password,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" Width="100"/>
五、小结
1. 使用情形:当我们需要一些控件没有的属性时,就可以使用附加属性
2.使用代码片段propa+两次Tab键可快速注册一个附加属性
3.修改附加属性的名字,数据类型,所属类,默认值以及改变时的回调
4.附加到其他控件上,进行使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律