WPF 授权码显示密文并支持换行

科技   2024-07-01 08:03   北京  

 WPF 授权码显示密文并支持换行

WPF 授权码显示密文并支持换行

作 者:WPFDevelopersOrg - 驚鏵

  • 框架使用.NET8
  • Visual Studio 2022;

有开发者需要制作一个授权码输入框输入内容后显示密文并且能 Enter 进行换行输入。由于无法使用 PasswordBox 控件本身不支持换行,因为它设计为单行输入控件。

所以最简单的方法是使用 TextBox 并通过自定义逻辑代码来掩盖输入的文本。这样可以实现多行输入,处理文本显示和密码掩码的逻辑。

接下来我们自定义一个控件 MultiLinePasswordBox 继承 TextBox 输入字符会以密码字符(如默认的 )显示。

私有字段如下:
  • passwordBuilder 用于存储密码。
  • previousText 用于存放上一次的文本。
  • isUpdating 用于记录是否正在编辑。
依赖属性如下:
  • PasswordChar:用于显示密码字符的字符,如果未显示则默认为
  • PlainText:控件中原始的未掩盖的文本。
构造函数:
  • 设置 TextWrapping = TextWrapping.Wrap; 支持多行显示。
  • 设置 AcceptsReturn = true 支持按下 Enter 换行。
  • 订阅 TextChanged 事件,文本更改时将输入的文本转换为密码掩码,如果 isUpdating 的标志为 true ,则直接返回,避免重复更新文本。
  • 计算当前输入文本和上一次文本之间的长度差异,如果长度是正数则插入,反之长度差异是负数则删除。
  • 调用 CreateMaskedTextWithLineBreaks 方法创建带有掩码和换行符的文本。
  • 更新控件的文本,保持光标位置不变,并更新PlainText。

1)MultiLinePasswordBox.cs 代码如下:

using System;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;

namespace WpfTextOrPasswordBox
{
    public class MultiLinePasswordBox : TextBox
    {
        private StringBuilder passwordBuilder = new StringBuilder();
        private string previousText = string.Empty;
        private bool isUpdating = false;

        public char PasswordChar
        {
            get { return (char)GetValue(PasswordCharProperty); }
            set { SetValue(PasswordCharProperty, value); }
        }

        public static readonly DependencyProperty PasswordCharProperty =
            DependencyProperty.Register("PasswordChar"typeof(char), typeof(MultiLinePasswordBox), new PropertyMetadata('●'));


        public string PlainText
        {
            get { return (string)GetValue(PlainTextProperty); }
            set { SetValue(PlainTextProperty, value); }
        }

        public static readonly DependencyProperty PlainTextProperty =
            DependencyProperty.Register("PlainText"typeof(string), typeof(MultiLinePasswordBox), new PropertyMetadata(string.Empty));


        public MultiLinePasswordBox()
        {
            AcceptsReturn = true;
            TextWrapping = TextWrapping.Wrap;
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
            TextChanged += PasswordTextBox_TextChanged;
        }

        private void PasswordTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (isUpdating)
                return;

            isUpdating = true;
            var caretIndex = CaretIndex;
            var input = Text;
            if (string.IsNullOrWhiteSpace(Text))
            {
                passwordBuilder.Clear();
            }
            else
            {
                var s = passwordBuilder.Length;
                int lengthDifference = input.Length - previousText.Length;
                if (lengthDifference > 0)
                {
                    var newText = input.Substring(caretIndex - lengthDifference, lengthDifference);
                    passwordBuilder.Insert(caretIndex - lengthDifference, newText);
                }
                else if (lengthDifference < 0)
                {
                    passwordBuilder.Remove(caretIndex, Math.Abs(lengthDifference));
                    if (passwordBuilder[caretIndex - 1].ToString() != input)
                    {
                        var index = passwordBuilder.ToString().IndexOf(passwordBuilder[caretIndex - 1]);
                        passwordBuilder.Replace(passwordBuilder[caretIndex - 1], input.Last());
                    }
                }
                var maskedText = CreateMaskedTextWithLineBreaks(passwordBuilder.ToString());
                TextChanged -= PasswordTextBox_TextChanged;
                Text = maskedText;
                TextChanged += PasswordTextBox_TextChanged;
            }
            previousText = Text;
            CaretIndex = caretIndex;
            PlainText = passwordBuilder.ToString();
            isUpdating = false;
        }

        private string CreateMaskedTextWithLineBreaks(string text)
        {
            var maskedText = new StringBuilder();
            foreach (char c in text)
            {
                if (c == '\r' || c == '\n')
                    maskedText.Append(c);
                else
                    maskedText.Append(PasswordChar.ToString());
            }
            return maskedText.ToString();
        }
    }
}

2)MultiLinePasswordBoxSample.xaml 代码如下:

<wd:Window
    x:Class="WpfTextOrPasswordBox.MultiLinePasswordBoxSample"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfTextOrPasswordBox"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
    Title="MultiLinePasswordBoxSample - WPF开发者"
    Width="800"
    Height="450"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">

    <Grid>
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Margin="0,20">
                <Run Text="明文:" />
                <Run Text="{Binding ElementName=myMultiLinePasswordBox, Path=PlainText}" />
            </TextBlock>
            <local:MultiLinePasswordBox
                x:Name="myMultiLinePasswordBox"
                Width="200"
                Height="60"
                wd:ElementHelper.CornerRadius="3"
                wd:ElementHelper.Watermark="请输入授权码" />

        </StackPanel>
    </Grid>
</wd:Window>

如果你对此有任何更好的想法或建议,我们将非常感激并乐于听取。[1]

参考资料
[1]

如果你对此有任何更好的想法或建议,我们将非常感激并乐于听取。: https://github.com/WPFDevelopersOrg/WPFDevelopers


WPF开发者
「WPF开发者」现役微软MVP,专注 WPF 和 Avalonia 技术分享与传播。
 最新文章