Click here to Skip to main content
15,892,298 members
Articles / Programming Languages / C#

C# Password TextBox with preview

Rate me:
Please Sign up or sign in to vote.
4.00/5 (4 votes)
3 Jun 2015CPOL3 min read 30.2K   947   11   7
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

C#
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

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Honeywell International
India India
Software Developer, Cinephile, Dromomaniac, Animal lover, Self proclaimed Photographer not necessarily in the same order.

Comments and Discussions

 
QuestionBug Pin
JFalcaoS24-Jul-18 1:21
JFalcaoS24-Jul-18 1:21 
SuggestionAmélioration du code Pin
claudetom019-Jun-15 7:46
claudetom019-Jun-15 7:46 
Bonsoir,
Lors de la suppression caractère par caractère ça ne marchait pas convenablement.
Répétition de code à différents endroits non nécessaires.
Suppression du tableau inutile, car sur la chaine l'ajout ou suppression est possible.

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

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

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

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnTextChanged(EventArgs e)
        {
            if (canEdit)
            {
                base.OnTextChanged(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] = '\u25CF';
                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)
            {
                //si sélection multiple inibition de la touche supp.
                e.Handled = this.SelectionLength > 0 ? true : false;
                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] = '\u25CF';
                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
                            adminPassword = adminPassword.Insert(selectionStart, e.KeyChar.ToString());
                    }
                }
                else
                {
                    if (this.TextLength == 0)
                    {
                        e.Handled = true;
                        adminPassword = "";
                    }
                }
            }
            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;

            int newSelectionStart = -1;
            int newSelectedChars = -1;

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

            if (selectedChars > 0)
            {
                newSelectionStart = selectionStart;
                newSelectedChars = selectedChars;
            }
            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)
                    {
                        newSelectionStart = 0;
                        newSelectedChars = 1;
                    }
                }
                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)
                    {
                        newSelectionStart = selectionStart;
                        newSelectedChars = 1;
                    }
                }
                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)
                    {
                        newSelectionStart = selectionStart - 1;
                        newSelectedChars = 1;
                    }
                }
            }

            //Une seule passe suffit pour la suppression des caractères
            if (newSelectionStart + newSelectedChars > -1)
            {
                adminPassword = adminPassword.Remove(newSelectionStart, newSelectedChars);
                this.Text = this.Text.Remove(selectionStart, selectedChars);
            }

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

        }

        private void ClearCharBufferPlusTextBox()
        {
            adminPassword = "";
            this.Clear();
        }
    }
}

GeneralRe: Amélioration du code Pin
Srivatsa Haridas11-Jun-15 23:48
professionalSrivatsa Haridas11-Jun-15 23:48 
GeneralRe: Amélioration du code Pin
claudetom0112-Jun-15 0:20
claudetom0112-Jun-15 0:20 
QuestionCouple of Bugs Identified Pin
piyush_singh3-Jun-15 21:09
piyush_singh3-Jun-15 21:09 
AnswerRe: Couple of Bugs Identified Pin
Srivatsa Haridas3-Jun-15 22:22
professionalSrivatsa Haridas3-Jun-15 22:22 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.