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日