Last active
January 9, 2018 03:55
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <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