【问题标题】:Custom Masked Passwordbox in WPFWPF 中的自定义屏蔽密码框
【发布时间】:2013-07-01 15:02:13
【问题描述】:

在 iOS 中,当您在字段中输入密码时,会显示该字段的最后一个字母,但在您输入下一个字符时会被混淆。有没有办法在 WPF 中复制这种行为?

【问题讨论】:

  • 如果您在桌面应用程序上执行此操作,这被认为是不好的做法。考虑一下这样一个事实:人们在手机上看着你的肩膀远不如桌面上的大显示器。因此,除非您打算在 Windows 手机或平板电脑上使用此功能。再想想。
  • @Viv 是有道理的,但不幸的是,我并没有在这种情况下规定要求。
  • @Viv plus,这个应用程序可能会被可能倾向于视障人士的人使用,在这种情况下可能更有意义。
  • 这可以通过 wpftutorial.net/PasswordBox.html 的附加属性来完成,但我怀疑我的优秀 IT 安全官会让它部署

标签: wpf passwords


【解决方案1】:

如果您在桌面应用程序中使用此类东西是合理的,那么您可以执行以下操作。

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

  • 我通过从TextBox 派生并添加一个SecureString 类型的新DP 来创建一个自定义Passwordbox(与普通PasswordBox 的概念几乎相同)。通过这种方式,我们不会失去任何安全优势,并且可以根据自己的喜好自定义视觉行为。
  • 现在我们可以使用 TextText 作为显示字符串,并将实际密码保存在后端 SecureString DP 中并将其绑定到 VM。
  • 我们处理PreviewTextInputPreviewKeyDown 事件来管理控件中的所有文本更改,包括Key.BackKey.Delete 和烦人的Key.Space(这不是通过PreviewTextInput

iOS 感觉:

为确切的 iOS 行为添加更多注意事项。

  1. 仅在将新字符添加到“当前字符串的结尾”时才显示最后一个字符(FlowDirection 独立)
  2. 编辑现有字符串之间的字符对掩码没有影响。
  3. 显示的最后一个字符取决于计时器(如果闲置一段时间后变为“*”)
  4. 在控件中禁用了所有复制粘贴操作。

在检测文本更改时可以很容易地处理前 2 个点,对于最后一个点,我们可以使用 DispatcherTimer 相应地处理显示字符串。

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

/// <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
}

工作示例:

Download Link

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

在这个示例中,我添加了一个 string 类型的新 DP,以显示控件工作正常。您显然不想在您的实时代码中使用该 DP (HiddenText),但我希望该示例有助于分析该类是否确实有效:)

【讨论】:

  • 哇,谢谢 Viv!这正是我正在寻找的东西的类型!感谢您帮助 WPF 新手。
  • 由于某种原因我无法绑定Password-DP。它永远不会更新。 SecureString 是不可绑定的吗?我在我的应用程序中的任何地方都使用 SecureString,直到我必须将它存储到一个文件中。然后我得到临时字符串并立即使用定义的密钥对其进行加密。同样,当我读取值时。所以我认为 SecureString 适合这种设计。但我不能用 Bindings =/ 更新它
猜你喜欢
  • 2015-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-25
  • 1970-01-01
  • 2011-02-01
  • 2017-10-02
  • 2015-10-02
相关资源
最近更新 更多