最近学习WPF,突然想做PasswordBox明文显示的样式,在网上搜索了一下,代码都有问题,不能够直接复用。所以,在同事的帮助下,做了一个明文显示样式的Demo。
首先,创建一个WPF App的项目。
打开MainWindow.xaml文件,代码如下:
1 <Window x:Class="WpfApp4.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:WpfApp4" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="450" Width="800"> 9 <Grid> 10 </Grid> 11 </Window>
构建Grid布局,并创建两个控件,分别为TextBox、PasswordBox,代码如下:
1 <Grid> 2 <Grid.RowDefinitions> 3 <RowDefinition Height="40"/> 4 <RowDefinition Height="auto"/> 5 </Grid.RowDefinitions> 6 <TextBox x:Name="userName" Grid.Row="0" Width="200" Height="40" HorizontalAlignment="Left"></TextBox> 7 <PasswordBox x:Name="userPwd" Grid.Row="1" Width="200" Height="40" HorizontalAlignment="Left" Style="{StaticResource PasswordBoxStyle1}" FontSize="20" VerticalAlignment="Center" local:ControlAttachProperty.PlaceHolder="请输入密码" /> 8 </Grid>
此时,Style="{StaticResource PasswordBoxStyle1}" 与 local:ControlAttachProperty.PlaceHolder="请输入密码" 会报错。(现在可以删掉这两句)
现在要实现的样式效果:
1、输入密码后,点击眼睛按钮可以显示密码明文效果。
2、密码框为空且没有获取焦点时,显示提示文字。
3、密码为空时,不允许点击眼睛按钮
样式代码需要放在Window标签下Grid标签前,代码如下:
1 <Window.Resources> 2 <Style x:Key="FocusVisual"> 3 <Setter Property="Control.Template"> 4 <Setter.Value> 5 <ControlTemplate> 6 <Rectangle Margin="2" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/> 7 </ControlTemplate> 8 </Setter.Value> 9 </Setter> 10 </Style> 11 12 <!--眼睛按钮的样式--> 13 <Style TargetType="Button" x:Key="EyeButton"> 14 <Setter Property="Template"> 15 <Setter.Value> 16 <ControlTemplate TargetType="Button"> 17 <Border Background="{TemplateBinding Background}" /> 18 </ControlTemplate> 19 </Setter.Value> 20 </Setter> 21 </Style> 22 23 <!--PassWordBox样式--> 24 <SolidColorBrush x:Key="TextBox.Static.Border" Color="#FFABAdB3"/> 25 <SolidColorBrush x:Key="TextBox.MouseOver.Border" Color="#FF7EB4EA"/> 26 <SolidColorBrush x:Key="TextBox.Focus.Border" Color="#FF569DE5"/> 27 <Style x:Key="PasswordBoxStyle1" TargetType="{x:Type PasswordBox}"> 28 <Setter Property="local:PasswordBoxHelper.Attach" Value="True"/> 29 <Setter Property="PasswordChar" Value="●"/> 30 <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/> 31 <Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border}"/> 32 <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/> 33 <Setter Property="BorderThickness" Value="1"/> 34 <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/> 35 <Setter Property="HorizontalContentAlignment" Value="Left"/> 36 <Setter Property="FocusVisualStyle" Value="{x:Null}"/> 37 <Setter Property="AllowDrop" Value="true"/> 38 <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/> 39 <Setter Property="Stylus.IsFlicksEnabled" Value="False"/> 40 <Setter Property="Template"> 41 <Setter.Value> 42 <ControlTemplate TargetType="{x:Type PasswordBox}"> 43 <Border x:Name="border" CornerRadius="10" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"> 44 <!--重写构造PasswordBox--> 45 <Grid x:Name="PART_InnerGrid"> 46 <Grid.ColumnDefinitions> 47 <ColumnDefinition/> 48 <ColumnDefinition Width="Auto"/> 49 </Grid.ColumnDefinitions> 50 <!--PasswordBox原有的显示节点--> 51 <ScrollViewer x:Name="PART_ContentHost" BorderThickness="0" IsTabStop="False" VerticalAlignment="Stretch" Background="{x:Null}" VerticalContentAlignment="Center" Margin="5,5"/> 52 <!--创建明文显示的TextBox--> 53 <TextBox x:Name="PART_PasswordShower" BorderBrush="Transparent" Text="{Binding Path=(local:PasswordBoxHelper.Password),RelativeSource={RelativeSource TemplatedParent}}" BorderThickness="0" Visibility="Collapsed" HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,5"/> 54 <!--创建提示字符--> 55 <TextBlock x:Name="PART_PlaceHolder" Text="{Binding Path=(local:ControlAttachProperty.PlaceHolder),RelativeSource={RelativeSource TemplatedParent}}" Visibility="Collapsed" Opacity="0.6" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,5"/> 56 <!--触发按钮显示样式--> 57 <Button x:Name="PART_ToggleEye" Grid.Column="1" Width="40" Margin="3,3" BorderThickness="0" Style="{StaticResource EyeButton}" > 58 <Button.Background> 59 <ImageBrush x:Name="img_eye" ImageSource="eye_slash.png"/> 60 </Button.Background> 61 </Button> 62 </Grid> 63 </Border> 64 <ControlTemplate.Triggers> 65 <Trigger Property="IsEnabled" Value="false"> 66 <Setter Property="Opacity" TargetName="border" Value="0.56"/> 67 </Trigger> 68 <Trigger Property="IsMouseOver" Value="true"> 69 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.MouseOver.Border}"/> 70 </Trigger> 71 <Trigger Property="IsKeyboardFocused" Value="true"> 72 <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border}"/> 73 </Trigger> 74 <!--密码框为空设置按钮禁用--> 75 <Trigger Property="local:PasswordBoxHelper.Password" Value=""> 76 <Setter TargetName="PART_ToggleEye" Property="IsEnabled" Value="False"/> 77 </Trigger> 78 <!--按住按钮,更改按钮背景图片并设置明文框显示且密码框不显示且不占用--> 79 <Trigger Property="IsPressed" SourceName="PART_ToggleEye" Value="true"> 80 <Setter TargetName="PART_ToggleEye" Property="Background"> 81 <Setter.Value> 82 <ImageBrush ImageSource="eye.png"/> 83 </Setter.Value> 84 </Setter> 85 <Setter TargetName="PART_ContentHost" Property="Visibility" Value="Collapsed"/> 86 <Setter TargetName="PART_PasswordShower" Property="Visibility" Value="Visible"/> 87 </Trigger> 88 <!--密码框为空不且没有获取焦点时,设置提示文字显示--> 89 <MultiTrigger> 90 <MultiTrigger.Conditions> 91 <Condition Property="local:PasswordBoxHelper.Password" Value=""/> 92 <Condition Property="IsFocused" Value="False"/> 93 </MultiTrigger.Conditions> 94 <Setter TargetName="PART_PlaceHolder" Property="Visibility" Value="Visible"/> 95 </MultiTrigger> 96 </ControlTemplate.Triggers> 97 </ControlTemplate> 98 </Setter.Value> 99 </Setter> 100 <Style.Triggers> 101 <MultiTrigger> 102 <MultiTrigger.Conditions> 103 <Condition Property="IsInactiveSelectionHighlightEnabled" Value="true"/> 104 <Condition Property="IsSelectionActive" Value="false"/> 105 </MultiTrigger.Conditions> 106 <Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/> 107 </MultiTrigger> 108 </Style.Triggers> 109 </Style> 110 </Window.Resources>
代码中使用了PasswordBoxHelper及ControlAttachProperty的类,PasswordBoxHelper用于获取PasswordwordBox的密码,提供给显示框显示明文密码。ControlAttachProperty用于创建提示文字的依赖属性。(注意:两个类放在MainWindow.xaml同级目录中)
PasswordBoxHelper代码如下:
1 public class PasswordBoxHelper 2 { 3 public static readonly DependencyProperty PasswordProperty = 4 DependencyProperty.RegisterAttached("Password", 5 typeof(string), typeof(PasswordBoxHelper), 6 new FrameworkPropertyMetadata(string.Empty, OnPasswordPropertyChanged)); 7 public static readonly DependencyProperty AttachProperty = 8 DependencyProperty.RegisterAttached("Attach", 9 typeof(bool), typeof(PasswordBoxHelper), new PropertyMetadata(false, Attach)); 10 private static readonly DependencyProperty IsUpdatingProperty = 11 DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), 12 typeof(PasswordBoxHelper)); 13 14 public static void SetAttach(DependencyObject dp, bool value) 15 { 16 dp.SetValue(AttachProperty, value); 17 } 18 public static bool GetAttach(DependencyObject dp) 19 { 20 return (bool)dp.GetValue(AttachProperty); 21 } 22 public static string GetPassword(DependencyObject dp) 23 { 24 return (string)dp.GetValue(PasswordProperty); 25 } 26 public static void SetPassword(DependencyObject dp, string value) 27 { 28 dp.SetValue(PasswordProperty, value); 29 } 30 private static bool GetIsUpdating(DependencyObject dp) 31 { 32 return (bool)dp.GetValue(IsUpdatingProperty); 33 } 34 private static void SetIsUpdating(DependencyObject dp, bool value) 35 { 36 dp.SetValue(IsUpdatingProperty, value); 37 } 38 private static void OnPasswordPropertyChanged(DependencyObject sender, 39 DependencyPropertyChangedEventArgs e) 40 { 41 PasswordBox passwordBox = sender as PasswordBox; 42 passwordBox.PasswordChanged -= PasswordChanged; 43 if (!(bool)GetIsUpdating(passwordBox)) 44 { 45 passwordBox.Password = (string)e.NewValue; 46 } 47 passwordBox.PasswordChanged += PasswordChanged; 48 } 49 private static void Attach(DependencyObject sender, 50 DependencyPropertyChangedEventArgs e) 51 { 52 PasswordBox passwordBox = sender as PasswordBox; 53 if (passwordBox == null) 54 return; 55 if ((bool)e.OldValue) 56 { 57 passwordBox.PasswordChanged -= PasswordChanged; 58 } 59 if ((bool)e.NewValue) 60 { 61 passwordBox.PasswordChanged += PasswordChanged; 62 } 63 } 64 private static void PasswordChanged(object sender, RoutedEventArgs e) 65 { 66 PasswordBox passwordBox = sender as PasswordBox; 67 SetIsUpdating(passwordBox, true); 68 SetPassword(passwordBox, passwordBox.Password); 69 SetIsUpdating(passwordBox, false); 70 } 71 }
ControlAttachProperty代码如下:
1 public class ControlAttachProperty 2 { 3 public static string GetPlaceHolder(DependencyObject obj) 4 { 5 return (string)obj.GetValue(PlaceHolderProperty); 6 } 7 8 public static void SetPlaceHolder(DependencyObject obj, string value) 9 { 10 obj.SetValue(PlaceHolderProperty, value); 11 } 12 13 // Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc... 14 public static readonly DependencyProperty PlaceHolderProperty = 15 DependencyProperty.RegisterAttached("PlaceHolder", typeof(string), typeof(ControlAttachProperty), new PropertyMetadata(string.Empty)); 16 }
现在,需要把之前删掉的两句(Style="{StaticResource PasswordBoxStyle1}" 与 local:ControlAttachProperty.PlaceHolder="请输入密码" )加进来。 Style是设置PasswordBox的样式, 在样式里可以重构PasswordBox。local:ControlAttachProperty.PlaceHolder是为passwordbox新增的依赖属性。
运行代码即可获取实现的效果。
需要注意的是:
①代码中用了两个图片,分别时eye.png与eye_slash.png, 在写Demo的时候,是直接放在MainWindow.xaml同级文件夹里。
②xmlns:local="clr-namespace:WpfApp4" , 这里是创建wpf项目的命名空间,喜欢复制代码的兄弟,记得更改为当前创建项目的命名空间。