前言在WPF应用程序开发中,用户界面的友好性和安全性是至关重要的。特别是在涉及敏感信息(如密码)输入时,提供一个既安全又便捷的输入方式显得尤为重要。
WPF以其强大的UI设计能力,为开发者提供了丰富的控件和灵活的定制选项。然而,默认的 PasswordBox
控件在某些场景下可能无法满足所有需求,例如需要支持数据绑定或动态控制密码的显示与隐藏。 正文设计并实现了一个自定义的WPF密码框控件,该控件不仅继承了 PasswordBox
的基本功能,还增加了对数据绑定的支持以及密码显示/隐藏的功能切换。 通过这一自定义控件,我们可以更轻松地创建出符合用户体验要求的安全输入界面,同时简化代码逻辑,提升开发效率。
Converter 类using System;using System.Collections.Generic;using System.Globalization;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows.Data;using System.Windows;namespaceWpfApp1{ /// /// /// publicclassBooleanToVisibilityConverter : IValueConverter { publicbool IsInversed { get; set; } /// /// /// /// /// /// /// /// public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (valueisbool realValue) { if (IsInversed) realValue = !realValue; return realValue ? Visibility.Visible : Visibility.Collapsed; } return Visibility.Collapsed; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { thrownew NotImplementedException(); } }}
控件代码using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;using System.Windows.Controls;namespaceWpfApp1{ /// /// 通用密码框 /// [TemplatePart(Name = PART_PasswordBox, Type = typeof(PasswordBox))] [TemplatePart(Name = PART_TextBox, Type = typeof(TextBox))] publicclassCommonPasswordBoxWithToggle : UserControl { #region 常量 publicconststring PART_PasswordBox = "PART_PasswordBox"; publicconststring PART_TextBox = "PART_TextBox"; #endregion #region 属性 /// /// 密码框 /// private PasswordBox PARTPasswordBox { get; set; } /// /// 文本框 /// private TextBox PARTTextBox { get; set; } #endregion #region 依赖属性 /// /// 密码 /// publicstring Password { get { return (string)GetValue(PasswordProperty); } set { SetValue(PasswordProperty, value); } } // Using a DependencyProperty as the backing store for Password. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty PasswordProperty = DependencyProperty.Register("Password", typeof(string), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(string.Empty)); /// /// 是否显示密码 /// publicbool IsPasswordVisible { get { return (bool)GetValue(IsPasswordVisibleProperty); } set { SetValue(IsPasswordVisibleProperty, value); } } // Using a DependencyProperty as the backing store for IsPasswordVisible. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty IsPasswordVisibleProperty = DependencyProperty.Register("IsPasswordVisible", typeof(bool), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(false)); /// /// 左侧图标按钮内容 /// /// publicstring LeftIconButtonContent { get { return (string)GetValue(LeftIconButtonContentProperty); } set { SetValue(LeftIconButtonContentProperty, value); } } // Using a DependencyProperty as the backing store for LeftIconButtonContent. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty LeftIconButtonContentProperty = DependencyProperty.Register("LeftIconButtonContent", typeof(string), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(string.Empty)); /// /// 左侧图标按钮显示 /// /// public Visibility LeftIconButtonVisibility { get { return (Visibility)GetValue(LeftIconButtonVisibilityProperty); } set { SetValue(LeftIconButtonVisibilityProperty, value); } } // Using a DependencyProperty as the backing store for LeftIconButtonVisibility. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty LeftIconButtonVisibilityProperty = DependencyProperty.Register("LeftIconButtonVisibility", typeof(Visibility), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(Visibility.Collapsed)); publicstring WaterText { get { return (string)GetValue(WaterTextProperty); } set { SetValue(WaterTextProperty, value); } } // Using a DependencyProperty as the backing store for WaterText. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty WaterTextProperty = DependencyProperty.Register("WaterText", typeof(string), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(string.Empty)); /// /// 隐藏密码图标 /// publicstring HidePasswordIcon { get { return (string)GetValue(HidePasswordIconProperty); } set { SetValue(HidePasswordIconProperty, value); } } // Using a DependencyProperty as the backing store for HidePasswordIcon. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty HidePasswordIconProperty = DependencyProperty.Register("HidePasswordIcon", typeof(string), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(string.Empty)); /// /// 显示密码图标 /// publicstring ShowPasswordIcon { get { return (string)GetValue(ShowPasswordIconProperty); } set { SetValue(ShowPasswordIconProperty, value); } } // Using a DependencyProperty as the backing store for ShowPasswordIcon. This enables animation, styling, binding, etc... publicstaticreadonly DependencyProperty ShowPasswordIconProperty = DependencyProperty.Register("ShowPasswordIcon", typeof(string), typeof(CommonPasswordBoxWithToggle), new PropertyMetadata(string.Empty)); #endregion #region 构造 static CommonPasswordBoxWithToggle() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CommonPasswordBoxWithToggle), new FrameworkPropertyMetadata(typeof(CommonPasswordBoxWithToggle))); } #endregion #region 方法 /// /// 应用模板 /// override public void OnApplyTemplate() { base.OnApplyTemplate(); try { PARTPasswordBox = Template.FindName(PART_PasswordBox, this) as PasswordBox; PARTPasswordBox.PasswordChanged -= PARTPasswordBox_PasswordChanged; PARTPasswordBox.PasswordChanged += PARTPasswordBox_PasswordChanged; PARTTextBox = Template.FindName(PART_TextBox, this) as TextBox; PARTTextBox.TextChanged -= PARTTextBox_TextChanged; PARTTextBox.TextChanged += PARTTextBox_TextChanged; } catch (Exception ex) { } } private void PARTTextBox_TextChanged(object sender, TextChangedEventArgs e) { if (IsPasswordVisible) { PARTPasswordBox.Password = PARTTextBox.Text; } Password = PARTTextBox.Text; } private void PARTPasswordBox_PasswordChanged(object sender, RoutedEventArgs e) { Password = PARTPasswordBox.Password; } #endregion }}
控件样式<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/><local:BooleanToVisibilityConverter x:Key="InversedBooleanToVisibilityConverter" IsInversed="True"/><sys:String x:Key="LockIcon">M11.2668 6.4V4.26667C11.2668 1.93333 9.33343 0 7.0001 0C4.66676 0 2.73343 1.93333 2.73343 4.26667V6.4H0.600098V16H13.4001V6.4H11.2668ZM3.8001 4.26667C3.8001 2.46667 5.2001 1.06667 7.0001 1.06667C8.8001 1.06667 10.2001 2.46667 10.2001 4.26667V6.4H3.8001V4.26667ZM12.3334 7.46667V11.7333H1.66676V7.46667H12.3334ZM1.66676 14.9333V12.8H12.3334V14.9333H1.66676Z<sys:String x:Key="HidePasswordIcon">M15.1657 6.0505C15.3195 5.82104 15.2666 5.5159 15.0648 5.33685C14.8354 5.15782 14.5302 5.20825 14.3512 5.43772C14.326 5.46293 11.4966 8.8017 8.13263 8.8017C4.86953 8.8017 1.91407 5.43772 1.88885 5.4125C1.70982 5.20823 1.37947 5.18303 1.17522 5.36207C0.97095 5.54112 0.945752 5.87145 1.12479 6.07572C1.17522 6.15137 1.78799 6.8398 2.75634 7.60388L1.45513 8.95552C1.25087 9.15978 1.27606 9.49012 1.48033 9.66917C1.53077 9.77003 1.65938 9.82298 1.78547 9.82298C1.91407 9.82298 2.04017 9.77255 2.14355 9.66917L3.54562 8.21665C4.20883 8.6756 4.99813 9.10933 5.8656 9.41447L5.331 11.2251C5.25535 11.505 5.40665 11.7849 5.68909 11.8631H5.84291C6.07237 11.8631 6.27664 11.7092 6.32707 11.4798L6.86169 9.66917C7.27021 9.74482 7.70394 9.79777 8.13515 9.79777C8.56889 9.79777 9.00262 9.74734 9.40862 9.66917L9.94322 11.4545C9.99365 11.684 10.2231 11.8378 10.4274 11.8378C10.4778 11.8378 10.5283 11.8378 10.556 11.8126C10.8359 11.737 10.9897 11.4545 10.9141 11.1746L10.3719 9.38925C11.2394 9.08412 12.0287 8.65038 12.6919 8.19143L14.0687 9.61873C14.1696 9.7196 14.2982 9.77255 14.4268 9.77255C14.5554 9.77255 14.6815 9.72212 14.7849 9.61873C14.9892 9.41447 14.9892 9.10933 14.8101 8.90508L13.5114 7.55345C14.5529 6.78937 15.1657 6.05048 15.1657 6.05048V6.0505Z<sys:String x:Key="ShowPasswordIcon">M8.01681 3.91217C11.2069 3.91217 13.9167 6.06444 15.2487 7.34645C15.5671 7.65338 15.7501 8.09132 15.7501 8.54611C15.7501 9.00089 15.5671 9.43883 15.2487 9.74577C13.9167 11.0278 11.2069 13.18 8.01681 13.18C4.82494 13.18 2.11168 11.024 0.779687 9.74015C0.461204 9.43509 0.279968 8.99902 0.279968 8.54611C0.279968 8.09319 0.462964 7.65713 0.779687 7.35206C2.11168 6.06819 4.82318 3.91217 8.01681 3.91217ZM8.01681 11.9448C10.818 11.9448 13.2621 9.99468 14.4692 8.83058C14.573 8.73139 14.5888 8.60974 14.5888 8.54611C14.5888 8.48247 14.573 8.36083 14.4692 8.26163C13.2603 7.09941 10.8163 5.14739 8.01681 5.14739C5.21205 5.14739 2.768 7.10128 1.55918 8.26725C1.45712 8.36457 1.44129 8.48435 1.44129 8.54611C1.44129 8.60787 1.45712 8.72578 1.55918 8.82497C2.768 9.99094 5.21381 11.9448 8.01681 11.9448ZM5.95087 8.54655C5.95087 7.40748 6.87743 6.48248 8.01493 6.48248C9.15399 6.48248 10.079 7.40592 10.079 8.54498C10.079 9.68405 9.15243 10.609 8.01493 10.609C6.87743 10.609 5.95087 9.68561 5.95087 8.54655ZM6.98368 8.54655C6.98368 9.1153 7.44618 9.5778 8.01493 9.5778C8.58368 9.5778 9.04618 9.1153 9.04618 8.54655C9.04618 7.9778 8.58368 7.5153 8.01493 7.5153C7.44618 7.5153 6.98368 7.9778 6.98368 8.54655Z<Style x:Key="CommonPasswordBoxWithToggleStyle" TargetType="{x:Type local:CommonPasswordBoxWithToggle}"> <Setter Property="ShowPasswordIcon" Value="{DynamicResource ShowPasswordIcon}"/> <Setter Property="HidePasswordIcon" Value="{DynamicResource HidePasswordIcon}"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CommonPasswordBoxWithToggle}"> <Grid> <Border x:Name="PART_Border" Background="White" BorderBrush="#FFABADB3" BorderThickness="1"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="12"/> <ColumnDefinition Width="auto"/> <ColumnDefinition Width="*"/> <ColumnDefinition Width="auto"/> <Path Grid.Column="1" x:Name="PathUC" Height="16" Width="16" HorizontalAlignment="Center" VerticalAlignment="Center" Data="{Binding LeftIconButtonContent, RelativeSource={RelativeSource Mode=TemplatedParent}}" Fill="{TemplateBinding Foreground}" Stretch="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=IconStretch}" Margin="0 0 6 0"/> <Border Grid.Column="2" Name="PART_WaterMark" Panel.ZIndex="100" IsHitTestVisible="False" BorderBrush="{TemplateBinding BorderBrush}" Opacity="0"> <Border.Background> <VisualBrush AlignmentX="Left" Stretch="None"> <VisualBrush.Visual> <TextBlock Text="{TemplateBinding WaterText}" FontSize="14" Foreground="{DynamicResource FontSecondaryBodyColor}"/> <PasswordBox Grid.Column="2" x:Name="PART_PasswordBox" Visibility="{Binding IsPasswordVisible,RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource InversedBooleanToVisibilityConverter}}" PasswordChar="●" VerticalAlignment="Center" VerticalContentAlignment="Center" BorderThickness="0" FontSize="12"/> <TextBox Grid.Column="2" x:Name="PART_TextBox" Visibility="{Binding IsPasswordVisible, RelativeSource={RelativeSource TemplatedParent},Converter={StaticResource BooleanToVisibilityConverter}}" Text="{Binding Password, RelativeSource={RelativeSource TemplatedParent},UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Center" VerticalContentAlignment="Center" BorderThickness="0" FontSize="12"/> <ToggleButton Grid.Column="3" Margin="5,0" IsChecked="{Binding IsPasswordVisible,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="16" Height="16"> <ToggleButton.Style> <Style TargetType="ToggleButton"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ToggleButton"> <Border Background="Transparent"> <Path x:Name="EyeIcon" Data="{Binding HidePasswordIcon,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:CommonPasswordBoxWithToggle}}" Fill="#666" Stretch="Uniform"/> <ControlTemplate.Triggers> <Trigger Property="IsChecked" Value="True"> <Setter TargetName="EyeIcon" Property="Data" Value="{Binding ShowPasswordIcon,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=local:CommonPasswordBoxWithToggle}}"/> <ControlTemplate.Triggers> <Trigger Property="Password" Value=""> <Setter TargetName="PART_WaterMark" Property="Opacity" Value="1"/> <Trigger Property="Password" Value="{x:Null}"> <Setter TargetName="PART_WaterMark" Property="Opacity" Value="1"/> <Trigger Property="IsMouseOver" Value="True"> <Setter TargetName="PART_Border" Property="BorderBrush" Value="LightBlue"/> <Trigger Property="IsFocused" Value="True"> <Setter TargetName="PART_Border" Property="BorderBrush" Value="LightBlue"/>
调用控件<local:CommonPasswordBoxWithToggle Style="{DynamicResource CommonPasswordBoxWithToggleStyle}" WaterText="请输入密码" Password="{Binding YourPassword,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" LeftIconButtonVisibility="Visible" LeftIconButtonContent="{DynamicResource LockIcon}" Width="300" Height="34"/>
控件效果总结通过本项目所实现的自定义WPF密码框控件,我们解决了标准 PasswordBox
控件在实际应用中的局限性。该控件不仅支持双向数据绑定,使得MVVM模式下的开发更加顺畅,还允许用户根据需要自由切换密码的显示与隐藏状态,极大地提升了用户体验。另外,简洁的设计和灵活的配置选项使得该控件易于集成到现有项目中,帮助大家快速构开发既美观又实用的用户界面。 最后
如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。也可以加入微信公众号[DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!
作者:苏秦与真相
出处:cnblogs.com/JqkAman/p/18755005
声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!
方便大家交流、资源共享和共同成长
纯技术交流群,需要加入的小伙伴请扫码,并备注【加群】
推荐阅读
觉得有收获?不妨分享让更多人受益
关注「DotNet技术匠」,共同提升技术实力