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.
- To introduce a minimalistic delay in the textbox field when the user keys in some characters.
- 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
#endregion
namespace PasswordTextbox
{
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;
public PasswordTextBox()
{
timer = new Timer { Interval = 250 };
timer.Tick += timer_Tick;
adminPassword = new Char[8];
}
public string AdminPassword
{
get
{
return new string(adminPassword).Trim('\0').Replace("\0", "");
}
}
protected override void OnTextChanged(EventArgs e)
{
if (canEdit)
{
base.OnTextChanged(e);
txtInput_TextChanged(this, e);
}
}
private void txtInput_TextChanged(object sender, EventArgs e)
{
HidePasswordCharacters();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
m_iCaretPosition = this.GetCharIndexFromPosition(e.Location);
}
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);
}
}
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)
{
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);
}
HidePasswordCharacters();
canEdit = true;
}
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
{
if (selectionStart == 0)
{
if (key == Keys.Delete)
{
adminPassword = new string(adminPassword).Remove(0, 1).ToCharArray();
}
}
else if (selectionStart > 0 && selectionStart < length)
{
if (key == Keys.Back || key == Keys.Delete)
{
adminPassword = new string(adminPassword).Remove(selectionStart, 1).ToCharArray();
}
}
else if (selectionStart == length)
{
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.