// 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); } }