diff --git a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs index e4be9da26..e802d3796 100644 --- a/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs +++ b/src/Wpf.Ui/Controls/PasswordBox/PasswordBox.cs @@ -16,8 +16,9 @@ namespace Wpf.Ui.Controls; /// /// The modified password control. /// -public class PasswordBox : Wpf.Ui.Controls.TextBox +public partial class PasswordBox : Wpf.Ui.Controls.TextBox { + private readonly PasswordHelper _passwordHelper; private bool _lockUpdatingContents; /// Identifies the dependency property. @@ -112,6 +113,7 @@ public event RoutedEventHandler PasswordChanged public PasswordBox() { _lockUpdatingContents = false; + _passwordHelper = new PasswordHelper(this); } /// @@ -228,47 +230,11 @@ private void UpdateTextContents(bool isTriggeredByTextInput) } var caretIndex = CaretIndex; - var selectionIndex = SelectionStart; - var currentPassword = Password ?? string.Empty; - var newPasswordValue = currentPassword; + var newPasswordValue = _passwordHelper.GetPassword(); if (isTriggeredByTextInput) { - var currentText = Text; - var newCharacters = currentText.Replace(PasswordChar.ToString(), string.Empty); - - if (currentText.Length < currentPassword.Length) - { - newPasswordValue = currentPassword.Remove( - selectionIndex, - currentPassword.Length - currentText.Length - ); - } - - if (newCharacters.Length > 1) - { - var index = currentText.IndexOf(newCharacters[0]); - - newPasswordValue = - index > newPasswordValue.Length - 1 - ? newPasswordValue + newCharacters - : newPasswordValue.Insert(index, newCharacters); - } - else - { - for (int i = 0; i < currentText.Length; i++) - { - if (currentText[i] == PasswordChar) - { - continue; - } - - newPasswordValue = - currentText.Length == newPasswordValue.Length - ? newPasswordValue.Remove(i, 1).Insert(i, currentText[i].ToString()) - : newPasswordValue.Insert(i, currentText[i].ToString()); - } - } + newPasswordValue = _passwordHelper.GetNewPassword(); } _lockUpdatingContents = true; diff --git a/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs b/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs new file mode 100644 index 000000000..64ed3fa0d --- /dev/null +++ b/src/Wpf.Ui/Controls/PasswordBox/PasswordHelper.cs @@ -0,0 +1,118 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + +using System.Diagnostics; + +// ReSharper disable once CheckNamespace +namespace Wpf.Ui.Controls; + +#pragma warning disable SA1601 + +public partial class PasswordBox +{ + private class PasswordHelper + { + private readonly PasswordBox _passwordBox; + private string _currentText; + private string _newPasswordValue; + private string _currentPassword; + + public PasswordHelper(PasswordBox passwordBox) + { + _passwordBox = passwordBox; + _currentText = string.Empty; + _newPasswordValue = string.Empty; + _currentPassword = string.Empty; + } + + public string GetNewPassword() + { + _currentPassword = GetPassword(); + _newPasswordValue = _currentPassword; + _currentText = _passwordBox.Text; + var selectionIndex = _passwordBox.SelectionStart; + var passwordChar = _passwordBox.PasswordChar; + var newCharacters = _currentText.Replace(passwordChar.ToString(), string.Empty); + bool isDeleted = false; + + if (IsDeleteOption()) + { + _newPasswordValue = _currentPassword.Remove( + selectionIndex, + _currentPassword.Length - _currentText.Length + ); + isDeleted = true; + } + + switch (newCharacters.Length) + { + 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; + } + } + + return _newPasswordValue; + } + + private void UpdatePasswordWithInputCharacter(int insertIndex, string insertValue) + { + Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text"); + + if (_currentText.Length == _newPasswordValue.Length) + { + // If it's a direct character replacement, remove the existing one before inserting the new one. + _newPasswordValue = _newPasswordValue.Remove(insertIndex, 1).Insert(insertIndex, insertValue); + } + else + { + _newPasswordValue = _newPasswordValue.Insert(insertIndex, insertValue); + } + } + + private bool IsDeleteOption() + { + Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text"); + Debug.Assert( + _currentPassword == _passwordBox.Password, + "_currentPassword == _passwordBox.Password" + ); + + return _currentText.Length < _currentPassword.Length; + } + + public string GetPassword() => _passwordBox.Password ?? string.Empty; + } +}