65.9K
CodeProject 正在变化。 阅读更多。
Home

带预览的 C# 密码文本框

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (4投票s)

2015 年 6 月 3 日

CPOL

3分钟阅读

viewsIcon

31605

downloadIcon

1034

自定义 TextBox 实现,用于密码输入,在掩码密码字符之前短暂预览

 

引言

这个自定义的 .NET Winform 文本框控件会在用户输入字符后的几毫秒内显示这些字符,然后用“星号”或其他密码字符进行遮蔽。同时允许用户编辑/插入/删除任何字符。

背景

.NET 工具箱提供的 TextBox 控件默认提供了以下选项来实现与密码相关的数据输入字段。

  • PasswordChar
  • UseSystemPasswordChar

然而,这些选项会在用户在文本框中输入字符时立即遮蔽这些字符。基本上,用户所看到的就是一串 '*' 字符,不确定自己即将输入什么。但是,.NET Winforms 中没有内置支持用于输入密码字符,这种支持允许我们以类似于 Android EditText 控件的方式输入任何字符。,在用“星号”或其他密码字符遮蔽之前,先显示用户输入的字符几毫秒。并且允许用户稍后编辑/插入/删除任何字符。

使用代码

希望代码能够自解释。HidePasswordCharacters 方法负责将字符遮蔽为 '*'。每当用户键入时,文本发生变化时,都会调用此方法。

Windows 定时器在这里起着重要的作用。

  1. 在用户键入字符时,在文本框字段中引入一个最小的延迟。
  2. 执行遮蔽用户输入字符的任务。

为了实现以上两个功能,我们初始化定时器

timer = new Timer { Interval = 200 };
timer.Tick += timer_Tick;

为了引入延迟,我们将 Timer 对象的 Interval 属性设置为,例如 200 毫秒。 我们还将 timer_Tick 事件处理程序设置为执行遮蔽操作。每隔 200 毫秒会调用此事件处理程序代码,以便用户看起来字符在一定延迟后被遮蔽(这是我们的要求)。

变量 m_iCaretPosition 保存 TextBox 中任何给定时间点的更新后的光标位置。

变量 adminPassword 保存文本框字段中实际输入的文本。

免责声明:虽然在此示例中,adminPassword 的内容通过属性公开。这是为了简洁起见。在现实中,绝不应该这样做。

using System;
using System.Globalization;
using System.Text;
using System.Windows.Forms;

#region Author and File Information

/*
 *  Title                        :    Custom Password Textbox with preview
 *  Author(s)                    :   Srivatsa Haridas
 *  Version                      :   v1.4
 */
#endregion

namespace PasswordTextbox
{
    /// <summary>
    /// 
    /// </summary>
    public class PasswordTextBox : TextBox
    {
        private readonly Timer timer;
        private char[] adminPassword; 
        private readonly char DecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.ToCharArray()[0];
        private int m_iCaretPosition = 0;
        private bool canEdit = true;
        private const int PWD_LENGTH = 8;

        /// <summary>
        /// 
        /// </summary>
        public PasswordTextBox()
        {
            timer = new Timer { Interval = 250 };
            timer.Tick += timer_Tick;
            adminPassword = new Char[8];
        }

        /// <summary>
        /// 
        /// </summary>
        public string AdminPassword
        {
            get
            {
                return new string(adminPassword).Trim('\0').Replace("\0", "");
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            if (canEdit)
            {
                base.OnTextChanged(e);
                txtInput_TextChanged(this, e);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtInput_TextChanged(object sender, EventArgs e)
        {
            HidePasswordCharacters();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            m_iCaretPosition = this.GetCharIndexFromPosition(e.Location);
        }

        /// <summary>
        /// 
        /// </summary>
        private void HidePasswordCharacters()
        {
            int index = this.SelectionStart;

            if (index > 1)
            {
                StringBuilder s = new StringBuilder(this.Text);
                s[index - 2] = '*';
                this.Text = s.ToString();
                this.SelectionStart = index;
                m_iCaretPosition = index;
            }
            timer.Enabled = true;
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);
            if (e.KeyCode == Keys.Delete)
            {
                canEdit = false;
                DeleteSelectedCharacters(this, e.KeyCode);
            }
        }

        /// <summary>
        /// Windows Timer elapsed eventhandler 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer_Tick(object sender, EventArgs e)
        {
            timer.Enabled = false;
            int index = this.SelectionStart;

            if (index >= 1)
            {
                StringBuilder s = new StringBuilder(this.Text);
                s[index - 1] = '*';
                this.Invoke(new Action(() =>
                {
                    this.Text = s.ToString();
                    this.SelectionStart = index;
                    m_iCaretPosition = index;
                }));
            }
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            base.OnKeyPress(e);

            int selectionStart = this.SelectionStart;
            int length = this.TextLength;
            int selectedChars = this.SelectionLength;
            canEdit = false;

            if (selectedChars == length)
            {
                /*
                 * Means complete text selected so clear it before using it
                 */
                ClearCharBufferPlusTextBox();
            }

            Keys eModified = (Keys)e.KeyChar;

            if (e.KeyChar == DecimalSeparator)
            {
                e.Handled = true;
            }
            if ((Keys.Delete != eModified) && (Keys.Back != eModified))
            {
                if (Keys.Space != eModified)
                {
                    if (e.KeyChar != '-')
                    {
                        if (!char.IsLetterOrDigit(e.KeyChar))
                        {
                            e.Handled = true;
                        }
                        else
                        {
                            if (this.TextLength < PWD_LENGTH)
                            {
                                adminPassword =
                                    new string(adminPassword).Insert(selectionStart, e.KeyChar.ToString()).ToCharArray();
                            }
                        }
                    }
                }
                else
                {
                    if (this.TextLength == 0)
                    {
                        e.Handled = true;
                        Array.Clear(adminPassword, 0, adminPassword.Length);
                    }
                }
            }
            else if ((Keys.Back == eModified) || (Keys.Delete == eModified))
            {
                DeleteSelectedCharacters(this, eModified);
            }

            /*
             * Replace the characters with '*'
             */
            HidePasswordCharacters();

            canEdit = true;
        }

        /// <summary>
        /// Deletes the specific characters in the char array based on the key press action
        /// </summary>
        /// <param name="sender"></param>
        private void DeleteSelectedCharacters(object sender, Keys key)
        {
            int selectionStart = this.SelectionStart;
            int length = this.TextLength;
            int selectedChars = this.SelectionLength;

            if (selectedChars == length)
            {
                ClearCharBufferPlusTextBox();
                return;
            }

            if (selectedChars > 0)
            {
                int i = selectionStart;
                this.Text.Remove(selectionStart, selectedChars);
                adminPassword = new string(adminPassword).Remove(selectionStart, selectedChars).ToCharArray();
            }
            else
            {
                /*
                 * Basically this portion of code is to handle the condition 
                 * when the cursor is placed at the start or in the end 
                 */
                if (selectionStart == 0)
                {
                    /*
                    * Cursor in the beginning, before the first character 
                    * Delete the character only when Del is pressed, No action when Back key is pressed
                    */
                    if (key == Keys.Delete)
                    {
                        adminPassword = new string(adminPassword).Remove(0, 1).ToCharArray();
                    }
                }
                else if (selectionStart > 0 && selectionStart < length)
                {
                    /*
                    * Cursor position anywhere in between 
                    * Backspace and Delete have the same effect
                    */
                    if (key == Keys.Back || key == Keys.Delete)
                    {
                        adminPassword = new string(adminPassword).Remove(selectionStart, 1).ToCharArray();
                    }
                }
                else if (selectionStart == length)
                {
                    /*
                    * Cursor at the end, after the last character 
                    * Delete the character only when Back key is pressed, No action when Delete key is pressed
                    */
                    if (key == Keys.Back)
                    {
                        adminPassword = new string(adminPassword).Remove(selectionStart - 1, 1).ToCharArray();
                    }
                }
            }

            this.Select((selectionStart > this.Text.Length ? this.Text.Length : selectionStart), 0);

        }

        private void ClearCharBufferPlusTextBox()
        {
            Array.Clear(adminPassword, 0, adminPassword.Length);
            this.Clear();
        }
    }
}

编译代码并将其添加到 C# Winform 中进行测试,并报告您发现的任何问题。在提出您宝贵的意见时请友善。我们都在这里学习和改进。

已知问题

期待读者的反馈。

历史

v1.0:这是我工作的一个初始版本。没有特殊处理来过滤特殊字符。这将在未来进行处理。

v1.1:修复了一个错误,即第一个字符没有自动遮蔽。

v1.2:修复了一个错误,即密码字段接受超过 8 个字符。

v1.3:修复了一个错误,即密码字段接受超过 8 个字符。

v1.4:修复了一个错误,即当用户尝试编辑中间的密码字符时,光标位置会移动到末尾。

© . All rights reserved.