最近在用MVVM框架做一个用户登录模块,涉及到密码框绑定的问题遇到阻碍。WPF中有现成的密码输入框控件PasswordBox,通过Password属性获取密码,但是Password属性不能进行数据绑定,原因是Password属性不是依赖属性。
网上常见的解决思路是“将密码框的密码和某一个缓冲区进行同步, 缓冲区在和后台进行绑定. 其中密码框与缓冲区之间的同步可采用事件进行通知, 并将缓冲区打造成依赖属性, 然后缓冲区就支持绑定了, 并给后台提供正确的密码”,这个方法确实可以达到绑定的功能,但是有一个问题“在更改了密码框的密码后, 需要手动更新密码框插入符(CaretIndex)的位置”,虽然也有相对应的解决方案,但效果不好。本思路的解决方案请参考周银辉的《[WPF]实现密码框的密码绑定》http://www.cnblogs.com/zhouyinhui/archive/2009/08/27/1554943.html
另外一种思路是重写PasswordBox,在此参考,该作者已经用TextBox作为基础重写了类似PasswordBox的控件:
《Passwords in WPF: How to find yours and how to prevent it being found》
http://www.codeproject.com/Articles/30976/Passwords-in-WPF-How-to-find-yours-and-how-to-prev
为了实现数据绑定,笔者将该控件作了相应修改,实现如下所示:
/// <summary> /// More secure PasswordBox based on TextBox control /// </summary> public class SecurePasswordBox : TextBox { // Fake char to display in Visual Tree private char PWD_CHAR = '●'; /// <summary> /// Only copy of real password /// </summary> /// <remarks>For more security use System.Security.SecureString type instead</remarks> private string _password = string.Empty; /// <summary> /// TextChanged event handler for secure storing of password into Visual Tree, /// text is replaced with PWD_CHAR chars, clean text is keept into /// Text property (CLR property not snoopable without mod) /// </summary> protected override void OnTextChanged(TextChangedEventArgs e) { if (dirtyBaseText == true) return; string currentText = this.BaseText; int selStart = this.SelectionStart; if (currentText.Length < _password.Length) { // Remove deleted chars _password = _password.Remove(selStart, _password.Length - currentText.Length); SetRealText(this, _password); } if (!string.IsNullOrEmpty(currentText)) { for (int i = 0; i < currentText.Length; i++) { if (currentText[i] != PWD_CHAR) { // Replace or insert char if (this.BaseText.Length == _password.Length) { _password = _password.Remove(i, 1).Insert(i, currentText[i].ToString());
} else { _password = _password.Insert(i, currentText[i].ToString());
} } }
SetRealText(this, _password);
this.BaseText = new string(PWD_CHAR, _password.Length); this.SelectionStart = selStart; } base.OnTextChanged(e); } // flag used to bypass OnTextChanged private bool dirtyBaseText; /// <summary> /// Provide access to base.Text without call OnTextChanged /// </summary> private string BaseText { get { return base.Text; } set { dirtyBaseText = true; base.Text = value; dirtyBaseText = false; } } /// <summary> /// Clean Password /// </summary> public new string Text { get { return _password; } set { _password = value; this.BaseText = new string(PWD_CHAR, value.Length); } } //数据绑定用的附加属性RealText public static string GetRealText(DependencyObject obj) { return (string)obj.GetValue(RealTextProperty); } public static void SetRealText(DependencyObject obj, string value) { obj.SetValue(RealTextProperty, value); } // Using a DependencyProperty as the backing store for RealText. This enables animation, styling, binding, etc... public static readonly DependencyProperty RealTextProperty = DependencyProperty.RegisterAttached("RealText", typeof(string), typeof(SecurePasswordBox), new UIPropertyMetadata("")); }
数据绑定如下:【注意:附加属性的绑定默认是单向的】
<local:SecurePasswordBox local:SecurePasswordBox.RealText="{Binding Password,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Text="{Binding Password,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>