Skip to content

Instantly share code, notes, and snippets.

@jnm2
Last active January 9, 2018 03:55
Show Gist options
  • Save jnm2/7382da316c355cf7231e to your computer and use it in GitHub Desktop.
Save jnm2/7382da316c355cf7231e to your computer and use it in GitHub Desktop.
Hooks into the key messages to a DevExpress TextEdit at a low level and replaces all characters with '*', maintaining a separate SecureString to store the actual password.
/// <summary>
/// Hooks into the key messages at a low level and replaces all characters with '*', maintaining a separate SecureString to store the actual password.
/// </summary>
[UserRepositoryItem("Register")]
public sealed class RepositoryItemPasswordEdit : RepositoryItemTextEdit
{
static RepositoryItemPasswordEdit()
{
Register();
}
public static void Register()
{
EditorRegistrationInfo.Default.Editors.Add(new EditorClassInfo("PasswordEdit",
typeof(PasswordEdit), typeof(RepositoryItemPasswordEdit),
typeof(TextEditViewInfo), new TextEditPainter(), true, null, typeof(TextEditAccessible)));
}
public override string EditorTypeName
{
get { return "PasswordEdit"; }
}
public RepositoryItemPasswordEdit()
{
this.UseSystemPasswordChar = true;
}
[DefaultValue(true)]
public override bool UseSystemPasswordChar
{
get { return base.UseSystemPasswordChar; }
set { base.UseSystemPasswordChar = value; }
}
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), Obsolete("", true)]
new public MaskProperties Mask
{
get { return base.Mask; }
}
}
/// <summary>
/// Hooks into the key messages at a low level and replaces all characters with '*', maintaining a separate SecureString to store the actual password.
/// </summary>
[DefaultBindingProperty("EditValue")]
public sealed class PasswordEdit : TextEdit
{
private static readonly SecureString EmptySecureString;
private SecureString secureString = new SecureString();
public override string EditorTypeName
{
get { return "PasswordEdit"; }
}
static PasswordEdit()
{
EmptySecureString = new SecureString();
EmptySecureString.MakeReadOnly();
RepositoryItemPasswordEdit.Register();
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[DXCategory("Properties")]
new public RepositoryItemPasswordEdit Properties
{
get { return (RepositoryItemPasswordEdit)base.Properties; }
}
protected override DXPopupMenu CreateMenu()
{
var r = base.CreateMenu();
r.Items.RemoveByTag(StringId.TextEditMenuUndo);
r.Items.RemoveByTag(StringId.TextEditMenuCut);
r.Items.RemoveByTag(StringId.TextEditMenuCopy);
r.Items.RemoveByTag(StringId.TextEditMenuPaste);
return r;
}
private SecureString readonlyCopy;
[DisplayName("EditValue (bindable)")]
[Category("Data"), Bindable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
new public SecureString EditValue
{
get
{
if (readonlyCopy == null)
{
if (secureString.Length == 0)
{
readonlyCopy = EmptySecureString;
}
else
{
var copy = secureString.Copy();
copy.MakeReadOnly();
readonlyCopy = copy;
}
}
return readonlyCopy;
}
set
{
if (readonlyCopy == value) return;
if (value == null || value.Length == 0)
{
secureString.Clear();
}
else
{
secureString.Dispose();
secureString = value.Copy();
}
base.Text = new string('*', secureString.Length);
}
}
[Browsable(false), Bindable(false), EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use the EditValue property.", true), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
new public string Text
{
get { return base.Text; }
set { base.Text = value; }
}
protected override TextBoxMaskBox CreateMaskBoxInstance()
{
return new PasswordEditMaskBox(this);
}
private sealed class PasswordEditMaskBox : TextBoxMaskBox
{
private const char DeleteChar = (char)0xFFFF;
public PasswordEditMaskBox(TextEdit ownerEdit) : base(ownerEdit)
{
}
protected override void WndProc(ref Message msg)
{
switch ((WM)msg.Msg)
{
case WM.CHAR:
if ((ModifierKeys & (Keys.Alt | Keys.Control)) == 0)
{
nextKey = (char)(long)msg.WParam;
if (nextKey != '\b') msg.WParam = (IntPtr)'*';
prevSelectionStart = ((TextBox)this).SelectionStart;
prevSelectionLength = ((TextBox)this).SelectionLength;
}
break;
case WM.KEYDOWN:
case WM.KEYUP:
var key = (Keys)msg.WParam;
if (key == Keys.Delete)
{
if ((WM)msg.Msg == WM.KEYDOWN)
goto case WM.CLEAR;
}
else switch (KeyMap.GetCharFromKeyData(key))
{
case '\0':
case '\b':
break;
default:
msg.WParam = (IntPtr)Keys.Multiply;
break;
}
break;
case WM.CLEAR:
nextKey = DeleteChar;
prevSelectionStart = ((TextBox)this).SelectionStart;
prevSelectionLength = ((TextBox)this).SelectionLength;
break;
case WM.CUT:
case WM.COPY:
case WM.PASTE:
case WM.EM_UNDO:
case WM.UNDO:
return;
case WM.EM_CANUNDO:
msg.Result = IntPtr.Zero;
return;
}
base.WndProc(ref msg); // Apply the change in OnTextChanged
}
private char nextKey;
private int prevSelectionStart;
private int prevSelectionLength;
protected override void OnTextChanged(EventArgs e)
{
// Wait till text changes to make sure the keystroke wasn't cancelled
switch (nextKey)
{
case '\0':
break;
case '\b':
{
var secureString = ((PasswordEdit)this.OwnerEdit).secureString;
((PasswordEdit)this.OwnerEdit).InvalidateReadonlyCopy();
if (prevSelectionLength != 0)
{
for (var i = 0; i < prevSelectionLength; i++)
secureString.RemoveAt(prevSelectionStart);
}
else
{
secureString.RemoveAt(prevSelectionStart - 1);
}
break;
}
case (char)0xFFFF: // Delete
{
var secureString = ((PasswordEdit)this.OwnerEdit).secureString;
((PasswordEdit)this.OwnerEdit).InvalidateReadonlyCopy();
if (prevSelectionLength != 0)
{
for (var i = 0; i < prevSelectionLength; i++)
secureString.RemoveAt(prevSelectionStart);
}
else
{
secureString.RemoveAt(prevSelectionStart);
}
break;
}
default:
{
var secureString = ((PasswordEdit)this.OwnerEdit).secureString;
((PasswordEdit)this.OwnerEdit).InvalidateReadonlyCopy();
if (prevSelectionLength != 0)
{
for (var i = 1; i < prevSelectionLength; i++)
secureString.RemoveAt(prevSelectionStart);
secureString.SetAt(prevSelectionStart, nextKey);
}
else
{
secureString.InsertAt(prevSelectionStart, nextKey);
}
break;
}
}
nextKey = (char)0;
base.OnTextChanged(e);
}
}
private void InvalidateReadonlyCopy()
{
if (readonlyCopy == null) return;
readonlyCopy.Dispose();
readonlyCopy = null;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment