You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
9.6 KiB
339 lines
9.6 KiB
/*************************************************************************************
|
|
|
|
Toolkit for WPF
|
|
|
|
Copyright (C) 2007-2025 Xceed Software Inc.
|
|
|
|
This program is provided to you under the terms of the XCEED SOFTWARE, INC.
|
|
COMMUNITY LICENSE AGREEMENT (for non-commercial use) as published at
|
|
https://github.com/xceedsoftware/wpftoolkit/blob/master/license.md
|
|
|
|
For more features, controls, and fast professional support,
|
|
pick up the Plus Edition at https://xceed.com/xceed-toolkit-plus-for-wpf/
|
|
|
|
Stay informed: follow @datagrid on Twitter or Like http://facebook.com/datagrids
|
|
|
|
***********************************************************************************/
|
|
|
|
using System.Security;
|
|
using System.Windows;
|
|
using System.Linq;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace Xceed.Wpf.Toolkit
|
|
{
|
|
public class WatermarkPasswordBox : WatermarkTextBox
|
|
{
|
|
#region Members
|
|
|
|
private int _newCaretIndex = -1;
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
#region Password
|
|
|
|
public string Password
|
|
{
|
|
[SecuritySafeCritical]
|
|
get
|
|
{
|
|
string passwordString;
|
|
var valuePtr = Marshal.SecureStringToBSTR( this.SecurePassword );
|
|
try
|
|
{
|
|
passwordString = Marshal.PtrToStringUni( valuePtr );
|
|
}
|
|
finally
|
|
{
|
|
Marshal.ZeroFreeBSTR( valuePtr );
|
|
}
|
|
return passwordString;
|
|
}
|
|
set
|
|
{
|
|
if( value == null )
|
|
{
|
|
value = string.Empty;
|
|
}
|
|
this.SecurePassword = new SecureString();
|
|
for( int i = 0; i < value.Length; ++i )
|
|
{
|
|
this.SecurePassword.AppendChar( value[ i ] );
|
|
}
|
|
|
|
// Internal changes to Password property will have a _newCaretIndex > 0.
|
|
this.SyncTextPassword( _newCaretIndex );
|
|
|
|
this.RaiseEvent( new RoutedEventArgs( PasswordChangedEvent, this ) );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region PasswordChar
|
|
|
|
public static readonly DependencyProperty PasswordCharProperty = DependencyProperty.Register( "PasswordChar", typeof( char ), typeof( WatermarkPasswordBox )
|
|
, new UIPropertyMetadata( '\u25CF', OnPasswordCharChanged ) ); //default is black bullet
|
|
|
|
public char PasswordChar
|
|
{
|
|
get
|
|
{
|
|
return ( char )GetValue( PasswordCharProperty );
|
|
}
|
|
|
|
set
|
|
{
|
|
SetValue( PasswordCharProperty, value );
|
|
}
|
|
}
|
|
|
|
private static void OnPasswordCharChanged( DependencyObject o, DependencyPropertyChangedEventArgs e )
|
|
{
|
|
var watermarkPasswordBox = o as WatermarkPasswordBox;
|
|
if( watermarkPasswordBox != null )
|
|
{
|
|
watermarkPasswordBox.OnPasswordCharChanged( ( char )e.OldValue, ( char )e.NewValue );
|
|
}
|
|
}
|
|
|
|
protected virtual void OnPasswordCharChanged( char oldValue, char newValue )
|
|
{
|
|
this.SyncTextPassword( this.CaretIndex );
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region SecurePassword
|
|
|
|
public SecureString SecurePassword
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
#endregion SecurePassword
|
|
|
|
#endregion //Properties
|
|
|
|
#region Constructors
|
|
|
|
public WatermarkPasswordBox()
|
|
{
|
|
this.Password = string.Empty;
|
|
this.IsUndoEnabled = false;
|
|
this.UndoLimit = 0;
|
|
|
|
CommandManager.AddPreviewCanExecuteHandler( this, OnPreviewCanExecuteCommand );
|
|
DataObject.AddPastingHandler( this, OnPaste );
|
|
}
|
|
|
|
#endregion //Constructors
|
|
|
|
#region Base Class Overrides
|
|
|
|
[SecuritySafeCritical]
|
|
protected override void OnPreviewTextInput( TextCompositionEventArgs e )
|
|
{
|
|
// Do not insert \r. When AcceptReturn is true, is it already added in OnPreviewKeyDown().
|
|
if( e.Text != "\r" )
|
|
{
|
|
this.PasswordInsert( e.Text, this.CaretIndex );
|
|
}
|
|
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewTextInput exist
|
|
|
|
base.OnPreviewTextInput( e );
|
|
}
|
|
|
|
[SecuritySafeCritical]
|
|
protected override void OnPreviewKeyDown( KeyEventArgs e )
|
|
{
|
|
// Keys not detected by OnPreviewTextInput
|
|
switch( e.Key )
|
|
{
|
|
case Key.Space:
|
|
this.PasswordInsert( " ", this.CaretIndex );
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewKeyDown exist
|
|
break;
|
|
case Key.Back:
|
|
// With a selection, delete from CaretIndex. Without a selection delete the character before the CaretIndex.
|
|
this.PasswordRemove( ( this.SelectedText.Length > 0 ) ? this.CaretIndex : this.CaretIndex - 1 );
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewKeyDown exists
|
|
break;
|
|
case Key.Delete:
|
|
this.PasswordRemove( this.CaretIndex );
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewKeyDown exist
|
|
break;
|
|
case Key.V:
|
|
if( ( Keyboard.Modifiers & ModifierKeys.Control ) == ModifierKeys.Control )
|
|
{
|
|
if( Clipboard.ContainsText() )
|
|
{
|
|
this.PasswordInsert( Clipboard.GetText(), this.CaretIndex );
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewKeyDown exist
|
|
}
|
|
}
|
|
break;
|
|
case Key.Enter:
|
|
if( this.AcceptsReturn )
|
|
{
|
|
// Add input because it's not added by default.
|
|
this.PasswordInsert( "\r", this.CaretIndex );
|
|
}
|
|
break;
|
|
case Key.Escape:
|
|
e.Handled = true; //Handle to prevent TextChanged when OnPreviewKeyDown exist
|
|
break;
|
|
}
|
|
|
|
base.OnPreviewKeyDown( e );
|
|
}
|
|
|
|
protected override void OnTextChanged( TextChangedEventArgs e )
|
|
{
|
|
base.OnTextChanged( e );
|
|
|
|
if( this.Text.Length != this.Password.Length )
|
|
{
|
|
// When Clear() or Cut() methods are called, we need to update the Password property to empty.
|
|
if( this.Text == "" )
|
|
{
|
|
this.SetPassword( "", 0 );
|
|
}
|
|
// When AppendText() method is called, we need to reset the Text property to prevent adding text.
|
|
else
|
|
{
|
|
this.SyncTextPassword( this.Password.Length );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion //Base Class Overrides
|
|
|
|
#region Event
|
|
|
|
public static readonly RoutedEvent PasswordChangedEvent = EventManager.RegisterRoutedEvent( "PasswordChanged", RoutingStrategy.Bubble, typeof( RoutedEventHandler )
|
|
, typeof( WatermarkPasswordBox ) );
|
|
public event RoutedEventHandler PasswordChanged
|
|
{
|
|
add
|
|
{
|
|
AddHandler( PasswordChangedEvent, value );
|
|
}
|
|
remove
|
|
{
|
|
RemoveHandler( PasswordChangedEvent, value );
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
[SecuritySafeCritical]
|
|
private void OnPaste( object sender, DataObjectPastingEventArgs e )
|
|
{
|
|
//Pasting something that is not text
|
|
if( !e.SourceDataObject.GetDataPresent( DataFormats.UnicodeText, true ) )
|
|
return;
|
|
|
|
var text = e.SourceDataObject.GetData( DataFormats.UnicodeText ) as string;
|
|
if( text != null )
|
|
{
|
|
this.PasswordInsert( text, this.CaretIndex );
|
|
}
|
|
e.CancelCommand(); //Cancel to prevent TextChanged
|
|
}
|
|
|
|
private void OnPreviewCanExecuteCommand( object sender, CanExecuteRoutedEventArgs e )
|
|
{
|
|
//Will not execute these actions
|
|
if( e.Command == ApplicationCommands.Copy ||
|
|
e.Command == ApplicationCommands.Cut ||
|
|
e.Command == ApplicationCommands.Undo )
|
|
{
|
|
e.CanExecute = false;
|
|
e.Handled = true; //Handle to prevent actions
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
[SecurityCritical]
|
|
private void PasswordInsert( string text, int index )
|
|
{
|
|
if( text == null )
|
|
return;
|
|
if( ( index < 0 ) || ( index > this.Password.Length ) )
|
|
return;
|
|
|
|
//If there is a selection, remove it first
|
|
if( this.SelectedText.Length > 0 )
|
|
{
|
|
this.PasswordRemove( index );
|
|
}
|
|
|
|
var newPassword = this.Password;
|
|
for( int i = 0; i < text.Length; ++i )
|
|
{
|
|
// MaxLength == 0 is no limit
|
|
if( ( this.MaxLength == 0 ) || ( newPassword.Length < this.MaxLength ) )
|
|
{
|
|
newPassword = newPassword.Insert( index++, text[ i ].ToString() );
|
|
}
|
|
}
|
|
this.SetPassword( newPassword, index );
|
|
}
|
|
|
|
[SecurityCritical]
|
|
private void PasswordRemove( int index )
|
|
{
|
|
if( ( index < 0 ) || ( index >= this.Password.Length ) )
|
|
return;
|
|
|
|
if( this.SelectedText.Length > 0 )
|
|
{
|
|
var newPassword = this.Password;
|
|
for( int i = 0; i < this.SelectedText.Length; ++i )
|
|
{
|
|
newPassword = newPassword.Remove( index, 1 );
|
|
}
|
|
this.SetPassword( newPassword, index );
|
|
}
|
|
else
|
|
{
|
|
var newPassword = this.Password.Remove( index, 1 );
|
|
this.SetPassword( newPassword, index );
|
|
}
|
|
}
|
|
|
|
private void SetPassword( string password, int caretIndex )
|
|
{
|
|
_newCaretIndex = caretIndex;
|
|
this.Password = password;
|
|
_newCaretIndex = -1;
|
|
}
|
|
|
|
private void SyncTextPassword( int nextCarretIndex )
|
|
{
|
|
var sb = new StringBuilder();
|
|
this.Text = sb.Append( Enumerable.Repeat( this.PasswordChar, this.Password.Length ).ToArray() ).ToString();
|
|
//set CaretIndex after Text is changed
|
|
this.CaretIndex = Math.Max( nextCarretIndex, 0 );
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
|