WPF中的自定义屏蔽密码框

时间:2013-07-01 15:02:14

标签: wpf passwords

在iOS中,当您在字段中键入密码时,会显示该字段的最后一个字母,但在您键入下一个字符时会对其进行模糊处理。有没有办法在WPF中复制此行为?

1 个答案:

答案 0 :(得分:11)

好的,如果您在桌面应用中使用此类内容是合理的,那么您可以执行以下操作。

我们之前有类似的要求,这就是我所做的。

  • 我通过从Passwordbox派生并向其添加类型为TextBox的新DP来创建自定义SecureString。(与普通PasswordBox几乎相同的概念)。我们不会以这种方式放弃任何安全利益,并可以根据我们内心的内容定制视觉行为。
  • 现在我们可以使用Text的{​​{1}}作为显示字符串,并在后端TextBox DP中保存实际密码并将其绑定到VM。
  • 我们处理SecureStringPreviewTextInput事件以管理控件中的所有文字更改,包括PreviewKeyDownKey.Back和恼人的Key.Delete等内容(这不是来自Key.Space

iOS感觉:

为了确切的iOS行为,请注意更多注意事项。

  1. 仅在向“当前字符串的结尾”添加新字符时显示最后一个字符(PreviewTextInput独立)
  2. 在现有字符串之间编辑字符对蒙版没有影响。
  3. 显示的最后一个字符取决于定时器(如果闲置,则在一段时间后变为“*”)
  4. 控件中禁用了所有复制粘贴操作。
  5. 检测到文本更改时,可以非常轻松地处理前2个点,对于最后一个,我们可以使用FlowDirection来相应地处理显示字符串。

    所以把这一切放在一起我们最终得到:

    DispatcherTimer

    工作样本:

    <强> Download Link

    您可以在控件中键入内容并检查其下方显示的存储值。

    在此示例中,我添加了一个类型为/// <summary> /// This class contains properties for CustomPasswordBox /// </summary> internal class CustomPasswordBox : TextBox { #region Member Variables /// <summary> /// Dependency property to hold watermark for CustomPasswordBox /// </summary> public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register( "Password", typeof(SecureString), typeof(CustomPasswordBox), new UIPropertyMetadata(new SecureString())); /// <summary> /// Private member holding mask visibile timer /// </summary> private readonly DispatcherTimer _maskTimer; #endregion #region Constructors /// <summary> /// Initialises a new instance of the LifeStuffPasswordBox class. /// </summary> public CustomPasswordBox() { PreviewTextInput += OnPreviewTextInput; PreviewKeyDown += OnPreviewKeyDown; CommandManager.AddPreviewExecutedHandler(this, PreviewExecutedHandler); _maskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1) }; _maskTimer.Tick += (sender, args) => MaskAllDisplayText(); } #endregion #region Commands & Properties /// <summary> /// Gets or sets dependency Property implementation for Password /// </summary> public SecureString Password { get { return (SecureString)GetValue(PasswordProperty); } set { SetValue(PasswordProperty, value); } } #endregion #region Methods /// <summary> /// Method to handle PreviewExecutedHandler events /// </summary> /// <param name="sender">Sender object</param> /// <param name="executedRoutedEventArgs">Event Text Arguments</param> private static void PreviewExecutedHandler(object sender, ExecutedRoutedEventArgs executedRoutedEventArgs) { if (executedRoutedEventArgs.Command == ApplicationCommands.Copy || executedRoutedEventArgs.Command == ApplicationCommands.Cut || executedRoutedEventArgs.Command == ApplicationCommands.Paste) { executedRoutedEventArgs.Handled = true; } } /// <summary> /// Method to handle PreviewTextInput events /// </summary> /// <param name="sender">Sender object</param> /// <param name="textCompositionEventArgs">Event Text Arguments</param> private void OnPreviewTextInput(object sender, TextCompositionEventArgs textCompositionEventArgs) { AddToSecureString(textCompositionEventArgs.Text); textCompositionEventArgs.Handled = true; } /// <summary> /// Method to handle PreviewKeyDown events /// </summary> /// <param name="sender">Sender object</param> /// <param name="keyEventArgs">Event Text Arguments</param> private void OnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs) { Key pressedKey = keyEventArgs.Key == Key.System ? keyEventArgs.SystemKey : keyEventArgs.Key; switch (pressedKey) { case Key.Space: AddToSecureString(" "); keyEventArgs.Handled = true; break; case Key.Back: case Key.Delete: if (SelectionLength > 0) { RemoveFromSecureString(SelectionStart, SelectionLength); } else if (pressedKey == Key.Delete && CaretIndex < Text.Length) { RemoveFromSecureString(CaretIndex, 1); } else if (pressedKey == Key.Back && CaretIndex > 0) { int caretIndex = CaretIndex; if (CaretIndex > 0 && CaretIndex < Text.Length) caretIndex = caretIndex - 1; RemoveFromSecureString(CaretIndex - 1, 1); CaretIndex = caretIndex; } keyEventArgs.Handled = true; break; } } /// <summary> /// Method to add new text into SecureString and process visual output /// </summary> /// <param name="text">Text to be added</param> private void AddToSecureString(string text) { if (SelectionLength > 0) { RemoveFromSecureString(SelectionStart, SelectionLength); } foreach (char c in text) { int caretIndex = CaretIndex; Password.InsertAt(caretIndex, c); MaskAllDisplayText(); if (caretIndex == Text.Length) { _maskTimer.Stop(); _maskTimer.Start(); Text = Text.Insert(caretIndex++, c.ToString()); } else { Text = Text.Insert(caretIndex++, "*"); } CaretIndex = caretIndex; } } /// <summary> /// Method to remove text from SecureString and process visual output /// </summary> /// <param name="startIndex">Start Position for Remove</param> /// <param name="trimLength">Length of Text to be removed</param> private void RemoveFromSecureString(int startIndex, int trimLength) { int caretIndex = CaretIndex; for (int i = 0; i < trimLength; ++i) { Password.RemoveAt(startIndex); } Text = Text.Remove(startIndex, trimLength); CaretIndex = caretIndex; } private void MaskAllDisplayText() { _maskTimer.Stop(); int caretIndex = CaretIndex; Text = new string('*', Text.Length); CaretIndex = caretIndex; } #endregion } 的新DP,以显示控件正常工作。您不希望在您的实时代码中使用该DP(string),但我希望该示例有助于分析该类是否真正有效:)