首先來講講創建這個控件的初衷,一個讓我很郁悶的問題。
公司的客戶端項目采用WPF+MVVM技術實現,在近期地推客戶端的過程中遇到了一個很奇葩的問題:在登錄界面點擊密碼框就會直接閃退,沒有任何提示
密碼框是WPF原生的PasswordBox,這似乎沒有什么不對。出現這個情況的一般是在xp系統(ghost的雨林木風版本或番茄花園),某些xp系統又不會出現。
出現這個問題的原因是因為客戶的系統缺少PasswordBox使用的某種默認的字體,網上有人說是times new roman,又或者其它某種字體。其實只要找到了這個字體,並在程序啟動的時候安裝這種字體可以解決。
但我覺得,這個解決方案太麻煩。與其依賴PasswordBox使用的默認字體,不如重寫PasswordBox,避免密碼框字體的依賴。
現在讓我們來重寫一個自己的帶水印的文本(密碼)框吧
1.首先創建一個類,繼承TextBox
public class SJTextBox : TextBox
2.指定依賴屬性的實例重寫基類型的元數據
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的變化,這是密碼框的核心功能
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也支持數據綁定,非常方便。
效果圖:

