- A+
WPF 中使用附加属性解决 PasswordBox 的数据绑定问题
1、前言
在 WPF 开发中 View 中的数据展示我们常通过 Binding 进行绑定。但是,使用 Binding 有一个前提:绑定的目标只能是依赖属性。 而 PasswordBox 控件中的 Password 并不是一个依赖属性,所以我们在使用 Password 时无法直接进行数据绑定。为了解决这个问题,我们就需要自己定义依赖属性。标题中的 “附加属性” 是依赖属性的一种特殊形式。
2、实现步骤
注:附加属性的定义方式:在 Visual Studio 中输入 propa ,然后按下两次 Tab 键即可。
2.1、定义一个 LoginPasswordBoxHelper 类,并在页面 xaml 代码中添加命名空间,该类用于辅助解决数据绑定问题。
xmlns:vm="clr-namespace:PasswordBoxDemo.ViewModel"
2.2、在类中添加用于绑定的 Password 属性
public static class LoginPasswordBoxHelper { 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(LoginPasswordBoxHelper), new PropertyMetadata("")); }
这个时候就可以在页面的 xaml 中的 PasswordBox 中添加如下数据绑定了:
<PasswordBox Width="200" Height="30" vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
但是,这时候只是提供了一个属性给 PasswordBox 用于 Binding,输入内容后数据没有任何更改效果。
因为当在 PasswordBox 中填写密码时,没有启动对应的事件将密码 Changed 到后端 ViewModel 中的 Password 属性
这时就需要再建一个附加属性 IsPasswordBindingEnable,用于给 PasswordBox 的更改添加事件,并在事件中更改到 后端 ViewModel 中的 Password 属性。
2.3、添加附加属性 IsPasswordBindingEnable,用于给 PasswordBox 的添加更改事件
当 IsPasswordBindingEnable="True" 时,给 PasswordBox 的 PasswordChanged 事件添加处理程序PasswordBoxPasswordChanged;
PasswordBoxPasswordChanged 作用:当页面中 PasswordBox 输入的值发生改变时,通过 SetPassword 完成数据更改,从而实现完整的数据绑定功能。
<PasswordBox Width="200" Height="30" vm:LoginPasswordBoxHelper.IsPasswordBindingEnable="True" vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public static bool GetIsPasswordBindingEnable(DependencyObject obj) { return (bool)obj.GetValue(IsPasswordBindingEnableProperty); } public static void SetIsPasswordBindingEnable(DependencyObject obj, bool value) { obj.SetValue(IsPasswordBindingEnableProperty, value); } public static readonly DependencyProperty IsPasswordBindingEnableProperty = DependencyProperty.RegisterAttached("IsPasswordBindingEnable", typeof(bool), typeof(LoginPasswordBoxHelper), new FrameworkPropertyMetadata(OnIsPasswordBindingEnabledChanged)); private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var passwordBox = obj as PasswordBox; if (passwordBox != null) { passwordBox.PasswordChanged -= PasswordBoxPasswordChanged; if ((bool)e.NewValue) { passwordBox.PasswordChanged += PasswordBoxPasswordChanged; } } } static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e) { var passwordBox = (PasswordBox)sender; if (!String.Equals(GetPassword(passwordBox), passwordBox.Password)) { SetPassword(passwordBox, passwordBox.Password); } }
3、完整代码
3.1、页面代码
Login.xaml
<Window x:Class="PasswordBoxDemo.Login" 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:vm="clr-namespace:PasswordBoxDemo.ViewModel" Title="MainWindow" Width="450" Height="400" mc:Ignorable="d"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <TextBox x:Name="tbUserName" Text="{Binding UserName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="200" Height="30" /> <PasswordBox Grid.Row="1" Width="200" Height="30" vm:LoginPasswordBoxHelper.IsPasswordBindingEnable="True" vm:LoginPasswordBoxHelper.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> <Button x:Name="btnLogin" Grid.Row="2" Content="登录" Width="150" Height="30" /> </Grid> </Window>
Login.xaml.cs
using PasswordBoxDemo.ViewModel; using System.Windows; namespace PasswordBoxDemo { public partial class Login : Window { private MainViewModel resource; public Login() { InitializeComponent(); resource = new MainViewModel(); this.DataContext = resource; } } }
3.2、数据绑定辅助类 LoginPasswordBoxHelper
using System; using System.Windows; using System.Windows.Controls; namespace PasswordBoxDemo.ViewModel { public static class LoginPasswordBoxHelper { 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(LoginPasswordBoxHelper), new PropertyMetadata("")); public static bool GetIsPasswordBindingEnable(DependencyObject obj) { return (bool)obj.GetValue(IsPasswordBindingEnableProperty); } public static void SetIsPasswordBindingEnable(DependencyObject obj, bool value) { obj.SetValue(IsPasswordBindingEnableProperty, value); } public static readonly DependencyProperty IsPasswordBindingEnableProperty = DependencyProperty.RegisterAttached("IsPasswordBindingEnable", typeof(bool), typeof(LoginPasswordBoxHelper), new FrameworkPropertyMetadata(OnIsPasswordBindingEnabledChanged)); private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var passwordBox = obj as PasswordBox; if (passwordBox != null) { passwordBox.PasswordChanged -= PasswordBoxPasswordChanged; if ((bool)e.NewValue) { passwordBox.PasswordChanged += PasswordBoxPasswordChanged; } } } static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e) { var passwordBox = (PasswordBox)sender; if (!String.Equals(GetPassword(passwordBox), passwordBox.Password)) { SetPassword(passwordBox, passwordBox.Password); } } } }
3.3、其它代码
ViewModel:
using GalaSoft.MvvmLight; namespace PasswordBoxDemo.ViewModel { public class MainViewModel : ViewModelBase { public MainViewModel() { } 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(); } } } }
4、附加功能:输入框添加水印
实现水印添加也可以用类似上述的方法实现,具体步骤如下:
4.1、在 LoginPasswordBoxHelper 类中添加附加属性 ShowWaterMark,用与切换水印展示状态;
public static bool GetShowWaterMark(DependencyObject obj) { return (bool)obj.GetValue(ShowWaterMarkProperty); } public static void SetShowWaterMark(DependencyObject obj, bool value) { obj.SetValue(ShowWaterMarkProperty, value); } /// <summary> /// 控制水印显示 /// </summary> public static readonly DependencyProperty ShowWaterMarkProperty = DependencyProperty.RegisterAttached("ShowWaterMark", typeof(bool), typeof(LoginPasswordBoxHelper), new FrameworkPropertyMetadata(true, OnShowWaterMarkChanged)); private static void OnShowWaterMarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
4.2、自定义水印展示样式
<Window.Resources> <Style x:Key="textbox" TargetType="{x:Type TextBox}"> <Setter Property="Padding" Value="2,5,0,0"/> <Setter Property="FontSize" Value="14"/> <Style.Triggers> <Trigger Property="Text" Value=""> <Setter Property="Background"> <Setter.Value> <VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None"> <VisualBrush.Visual> <TextBlock Padding="5,3,0,0" Background="Transparent" Foreground="Silver" FontSize="14" Text="请输入用户名"></TextBlock> </VisualBrush.Visual> </VisualBrush> </Setter.Value> </Setter> </Trigger> </Style.Triggers> </Style> <Style x:Key="password" TargetType="{x:Type PasswordBox}"> <Setter Property="Padding" Value="2,5,0,0"/> <Setter Property="FontSize" Value="14"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type PasswordBox}"> <Border Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true"> <Grid> <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> <StackPanel Orientation="Horizontal" Visibility="Visible" Name="myWaterMark"> <TextBlock Padding="3" Background="Transparent" Foreground="Silver" FontSize="14" Text="请输入密码"/> </StackPanel> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="IsEnabled" Value="false"> <Setter Property="Visibility" TargetName="myWaterMark" Value="Collapsed"/> </Trigger> <Trigger Property="vm:LoginPasswordBoxHelper.ShowWaterMark" Value="False"> <Setter Property="Visibility" TargetName="myWaterMark" Value="Collapsed"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Window.Resources>