diff --git a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs
index e802d3796..780ace8a6 100644
--- a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs
+++ b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs
@@ -3,11 +3,6 @@
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.
-// 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
@@ -16,12 +11,14 @@ namespace Wpf.Ui.Controls;
///
/// The modified password control.
///
-public partial class PasswordBox : Wpf.Ui.Controls.TextBox
+public partial class PasswordBox : TextBox
{
private readonly PasswordHelper _passwordHelper;
- private bool _lockUpdatingContents;
+ private bool _isUpdating;
- /// Identifies the dependency property.
+ ///
+ /// Identifies the dependency property.
+ ///
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
nameof(Password),
typeof(string),
@@ -29,7 +26,9 @@ public partial class PasswordBox : Wpf.Ui.Controls.TextBox
new PropertyMetadata(string.Empty, OnPasswordChanged)
);
- /// Identifies the dependency property.
+ ///
+ /// Identifies the dependency property.
+ ///
public static readonly DependencyProperty PasswordCharProperty = DependencyProperty.Register(
nameof(PasswordChar),
typeof(char),
@@ -37,7 +36,9 @@ public partial class PasswordBox : Wpf.Ui.Controls.TextBox
new PropertyMetadata('*', OnPasswordCharChanged)
);
- /// Identifies the dependency property.
+ ///
+ /// Identifies the dependency property.
+ ///
public static readonly DependencyProperty IsPasswordRevealedProperty = DependencyProperty.Register(
nameof(IsPasswordRevealed),
typeof(bool),
@@ -45,7 +46,9 @@ public partial class PasswordBox : Wpf.Ui.Controls.TextBox
new PropertyMetadata(false, OnIsPasswordRevealedChanged)
);
- /// Identifies the dependency property.
+ ///
+ /// Identifies the dependency property.
+ ///
public static readonly DependencyProperty RevealButtonEnabledProperty = DependencyProperty.Register(
nameof(RevealButtonEnabled),
typeof(bool),
@@ -53,7 +56,9 @@ public partial class PasswordBox : Wpf.Ui.Controls.TextBox
new PropertyMetadata(true)
);
- /// Identifies the routed event.
+ ///
+ /// Identifies the routed event.
+ ///
public static readonly RoutedEvent PasswordChangedEvent = EventManager.RegisterRoutedEvent(
nameof(PasswordChanged),
RoutingStrategy.Bubble,
@@ -62,7 +67,15 @@ public partial class PasswordBox : Wpf.Ui.Controls.TextBox
);
///
- /// Gets or sets currently typed text represented by asterisks.
+ /// Initializes a new instance of the class.
+ ///
+ public PasswordBox()
+ {
+ _passwordHelper = new PasswordHelper(this);
+ }
+
+ ///
+ /// Gets or sets the actual password (not asterisks).
///
public string Password
{
@@ -71,7 +84,7 @@ public string Password
}
///
- /// Gets or sets character used to mask the password.
+ /// Gets or sets the character used to mask the password.
///
public char PasswordChar
{
@@ -80,7 +93,7 @@ public char PasswordChar
}
///
- /// Gets a value indicating whether the password is revealed.
+ /// Gets a value indicating whether the password is currently revealed.
///
public bool IsPasswordRevealed
{
@@ -89,7 +102,7 @@ public bool IsPasswordRevealed
}
///
- /// Gets or sets a value indicating whether to display the password reveal button.
+ /// Gets or sets whether the password reveal button is enabled.
///
public bool RevealButtonEnabled
{
@@ -98,120 +111,101 @@ public bool RevealButtonEnabled
}
///
- /// Event fired from this text box when its inner content
- /// has been changed.
+ /// Occurs when the password content changes.
///
- ///
- /// It is redirected from inner TextContainer.Changed event.
- ///
public event RoutedEventHandler PasswordChanged
{
add => AddHandler(PasswordChangedEvent, value);
remove => RemoveHandler(PasswordChangedEvent, value);
}
- public PasswordBox()
- {
- _lockUpdatingContents = false;
- _passwordHelper = new PasswordHelper(this);
- }
-
- ///
+ ///
protected override void OnTextChanged(TextChangedEventArgs e)
{
- UpdateTextContents(true);
+ UpdateTextContents(isTriggeredByTextInput: true);
- if (_lockUpdatingContents)
+ if (!_isUpdating)
{
base.OnTextChanged(e);
- }
- else
- {
SetPlaceholderTextVisibility();
-
RevealClearButton();
}
}
///
- /// Is called when property is changing.
+ /// Called when the property changes.
///
- protected virtual void OnPasswordChanged()
- {
- UpdateTextContents(false);
- }
+ protected virtual void OnPasswordChanged() => UpdateTextContents(isTriggeredByTextInput: false);
///
- /// Is called when property is changing.
+ /// Called when the property changes.
///
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;
+ UpdateWithLock(() => SetCurrentValue(TextProperty, new string(PasswordChar, Password.Length)));
}
- protected virtual void OnPasswordRevealModeChanged()
+ ///
+ /// Called when the property changes.
+ ///
+ protected virtual void OnIsPasswordRevealedChanged()
{
- _lockUpdatingContents = true;
-
- SetCurrentValue(
- TextProperty,
- IsPasswordRevealed ? Password : new string(PasswordChar, Password.Length)
- );
-
- _lockUpdatingContents = false;
+ UpdateWithLock(() => SetCurrentValue(TextProperty, IsPasswordRevealed ? Password : new string(PasswordChar, Password.Length)));
}
- ///
- /// 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}",
- "Wpf.Ui.PasswordBox"
- );
-
- switch (parameter)
+ if (parameter == "reveal")
{
- case "reveal":
- SetCurrentValue(IsPasswordRevealedProperty, !IsPasswordRevealed);
- _ = Focus();
- CaretIndex = Text.Length;
- break;
- default:
- base.OnTemplateButtonClick(parameter);
- break;
+ SetCurrentValue(IsPasswordRevealedProperty, !IsPasswordRevealed);
+ _ = Focus();
+ CaretIndex = Text.Length;
+ }
+ else
+ {
+ base.OnTemplateButtonClick(parameter);
}
}
+ ///
+ /// Updates the text contents based on the current state.
+ ///
+ /// True if triggered by user text input; false if triggered by property change.
private void UpdateTextContents(bool isTriggeredByTextInput)
{
- if (_lockUpdatingContents)
+ if (_isUpdating)
{
return;
}
if (IsPasswordRevealed)
{
- if (Password == Text)
- {
- return;
- }
+ HandleRevealedModeUpdate(isTriggeredByTextInput);
+ return;
+ }
- _lockUpdatingContents = true;
+ HandleHiddenModeUpdate(isTriggeredByTextInput);
+ }
+ ///
+ /// Handles updates when password is in revealed mode.
+ ///
+ /// True if triggered by user text input.
+ private void HandleRevealedModeUpdate(bool isTriggeredByTextInput)
+ {
+ if (Password == Text)
+ {
+ return;
+ }
+
+ UpdateWithLock(() =>
+ {
if (isTriggeredByTextInput)
{
SetCurrentValue(PasswordProperty, Text);
@@ -222,68 +216,80 @@ private void UpdateTextContents(bool isTriggeredByTextInput)
CaretIndex = Text.Length;
}
- RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
-
- _lockUpdatingContents = false;
-
- return;
- }
+ RaisePasswordChangedEvent();
+ });
+ }
+ ///
+ /// Handles updates when password is in hidden mode.
+ ///
+ /// True if triggered by user text input.
+ private void HandleHiddenModeUpdate(bool isTriggeredByTextInput)
+ {
var caretIndex = CaretIndex;
- var newPasswordValue = _passwordHelper.GetPassword();
+ var newPassword = isTriggeredByTextInput ? _passwordHelper.GetNewPassword() : Password;
- if (isTriggeredByTextInput)
+ UpdateWithLock(() =>
{
- newPasswordValue = _passwordHelper.GetNewPassword();
- }
-
- _lockUpdatingContents = true;
-
- SetCurrentValue(TextProperty, new string(PasswordChar, newPasswordValue.Length));
- SetCurrentValue(PasswordProperty, newPasswordValue);
- CaretIndex = caretIndex;
-
- RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
+ SetCurrentValue(TextProperty, new string(PasswordChar, newPassword.Length));
+ SetCurrentValue(PasswordProperty, newPassword);
+ CaretIndex = caretIndex;
+ RaisePasswordChangedEvent();
+ });
+ }
- _lockUpdatingContents = false;
+ ///
+ /// Executes an action while preventing recursive updates.
+ ///
+ /// The action to execute.
+ private void UpdateWithLock(Action updateAction)
+ {
+ _isUpdating = true;
+ updateAction();
+ _isUpdating = false;
}
///
- /// Called when is changed.
+ /// Raises the .
///
- private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ private void RaisePasswordChangedEvent() => RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
+
+ ///
+ /// Handles changes to the dependency property.
+ ///
+ /// The that raised the event.
+ /// The containing the event data.
+ private static void OnPasswordChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
- if (d is not PasswordBox control)
+ if (dependencyObject is PasswordBox passwodBox)
{
- return;
+ passwodBox.OnPasswordChanged();
}
-
- control.OnPasswordChanged();
}
///
- /// Called if the character is changed in the during the run.
+ /// Handles changes to the dependency property.
///
- private static void OnPasswordCharChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ /// The instance where the change occurred.
+ /// Event data that contains information about the property change.
+ private static void OnPasswordCharChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
- if (d is not PasswordBox control)
+ if (dependencyObject is PasswordBox passwodBox)
{
- return;
+ passwodBox.OnPasswordCharChanged();
}
-
- control.OnPasswordCharChanged();
}
///
- /// Called if the reveal mode is changed in the during the run.
+ /// Handles changes to the dependency property.
///
- private static void OnIsPasswordRevealedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ /// The instance where the property changed.
+ /// The containing the old and new values.
+ private static void OnIsPasswordRevealedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
- if (d is not PasswordBox control)
+ if (dependencyObject is PasswordBox passwodBox)
{
- return;
+ passwodBox.OnIsPasswordRevealedChanged();
}
-
- control.OnPasswordRevealModeChanged();
}
-}
+}
\ No newline at end of file
diff --git a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.xaml b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.xaml
index 5e4b52517..b4a266e27 100644
--- a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.xaml
+++ b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.xaml
@@ -14,6 +14,21 @@
xmlns:controls="clr-namespace:Wpf.Ui.Controls"
xmlns:system="clr-namespace:System;assembly=mscorlib">
+
+
+
+
+
+
1,1,1,1
0,0,0,1
10,8,0,0
@@ -205,11 +220,8 @@
CommandParameter="clear"
Cursor="Arrow"
Foreground="{DynamicResource TextControlButtonForeground}"
- IsTabStop="False">
-
-
-
-
+ Icon="{StaticResource PasswordBoxClearIcon}"
+ IsTabStop="False" />
-
-
-
-
+ Icon="{StaticResource PasswordBoxRevealIcon}"
+ IsTabStop="False" />
-
-
-
-
-
+
+
diff --git a/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs b/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs
index 64ed3fa0d..87340821a 100644
--- a/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs
+++ b/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs
@@ -12,107 +12,116 @@ namespace Wpf.Ui.Controls;
public partial class PasswordBox
{
+ ///
+ /// Helper class for managing password operations in .
+ ///
private class PasswordHelper
{
private readonly PasswordBox _passwordBox;
private string _currentText;
- private string _newPasswordValue;
+ private string _newPassword;
private string _currentPassword;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The parent control.
public PasswordHelper(PasswordBox passwordBox)
{
_passwordBox = passwordBox;
_currentText = string.Empty;
- _newPasswordValue = string.Empty;
+ _newPassword = string.Empty;
_currentPassword = string.Empty;
}
+ ///
+ /// Calculates and returns the new password value based on current input.
+ ///
+ /// The updated password string.
+ ///
+ /// Handles three scenarios:
+ /// 1. When text is being deleted
+ /// 2. When password is revealed (plain text mode)
+ /// 3. When password is hidden (masked character mode)
+ ///
public string GetNewPassword()
{
- _currentPassword = GetPassword();
- _newPasswordValue = _currentPassword;
+ _currentPassword = _passwordBox.Password;
+ _newPassword = _currentPassword;
_currentText = _passwordBox.Text;
- var selectionIndex = _passwordBox.SelectionStart;
- var passwordChar = _passwordBox.PasswordChar;
- var newCharacters = _currentText.Replace(passwordChar.ToString(), string.Empty);
- bool isDeleted = false;
+ int selectionIndex = _passwordBox.SelectionStart;
- if (IsDeleteOption())
+ if (IsDeletingText())
{
- _newPasswordValue = _currentPassword.Remove(
- selectionIndex,
- _currentPassword.Length - _currentText.Length
- );
- isDeleted = true;
+ int charsToRemove = _currentPassword.Length - _currentText.Length;
+ _newPassword = _currentPassword.Remove(selectionIndex, charsToRemove);
+ return _newPassword;
}
- switch (newCharacters.Length)
+ if (_passwordBox.IsPasswordRevealed)
{
- case > 1:
- {
- var index = _currentText.IndexOf(newCharacters[0]);
-
- _newPasswordValue =
- index > _newPasswordValue.Length - 1
- ? _newPasswordValue + newCharacters
- : _newPasswordValue.Insert(index, newCharacters);
- break;
- }
-
- case 1:
- {
- for (int i = 0; i < _currentText.Length; i++)
- {
- if (_currentText[i] == passwordChar)
- {
- continue;
- }
-
- UpdatePasswordWithInputCharacter(i, _currentText[i].ToString());
- break;
- }
-
- break;
- }
-
- case 0 when !isDeleted:
- {
- // The input is a PasswordChar, which is to be inserted at the designated position.
- int insertIndex = selectionIndex - 1;
- UpdatePasswordWithInputCharacter(insertIndex, passwordChar.ToString());
- break;
- }
+ _newPassword = _currentText;
+ return _newPassword;
}
- return _newPasswordValue;
+ return HandleHiddenModeChanges(selectionIndex);
}
- private void UpdatePasswordWithInputCharacter(int insertIndex, string insertValue)
+ ///
+ /// Handles password changes when in hidden (masked) mode.
+ ///
+ /// Current caret position in the text box.
+ /// The updated password string.
+ ///
+ /// Manages three cases:
+ /// 1. Characters were inserted
+ /// 2. Character was replaced (overwrite)
+ /// 3. Characters were removed
+ ///
+ private string HandleHiddenModeChanges(int selectionIndex)
{
- Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text");
+ char passwordChar = _passwordBox.PasswordChar;
+ int currentLength = _currentPassword.Length;
- if (_currentText.Length == _newPasswordValue.Length)
+ if (_currentText.Length > currentLength)
{
- // If it's a direct character replacement, remove the existing one before inserting the new one.
- _newPasswordValue = _newPasswordValue.Remove(insertIndex, 1).Insert(insertIndex, insertValue);
+ // Characters were inserted
+ int insertedCount = _currentText.Length - currentLength;
+ string insertedText = _currentText.Substring(selectionIndex - insertedCount, insertedCount);
+ _newPassword = _currentPassword.Insert(selectionIndex - insertedCount, insertedText);
}
- else
+ else if (_currentText.Length == currentLength)
{
- _newPasswordValue = _newPasswordValue.Insert(insertIndex, insertValue);
+ // Character was replaced (overwrite)
+ for (int i = 0; i < _currentText.Length; i++)
+ {
+ if (_currentText[i] != passwordChar && i < _newPassword.Length)
+ {
+ _newPassword = _newPassword.Remove(i, 1).Insert(i, _currentText[i].ToString());
+ break;
+ }
+ }
+ }
+ else if (_currentText.Length < currentLength)
+ {
+ // Characters were removed (fallback)
+ int removedCount = currentLength - _currentText.Length;
+ _newPassword = _currentPassword.Remove(selectionIndex, removedCount);
}
+
+ return _newPassword;
}
- private bool IsDeleteOption()
+ ///
+ /// Determines if the current operation is deleting text.
+ ///
+ /// True if text is being deleted; otherwise, false.
+ private bool IsDeletingText()
{
- Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text");
- Debug.Assert(
- _currentPassword == _passwordBox.Password,
- "_currentPassword == _passwordBox.Password"
- );
+ Debug.Assert(_currentText == _passwordBox.Text, "Text mismatch");
+ Debug.Assert(_currentPassword == _passwordBox.Password, "Password mismatch");
return _currentText.Length < _currentPassword.Length;
}
-
- public string GetPassword() => _passwordBox.Password ?? string.Empty;
}
}