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类型,并非依赖属性,所以不支持绑定。

图3.1

 

图3.2

   但是在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的字样

输入123,TextBox无反应

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.附加到其他控件上,进行使用

 

 

 

 

 









posted @ 2023-05-21 21:35  just--like  阅读(967)  评论(0编辑  收藏  举报