Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# Password TextBox with preview

0.00/5 (No votes)
3 Jun 2015 2  
Custom implementation of TextBox for password with preview for a short duration before masking the password characters

 

Introduction

The custom .NET Winform textbox control will display the characters entered by the user for a few milliseconds before masking it with an "asterix" or any other password character. And also allowing the user to edit/insert/delete any character

Background

The TextBox control provided by the .NET Toolbox by default provides us the following options to implement data entry fields concerning with passwords.

  • PasswordChar
  • UseSystemPasswordChar

However these options would readily mask the characters entered in the TextBox field as and when the user enters the characters in the textbox.Basically all the user sees is '*' characters being not sure what he is about to type.However there is no in built support in .NET Winforms for entering password characters which would allow us to enter any characters behaving in a way similar to Android EditText control. i.e. To display the character entered by the user for a few milliseconds before masking it with an "asterix" or any other password character. And also allowing the user to edit/insert/delete any character at a later point of time.

Using the code

I hope the code is more or less self-explanatory. The HidePasswordCharacters method does the job of masking the characters into '*'. This method is called each time when there is a change in the text as the user types.

The Windows Timer here has an important role.

  1. To introduce a minimalistic delay in the textbox field when the user keys in some characters.
  2. Perform the job of masking the entered characters by the user.

To achieve the above two functionalities we initialize the timer

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

To introduce the delay we set the Interval property of the Timer object to say 200ms. We also set the timer_Tick event handler to perform the masking operation. This event handler code is called every 200ms so that it appears to the user as though the characters are masked after a certain delay (which is our requirement).

The variable m_iCaretPosition holds the updated caret position inside the TextBox at any given point of time.

The variable adminPassword holds the actual text entered in the textbox field.

Disclaimer : Although in this example the contents of adminPassword is exposed via a Property. This is for the sake of brevity. In reality this should NEVER be the case

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();
        }
    }
}

Compile the code and add it to a C# Winform play around and report any issues you find. Be kind while you pass your much valued critique. We all are here to learn and improvise.

Known issues

Awaiting from the readers.

History

v1.0 : This is just an initial version of my work. There is no special handling to filter out special characters. This shall be for the future.

v1.1 : Fixed a bug, for the first character not being masked automatically

v1.2 : Fixed a bug for password field accepting more than 8 characters

v1.3 : Fixed a bug for password field accepting more than 8 characters

v1.4 : Fixed a bug for caret position advancing to the end whenever the user tries to edit an in between password character.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here