WPF 中使用附加属性解决 PasswordBox 的数据绑定问题

  • A+
所属分类:.NET技术
摘要

在 WPF 开发中 View 中的数据展示我们常通过 Binding 进行绑定。但是,使用 Binding 有一个前提:绑定的目标只能是依赖属性。 而 PasswordBox 控件中的 Password 并不是一个依赖属性,所以我们在使用 Password 时无法直接进行数据绑定。为了解决这个问题,我们就需要自己定义依赖属性。标题中的 “附加属性” 是依赖属性的一种特殊形式。


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> 

5、效果展示

WPF 中使用附加属性解决 PasswordBox 的数据绑定问题