WPF中文网

PasswordBox与附加属性实战

WPF的PasswordBox的Password属性不是依赖属性,所以不能绑定。为了实现这个控件的MVVM模式,我们可以利用附加属性实现PasswordBox控件的绑定使用。其主要的思想是,设计一个PasswordBoxHelper类型,并在其中定义一个附加属性,这个属性的名称也叫Password,将来作为PasswordBox控件的附加属性,假设我们有一个Person实体,这个实体有一个UserName属性和一个Password属性,分别表示用户登录时填写的账户和密码。通过PasswordBoxHelper,可以将PasswordBox控件与Person实体建立数据绑定的桥梁,换句话说,PasswordBox的Password属性、PasswordBoxHelper中的Password属性和Person实体中的Password属性三者建立了某种连接。形象表达如下:

PasswordBox.Password -> PasswordBoxHelper.Password -> Person.Password

为此,我们先做一些准备工作。

第一步,创建一个ObservableObject类型,用来实现属性通知。

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

第二步,创建一个Person实体。

public class Person : ObservableObject
{
    private string username;
    public string UserName
    {
        get { return username; }
        set { username = value;RaisePropertyChanged(); }
    }

    private string password;
    public string Password
    {
        get { return password; }
        set { password = value; RaisePropertyChanged(); }
    }
}

第三步,创建一个MainViewModel,并在其中实例化Person。

public class MainViewModel : ObservableObject
{
    private Person person = new Person();
    public Person Person
    {
        get { return person; }
        set { person = value;RaisePropertyChanged(); }
    }
}

第四步,将MainViewModel赋值给主窗体。

<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>

第五步,创建PasswordBoxHelper类,实现附加属性的业务逻辑。

public 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("",
                new PropertyChangedCallback(OnPasswordPropertyChangedCallback)));

    private static void OnPasswordPropertyChangedCallback(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if(d is PasswordBox passwordBox)
        {
            passwordBox.PasswordChanged -= PasswordBox_PasswordChanged;
            passwordBox.PasswordChanged += PasswordBox_PasswordChanged;
        }
    }

    private static void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
    {
        if(sender is PasswordBox passwordBox)
        {
            SetPassword(passwordBox, passwordBox.Password);
        }
    }
}

分析PasswordBoxHelper的业务实现

这时我们定义了一个PasswordProperty 附加属性,当PasswordProperty 的值发生改变时,会调用OnPasswordPropertyChangedCallback回调函数,在这个回调函数中,我们会拿到PasswordBox 控件,并订阅它的PasswordChanged ,在PasswordChanged 事件中调用SetPassword(),实际上这里就是将PasswordBox 控件的值赋值到PasswordProperty ,而PasswordProperty 又在前端绑定了Person.Password,于是,PasswordBox的值就给到了Person.Password。

第六步,编写XAML前端代码,这里我们实例化了一个TextBox控件,将其绑定到Person的UserName属性上,实例化了一个PasswordBox控件,在其中引用了PasswordBoxHelper.Password附加属性,注意,引用之前要将其命名空间写好:xmlns:helper="clr-namespace:HelloWorld.MVVM"

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM"
        mc:Ignorable="d" 
        Title="WPF中文网 - www.wpfsoft.com" Height="350" Width="500">
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <StackPanel Margin="80">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="用户:" VerticalAlignment="Center"/>
            <TextBox Text="{Binding Person.UserName,UpdateSourceTrigger=PropertyChanged}" 
                     Width="200" Height="25"/>
            <TextBlock Text="{Binding Person.UserName}" VerticalAlignment="Center" Margin="5 0"/>
        </StackPanel>
        <Rectangle Height="10"/>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="密码:" VerticalAlignment="Center"/>
            <PasswordBox helper:PasswordBoxHelper.Password="{Binding Person.Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"  
                         Width="200" Height="25"/>
            <TextBlock Text="{Binding Person.Password}" VerticalAlignment="Center" Margin="5 0"/>
        </StackPanel>
        <Rectangle Height="10"/>
        <Button Content="登录" Width="200" Height="25" HorizontalAlignment="Left" Margin="35,0"/>
    </StackPanel>
    
</Window>

最后,F5运行调试结果如下

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:085-《PasswordBox与附加属性实战》-源代码.rar
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年10月26

一、附加属性的概念

要阐述附加属性这个概念,我们先简单聊一下属性的概念。属性就像一个标签,比如一个人,他的年龄、姓名、性别,这些都叫属性,且终生相随,不会因为时间或空间不同而不同。但是有些属性则不然,比如我们在学校的时候,填写表格会多一栏班级与年级属性,毕业了就没有这两个属性了,去电影院的时候会有一个座位号的属性,只有结婚了才多一个老婆属性,我们不可能在大街上对迎面走来的5岁小男孩说你老婆是谁?所以,出生就自带的属性我们可称为一般属性,而在特定条件下、特定场合下才有的属性,我们可看成是附加属性。

为什么特定场合下的属性叫附加属性呢?本质上讲,班级与年级是学校才有的概念,是学校主动附加到每一个学生身上的,为了方便管理嘛。去电影院也是如此,如果不给每一个客人一个座位,那大家都抢着坐C位,很可能会发生一些比银幕上还精彩的事情。

在WPF中,我们在学习布局控件时,其实也已经使用过附加属性了。下面我们来看一些例子。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <Button Grid.Row="0" Content="按钮1"/>
    <Button Grid.Row="1" Content="按钮1"/>
</Grid>

上面的代码中,按钮1被放到Grid的第一行中,按钮2被放到Grid的第二行中。通过Grid.Row附加属性完成这一设置。实际上这个Row属性并没有定义在Button中,而是定义在Grid中,且被定义成附加属性。

<Canvas>
    <Button Canvas.Left="20" Canvas.Top="20" Content="按钮1"/>
    <Button Canvas.Left="80" Canvas.Top="20" Content="按钮1"/>
</Canvas>

观察上面Canvas中的两个按钮,这次为了让它们两个显示在Canvas合适的位置,我们使用了Canvas.Left和Canvas.Top两个属性,分别去设置按钮相对于Canvas的左边距和顶边距。此时,在Canvas类中就定义了Left和Top两个附加属性。

<DockPanel LastChildFill="False">
    <Button DockPanel.Dock="Left" Content="按钮1"/>
    <Button DockPanel.Dock="Right" Content="按钮1"/>
</DockPanel>

观察上面的DockPanel中的两个按钮,这次则采用了DockPanel.Dock附加属性去设置两个按钮的呈现位置。可以相像在DockPanel类中肯定定义了Dock附加属性。

综上所述,附加属性定义好后,是附加到别的控件上起作用的。像酒店一样,客人来开房,酒店给客人房间号,安排客人入住,而不是客人自带一个房间去开房。房间号原本是属于酒店的,只是客人来开房后,才临时属于那个客人,等退房后,房间号便从客人身上消失了。

二、附加属性的定义

在C#后端代码中,键入propa,然后按下tab键,VS会自动创建一个附加属性的定义模板,如下所示。

public static int GetMyProperty(DependencyObject obj)
{
    return (int)obj.GetValue(MyPropertyProperty);
}

public static void SetMyProperty(DependencyObject obj, int value)
{
    obj.SetValue(MyPropertyProperty, value);
}

// Using a DependencyProperty as the backing store for MyProperty.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyPropertyProperty =
    DependencyProperty.RegisterAttached(
        "MyProperty", 
        typeof(int), 
        typeof(ownerclass), 
        new PropertyMetadata(0));

附加属性利用DependencyProperty的RegisterAttached方法成员进行注册,在注册的时候要求传入一些参数,与注册依赖属性的参数完全相同。

只不过在设置或读取附加属性时,将采用SetMyProperty和GetMyProperty的形式,并最终利用SetValue和GetValue方法成员完成。咦?哪里来的SetValue和GetValue?原来,在DependencyObject基类中定义了这两个方法成员,它们是WPF属性系统的业务核心。所以,作为附加属性的调用者(实际受益人,好比上述代码中的Button对象),这个类可一定要继承DependencyObject基类哦。

——重庆教主 2023年10月26日

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1