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、效果展示

