首先來說講建立這個控件的初衷,一個讓我很鬱悶的問題。html
公司的客戶端項目採用WPF+MVVM技術實現,在近期地推客戶端的過程當中遇到了一個很奇葩的問題:在登陸界面點擊密碼框就會直接閃退,沒有任何提示ide
密碼框是WPF原生的PasswordBox,這彷佛沒有什麼不對。出現這個狀況的通常是在xp系統(ghost的雨林木風版本或番茄花園),某些xp系統又不會出現。字體
出現這個問題的緣由是由於客戶的系統缺乏PasswordBox使用的某種默認的字體,網上有人說是times new roman,又或者其它某種字體。其實只要找到了這個字體,並在程序啓動的時候安裝這種字體能夠解決。ui
但我以爲,這個解決方案太麻煩。與其依賴PasswordBox使用的默認字體,不如重寫PasswordBox,避免密碼框字體的依賴。this
如今讓咱們來重寫一個本身的帶水印的文本(密碼)框吧spa
1.首先建立一個類,繼承TextBoxorm
public class SJTextBox : TextBox
2.指定依賴屬性的實例重寫基類型的元數據htm
static SJTextBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SJTextBox), new FrameworkPropertyMetadata(typeof(SJTextBox))); }
3.定義依賴屬性 對象
public static DependencyProperty WaterRemarkProperty = DependencyProperty.Register("WaterRemark", typeof(string), typeof(SJTextBox)); /// <summary> /// 水印文字 /// </summary> public string WaterRemark { get { return GetValue(WaterRemarkProperty).ToString(); } set { SetValue(WaterRemarkProperty, value); } } public static DependencyProperty BorderCornerRadiusProperty = DependencyProperty.Register("BorderCornerRadius", typeof(CornerRadius), typeof(SJTextBox)); /// <summary> /// 邊框角度 /// </summary> public CornerRadius BorderCornerRadius { get { return (CornerRadius)GetValue(BorderCornerRadiusProperty); } set { SetValue(BorderCornerRadiusProperty, value); } } public static DependencyProperty IsPasswordBoxProperty = DependencyProperty.Register("IsPasswordBox", typeof(bool), typeof(SJTextBox), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsPasswordBoxChnage))); /// <summary> /// 是否爲密碼框 /// </summary> public bool IsPasswordBox { get { return (bool)GetValue(IsPasswordBoxProperty); } set { SetValue(IsPasswordBoxProperty, value); } } public static DependencyProperty PasswordCharProperty = DependencyProperty.Register("PasswordChar", typeof(char), typeof(SJTextBox), new FrameworkPropertyMetadata('●')); /// <summary> /// 替換明文的單個密碼字符 /// </summary> public char PasswordChar { get { return (char)GetValue(PasswordCharProperty); } set { SetValue(PasswordCharProperty, value); } } public static DependencyProperty PasswordStrProperty = DependencyProperty.Register("PasswordStr", typeof(string), typeof(SJTextBox), new FrameworkPropertyMetadata(string.Empty)); /// <summary> /// 密碼字符串 /// </summary> public string PasswordStr { get { return GetValue(PasswordStrProperty).ToString(); } set { SetValue(PasswordStrProperty, value); } }
4.當設置爲密碼框時,監聽TextChange事件,處理Text的變化,這是密碼框的核心功能blog
private static void OnIsPasswordBoxChnage(DependencyObject sender, DependencyPropertyChangedEventArgs e) { (sender as SJTextBox).SetEvent(); } /// <summary> /// 定義TextChange事件 /// </summary> private void SetEvent() { if (IsPasswordBox) this.TextChanged += SJTextBox_TextChanged; else this.TextChanged -= SJTextBox_TextChanged; }
5.在TextChange事件中,處理Text爲密碼文,並將原字符記錄給PasswordStr予以存儲
private void SJTextBox_TextChanged(object sender, TextChangedEventArgs e) { if (!IsResponseChange) //響應事件標識,替換字符時,不處理後續邏輯 return; Console.WriteLine(string.Format("------{0}------", e.Changes.Count)); foreach (TextChange c in e.Changes) { Console.WriteLine(string.Format("addLength:{0} removeLenth:{1} offSet:{2}", c.AddedLength, c.RemovedLength, c.Offset)); PasswordStr = PasswordStr.Remove(c.Offset, c.RemovedLength); //從密碼文中根據本次Change對象的索引和長度刪除對應個數的字符 PasswordStr = PasswordStr.Insert(c.Offset, Text.Substring(c.Offset, c.AddedLength)); //將Text新增的部分記錄給密碼文 lastOffset = c.Offset; } Console.WriteLine(PasswordStr); /*將文本轉換爲密碼字符*/ IsResponseChange = false; //設置響應標識爲不響應 this.Text = ConvertToPasswordChar(Text.Length); //將輸入的字符替換爲密碼字符 IsResponseChange = true; //回覆響應標識 this.SelectionStart = lastOffset + 1; //設置光標索引 Console.WriteLine(string.Format("SelectionStar:{0}", this.SelectionStart)); }
/// <summary> /// 按照指定的長度生成密碼字符 /// </summary> /// <param name="length"></param> /// <returns></returns> private string ConvertToPasswordChar(int length) { if (PasswordBuilder != null) PasswordBuilder.Clear(); else PasswordBuilder = new StringBuilder(); for (var i = 0; i < length; i++) PasswordBuilder.Append(PasswordChar); return PasswordBuilder.ToString(); }
ConvertToPasswordChar()方法用於返回指定個數的密碼字符,替換Text爲密碼文就是調用此方法傳遞Text的長度完成的
6.若是用戶設置了記住密碼,密碼文(PasswordStr)一開始就有值的話,別忘了在Load事件裏事先替換一次明文
private void SJTextBox_Loaded(object sender, RoutedEventArgs e) { if (IsPasswordBox) { IsResponseChange = false; this.Text = ConvertToPasswordChar(PasswordStr.Length); IsResponseChange = true; } }
7.代碼邏輯部分已經完成,替換明文爲密碼字符的功能已經實現了。自定義邊框角度,水印功能,須要藉助Style來完成,讓咱們來爲它寫一個Style
<Style TargetType="{x:Type local:SJTextBox}"> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="VerticalContentAlignment" Value="Center"/> <Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderBrush" Value="Gray"/> <Setter Property="Cursor" Value="IBeam"/> <Setter Property="Padding" Value="3,0,0,0"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:SJTextBox}"> <Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding BorderCornerRadius}" <!--綁定自定義邊框角度--> SnapsToDevicePixels="True"> <Grid> <ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/> <TextBlock x:Name="txtRemark" Text="{TemplateBinding WaterRemark}" <!--綁定水印文字--> Foreground="Gray" VerticalAlignment="Center" Margin="{TemplateBinding Padding}" Visibility="Collapsed"/> <!--默認水印文字隱藏--> </Grid> </Border> <ControlTemplate.Triggers> <Trigger Property="Text" Value=""> <!--使用觸發器來控制水印的隱藏顯示:當文本框沒有字符時顯示水印文字--> <Setter Property="Visibility" Value="Visible" TargetName="txtRemark"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style>
8.自定義水印文本(密碼)框的調用
<local:SJTextBox Height="30" BorderCornerRadius="3" Margin="10,10" Background="White" WaterRemark="This is a TextBox"/> <!--水印文本框--> <local:SJTextBox Height="30" BorderCornerRadius="3" Margin="10,0" Background="White" WaterRemark="This is a PassworBox" IsPasswordBox="True" PasswordStr="{Binding Password}"/> <!--水印密碼框-->
自定義的水印文本(密碼)框已經完成了,它避免了對密碼字體的依賴,同時密碼文屬性PasswordStr也支持數據綁定,很是方便。
效果圖: