如果您在桌面应用程序中使用此类东西是合理的,那么您可以执行以下操作。
我们之前有类似的要求,这就是我所做的。
- 我通过从
TextBox 派生并添加一个SecureString 类型的新DP 来创建一个自定义Passwordbox(与普通PasswordBox 的概念几乎相同)。通过这种方式,我们不会失去任何安全优势,并且可以根据自己的喜好自定义视觉行为。
- 现在我们可以使用
Text 的 Text 作为显示字符串,并将实际密码保存在后端 SecureString DP 中并将其绑定到 VM。
- 我们处理
PreviewTextInput 和PreviewKeyDown 事件来管理控件中的所有文本更改,包括Key.Back、Key.Delete 和烦人的Key.Space(这不是通过PreviewTextInput
iOS 感觉:
为确切的 iOS 行为添加更多注意事项。
- 仅在将新字符添加到“当前字符串的结尾”时才显示最后一个字符(
FlowDirection 独立)
- 编辑现有字符串之间的字符对掩码没有影响。
- 显示的最后一个字符取决于计时器(如果闲置一段时间后变为“*”)
- 在控件中禁用了所有复制粘贴操作。
在检测文本更改时可以很容易地处理前 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),但我希望该示例有助于分析该类是否确实有效:)