wpf之依赖属性
什么是依赖附加属性
依赖属性就是一种自己可以没有值,并且可以通过绑定从其他数据源获取值。依赖属性可支持WPF中的样式设置、数据绑定、继承、动画及默认值。
如果要定义依赖属性,必须满足下面三个条件:
1 所属对象必须是依赖对象(依赖对象就是说必须要继承自DependencyObject,在wpf中大部分)
2 必须是静态+readonly
3 不是通过new而是通过注册来完成定义的
下面展示一个简单的依赖属性定义:
public partial class MyDependency : UserControl { public MyDependency() { InitializeComponent(); } public int MyProperty { get { return (int)GetValue(MyPropertyProperty); } set { SetValue(MyPropertyProperty, value); } } /// <summary> /// 定义一个依赖属性,不是通过new生成,而是通过n注册生成的, /// 注意第一个参数之所以是MyProperty是因为依赖属性默认后面是Property来结尾,是默认的一个约束, /// /// </summary> public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.Register("MyProperty", typeof(int), typeof(MyDependency), new PropertyMetadata(0)); }
至于依赖附加属性,看下面一个这个例子:
<UserControl x:Class="MyWpf.MyGrid" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MyWpf" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Grid.RowDefinitions> <!--指定宽高,剩余的会自动占满--> <RowDefinition Height="90"></RowDefinition> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <!--可以指定行列--> <Border Background="Red" Grid.Row="0"></Border> <Border Background="Blue" Grid.Row="1"></Border> <Border Background="SandyBrown" Grid.Column="1" Grid.Row="0"></Border> <Border Background="RosyBrown" Grid.Column="1" Grid.Row="1"></Border> </Grid> </UserControl>
<Border>标签这是没有Row这个属性的,但是通过Grid中的这个属性附加到了Border上,使得Border标签也有了Row属性。使用附加属性,属性定义在与其使用的类不同的类上。这通常用于布局。
依赖附加属性关心的不是拥有者是不是依赖对象,关心的是被附加的对象。
定义一个依赖附加属性:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace MyWpf { /// <summary> /// MyDependency.xaml 的交互逻辑 /// </summary> public partial class MyDependency : UserControl { public MyDependency() { InitializeComponent(); } /// <summary> /// 定义一个依赖属性,不是通过new生成,而是通过n注册生成的, /// 注意第一个参数之所以是MyProperty是因为依赖属性默认后面是Property, /// /// </summary> public static readonly DependencyProperty MyTestProperty = DependencyProperty.RegisterAttached("MyTest", typeof(int), typeof(MyDependency), new PropertyMetadata(0)); /// <summary> /// 注意必须是Get+附加属性的名称,默认的后缀Property不用写在这里 /// </summary> /// <param name="dependency"></param> /// <returns></returns> public string GetMyTest(DependencyObject dependency) { return (string)dependency.GetValue(MyTestProperty); } public void SetMyTest(DependencyObject dependency,string value) { dependency.SetValue(MyTestProperty,value); } } }
依赖属性和依赖附加属性的区别
1 承载对象的区别
一个是需要拥有者必须是依赖对象,依赖附加属性则要求被附加对象是依赖对象
2 定义方法的区别
一个是Regist,一个是RegisterAttached
3 包装(封装)
依赖属性使用属性包装器,但是依赖附加属性使用的是方法包装
依赖附加属性使用场景
1 绑定中转
我们以登录界面为例,其中PasswordBox控件,Password属性不是依赖对象,所以没法进行Binding绑定,所以需要创建依赖附加属性来解决。
依赖附加属性关心的是被附加的对象是不是依赖对象,下面的PasswordBox是继承自依赖对象的,所以可以添加依赖附加属性。
界面:
<UserControl x:Class="MyWpf.DependencyAttachedProperty" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MyWpf" xmlns:c="clr-namespace:MyWpf.CommonService" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="60"></RowDefinition> <RowDefinition Height="60"></RowDefinition> </Grid.RowDefinitions> <Button Content="提交" Grid.Row="1" Command="{Binding GetPasswordCommand}"></Button> <TextBox Text="{Binding UserName}" ></TextBox> <PasswordBox Password="" Grid.Row="0" c:PaswwordHelper.Password="{Binding Password}"></PasswordBox> </Grid> </UserControl>
后台:
namespace MyWpf { /// <summary> /// DependencyAttachedProperty.xaml 的交互逻辑 /// </summary> public partial class DependencyAttachedProperty : UserControl { public DependencyAttachedProperty() { InitializeComponent(); this.DataContext = new Person(); } } public class Person { public string UserName { get; set; } public string Password { get; set; } public Person() { UserName = "哈哈"; Password = "123"; } private CommandBase _getPasswordCommand; public CommandBase GetPasswordCommand { get { if (_getPasswordCommand == null) { _getPasswordCommand = new CommandBase(); _getPasswordCommand.DoExecute = new Action<object>( (o) => { MessageBox.Show(this.Password); } ); } return _getPasswordCommand; } set { _getPasswordCommand = value; } } } public class CommandBase : ICommand { public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { DoExecute?.Invoke(parameter); } public Action<object> DoExecute { get; set; } } }
帮助类:
namespace MyWpf.CommonService { public class PaswwordHelper { /// <summary> /// 定义一个依赖属性,不是通过new生成,而是通过n注册生成的, /// 注意第三个参数是typeof(PaswwordHelper),这个依赖附加属性是属于这个类的 /// </summary> public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PaswwordHelper),
new PropertyMetadata(new PropertyChangedCallback(OnBindedPasswordChanged))); /// <summary> /// 注意必须是Get+附加属性的名称,默认的后缀Property不用写在这里 /// </summary> /// <param name="dependency"></param> /// <returns></returns> public static string GetPassword(DependencyObject dependency) { return (string)dependency.GetValue(PasswordProperty); } public static void SetPassword(DependencyObject dependency, string value) { dependency.SetValue(PasswordProperty, value); } /// <summary> /// 回调函数 /// </summary> /// <param name="obj"></param> /// <param name="e"></param> private static void OnBindedPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var passwordBox = obj as System.Windows.Controls.PasswordBox; if (passwordBox != null) { //PasswordBox的Password属性赋值 passwordBox.Password = e.NewValue == null ? string.Empty : e.NewValue.ToString(); } } } }
结果:
可以看到,我们明显在界面上已经做过密码更改了,但是Password还是之前的旧密码,所以我们可以再建一个附加属性来联动更改:
namespace MyWpf.CommonService { public class PaswwordHelper { /// <summary> /// 定义一个依赖属性,不是通过new生成,而是通过n注册生成的, /// 注意第三个参数是typeof(PaswwordHelper),这个依赖附加属性是属于这个类的 /// </summary> public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PaswwordHelper),
new PropertyMetadata(new PropertyChangedCallback(OnBindedPasswordChanged))); /// <summary> /// 注意必须是Get+附加属性的名称,默认的后缀Property不用写在这里 /// </summary> /// <param name="dependency"></param> /// <returns></returns> public static string GetPassword(DependencyObject dependency) { return (string)dependency.GetValue(PasswordProperty); } public static void SetPassword(DependencyObject dependency, string value) { dependency.SetValue(PasswordProperty, value); } #region public static readonly DependencyProperty AttachedProperty = DependencyProperty.RegisterAttached("Attached", typeof(string), typeof(PaswwordHelper),
new PropertyMetadata(new PropertyChangedCallback(OnAttachedChanged))); /// <summary> /// 注意必须是Get+附加属性的名称,默认的后缀Property不用写在这里 /// </summary> /// <param name="dependency"></param> /// <returns></returns> public static string GetAttached(DependencyObject dependency) { return (string)dependency.GetValue(AttachedProperty); } public static void SetAttached(DependencyObject dependency, string value) { dependency.SetValue(AttachedProperty, value); } #endregion /// <summary> /// 依赖附加属性值更改的时候的回调函数 /// </summary> /// <param name="obj"></param> /// <param name="e"></param> private static void OnBindedPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var passwordBox = obj as System.Windows.Controls.PasswordBox; passwordBox.PasswordChanged -= Pb_PasswordChanged; if (!isUpdating ) { passwordBox.Password = e.NewValue == null ? string.Empty : e.NewValue.ToString(); } passwordBox.PasswordChanged += Pb_PasswordChanged; } private static void OnAttachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var passwordBox = obj as PasswordBox; if (passwordBox != null) { passwordBox.PasswordChanged += Pb_PasswordChanged; } } static bool isUpdating = false; private static void Pb_PasswordChanged(object sender, RoutedEventArgs e) { PasswordBox pb = sender as PasswordBox; isUpdating = true; SetPassword(pb, pb.Password); isUpdating = false; } } }
界面:
<UserControl x:Class="MyWpf.DependencyAttachedProperty" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:MyWpf" xmlns:c="clr-namespace:MyWpf.CommonService" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="60"></RowDefinition> <RowDefinition Height="60"></RowDefinition> </Grid.RowDefinitions> <Button Content="提交" Grid.Row="1" Command="{Binding GetPasswordCommand}"></Button> <TextBox Text="{Binding UserName}" ></TextBox> <!-- c:PaswwordHelper.Attached="true" 没什么特别的意思,主要是给个值能够触发这个依赖属性就行, 主要是为了绑定上密码更改的事件PasswordChanged UpdateSourceTrigger=PropertyChanged 意味着当目标控件值发生变化时,源数据立马更新,不是失去焦点之后才更新--> <PasswordBox Password="" Grid.Row="0" c:PaswwordHelper.Password="{Binding Password,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" c:PaswwordHelper.Attached="true" ></PasswordBox> </Grid> </UserControl>
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术