// TODO: This is an initial implementation and requires the necessary corrections, tests and adjustments.
/* TextProperty contains asterisks OR raw password if IsPasswordRevealed is set to true
PasswordProperty always contains raw password */
using System.Windows.Controls;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
///
/// The modified password control.
///
public partial class PasswordBox : WPFluent.Controls.TextBox
{
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty IsPasswordRevealedProperty = DependencyProperty.Register(
nameof(IsPasswordRevealed),
typeof(bool),
typeof(PasswordBox),
new PropertyMetadata(false, OnIsPasswordRevealedChanged));
///
/// Identifies the routed event.
///
public static readonly RoutedEvent PasswordChangedEvent = EventManager.RegisterRoutedEvent(
nameof(PasswordChanged),
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(PasswordBox));
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty PasswordCharProperty = DependencyProperty.Register(
nameof(PasswordChar),
typeof(char),
typeof(PasswordBox),
new PropertyMetadata('*', OnPasswordCharChanged));
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
nameof(Password),
typeof(string),
typeof(PasswordBox),
new PropertyMetadata(string.Empty, OnPasswordChanged));
///
/// Identifies the dependency property.
///
public static readonly DependencyProperty RevealButtonEnabledProperty = DependencyProperty.Register(
nameof(RevealButtonEnabled),
typeof(bool),
typeof(PasswordBox),
new PropertyMetadata(true));
private bool _lockUpdatingContents;
private readonly PasswordHelper _passwordHelper;
public PasswordBox()
{
_lockUpdatingContents = false;
_passwordHelper = new PasswordHelper(this);
}
///
/// Event fired from this text box when its inner content has been changed.
///
///
/// It is redirected from inner TextContainer.Changed event.
///
public event RoutedEventHandler PasswordChanged
{
add => AddHandler(PasswordChangedEvent, value);
remove => RemoveHandler(PasswordChangedEvent, value);
}
///
/// Called if the reveal mode is changed in the during the run.
///
private static void OnIsPasswordRevealedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordRevealModeChanged();
}
///
/// Called when is changed.
///
private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordChanged();
}
///
/// Called if the character is changed in the during the run.
///
private static void OnPasswordCharChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordCharChanged();
}
private void UpdateTextContents(bool isTriggeredByTextInput)
{
if(_lockUpdatingContents)
{
return;
}
if(IsPasswordRevealed)
{
if(Password == Text)
{
return;
}
_lockUpdatingContents = true;
if(isTriggeredByTextInput)
{
SetCurrentValue(PasswordProperty, Text);
}
else
{
SetCurrentValue(TextProperty, Password);
CaretIndex = Text.Length;
}
RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
_lockUpdatingContents = false;
return;
}
var caretIndex = CaretIndex;
var newPasswordValue = _passwordHelper.GetPassword();
if(isTriggeredByTextInput)
{
newPasswordValue = _passwordHelper.GetNewPassword();
}
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, new string(PasswordChar, newPasswordValue.Length));
SetCurrentValue(PasswordProperty, newPasswordValue);
CaretIndex = caretIndex;
RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
_lockUpdatingContents = false;
}
///
/// Is called when property is changing.
///
protected virtual void OnPasswordChanged() { UpdateTextContents(false); }
///
/// Is called when property is changing.
///
protected virtual void OnPasswordCharChanged()
{
// If password is currently revealed,
// do not replace displayed text with asterisks
if(IsPasswordRevealed)
{
return;
}
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, new string(PasswordChar, Password.Length));
_lockUpdatingContents = false;
}
protected virtual void OnPasswordRevealModeChanged()
{
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, IsPasswordRevealed ? Password : new string(PasswordChar, Password.Length));
_lockUpdatingContents = false;
}
///
/// Triggered by clicking a button in the control template.
///
/// Additional parameters.
protected override void OnTemplateButtonClick(string? parameter)
{
System.Diagnostics.Debug
.WriteLine(
$"INFO: {typeof(PasswordBox)} button clicked with param: {parameter}",
"WPFluent.PasswordBox");
switch(parameter)
{
case "reveal":
SetCurrentValue(IsPasswordRevealedProperty, !IsPasswordRevealed);
Focus();
CaretIndex = Text.Length;
break;
default:
base.OnTemplateButtonClick(parameter);
break;
}
}
///
protected override void OnTextChanged(TextChangedEventArgs e)
{
UpdateTextContents(true);
if(_lockUpdatingContents)
{
base.OnTextChanged(e);
}
else
{
SetPlaceholderTextVisibility();
RevealClearButton();
}
}
///
/// Gets a value indicating whether the password is revealed.
///
public bool IsPasswordRevealed
{
get => (bool)GetValue(IsPasswordRevealedProperty);
private set => SetValue(IsPasswordRevealedProperty, value);
}
///
/// Gets or sets currently typed text represented by asterisks.
///
public string Password { get => (string)GetValue(PasswordProperty); set => SetValue(PasswordProperty, value); }
///
/// Gets or sets character used to mask the password.
///
public char PasswordChar
{
get => (char)GetValue(PasswordCharProperty);
set => SetValue(PasswordCharProperty, value);
}
///
/// Gets or sets a value indicating whether to display the password reveal button.
///
public bool RevealButtonEnabled
{
get => (bool)GetValue(RevealButtonEnabledProperty);
set => SetValue(RevealButtonEnabledProperty, value);
}
}