From 8f2641bf8cbabf2aea68db408a5e3aa3bf58def0 Mon Sep 17 00:00:00 2001 From: Israel Soto Date: Wed, 4 May 2022 21:15:13 -0500 Subject: [PATCH 1/4] [Entry/Win] Fixed cursor jumping to the beginning of a PasswordBox * Fixes #5401 --- .../Platform/Windows/MauiPasswordTextBox.cs | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs index 4f2bd3108673..1b5ac5926334 100644 --- a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs +++ b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs @@ -53,9 +53,11 @@ static void OnPasswordPropertyChanged(DependencyObject dependencyObject, Depende bool _cachedSpellCheckSetting; CancellationTokenSource? _cts; bool _internalChangeFlag; + int _cachedCursorPosition; public MauiPasswordTextBox() { + TextChanging += OnNativeTextChanging; TextChanged += OnNativeTextChanged; } @@ -132,6 +134,24 @@ protected override void OnKeyDown(KeyRoutedEventArgs e) base.OnKeyDown(e); } + private void OnNativeTextChanging(TextBox sender, TextBoxTextChangingEventArgs args) + { + if (!IsPassword) + return; + + // As we are obfuscating the text by ourselves, we are setting the Text property directly on code many times. + // This causes that we invoke the SelectionChanged event many times with SelectionStart = 0, + // setting the cursor to the beginning of the TextBox. + // To avoid this behavior let's save the current cursor position of the first time the Text is changing and + // keep the same cursor position after each Text update. + var updatedPassword = DetermineTextFromPassword(Password, SelectionStart, Text); + + if (Password != updatedPassword) + _cachedCursorPosition = SelectionStart; + else + SelectionStart = _cachedCursorPosition; + } + void OnNativeTextChanged(object sender, TextChangedEventArgs textChangedEventArgs) { if (IsPassword) @@ -161,11 +181,7 @@ void UpdateVisibleText() var updatedText = IsPassword ? Obfuscate(Password) : Password; if (Text != updatedText) - { - var savedSelectionStart = SelectionStart; Text = updatedText; - SelectionStart = savedSelectionStart; - } } void UpdateInputScope() @@ -198,27 +214,15 @@ void UpdateInputScope() void ImmediateObfuscation() { - var updatedPassword = DetermineTextFromPassword(Password, SelectionStart, Text); - var updatedVisibleText = Obfuscate(updatedPassword); - - if (Password != updatedPassword) - Password = updatedPassword; - - if (Text != updatedVisibleText) - { - var savedSelectionStart = SelectionStart; - Text = updatedVisibleText; - SelectionStart = savedSelectionStart; - } + UpdatePasswordIfNeeded(); + UpdateVisibleText(); } void DelayObfuscation() { var lengthDifference = Text.Length - Password.Length; - var updatedPassword = DetermineTextFromPassword(Password, SelectionStart, Text); - if (Password != updatedPassword) - Password = updatedPassword; + UpdatePasswordIfNeeded(); // Cancel any pending delayed obfuscation _cts?.Cancel(); @@ -244,11 +248,7 @@ void DelayObfuscation() } if (Text != updatedVisibleText) - { - var savedSelectionStart = SelectionStart; Text = updatedVisibleText; - SelectionStart = savedSelectionStart; - } void StartTimeout(CancellationToken token) { @@ -260,14 +260,20 @@ void StartTimeout(CancellationToken token) DispatcherQueue.TryEnqueue(UI.Dispatching.DispatcherQueuePriority.Normal, () => { - var savedSelectionStart = SelectionStart; - Text = Obfuscate(Password); - SelectionStart = savedSelectionStart; + UpdateVisibleText(); }); }, token); } } + void UpdatePasswordIfNeeded() + { + var updatedPassword = DetermineTextFromPassword(Password, SelectionStart, Text); + + if (Password != updatedPassword) + Password = updatedPassword; + } + static string Obfuscate(string text, bool leaveLastVisible = false) { if (string.IsNullOrEmpty(text)) From 76bfe068e7edcba5701d36de3ba25262f8a86813 Mon Sep 17 00:00:00 2001 From: Israel Soto Date: Thu, 5 May 2022 15:11:07 -0500 Subject: [PATCH 2/4] [Entry/Win] Fixed password being set as plain text for a moment in Entry control * Password is not longer being set as plain text after the text is transformed --- .../Core/Platform/Windows/Extensions/TextBoxExtensions.cs | 6 +++++- src/Core/src/Platform/Windows/MauiPasswordTextBox.cs | 3 +-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs index 2b65c9cc0da0..897bfa7a4a54 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs @@ -1,5 +1,6 @@ #nullable enable using Microsoft.Maui.Controls.Internals; +using Microsoft.Maui.Platform; using Microsoft.UI.Xaml.Controls; namespace Microsoft.Maui.Controls.Platform @@ -8,7 +9,10 @@ internal static class TextBoxExtensions { public static void UpdateText(this TextBox platformControl, InputView inputView) { - platformControl.Text = TextTransformUtilites.GetTransformedText(inputView.Text, inputView.TextTransform); + if (platformControl is MauiPasswordTextBox passwordBox && passwordBox.IsPassword) + passwordBox.Password = TextTransformUtilites.GetTransformedText(inputView.Text, inputView.TextTransform); + else + platformControl.Text = TextTransformUtilites.GetTransformedText(inputView.Text, inputView.TextTransform); } } diff --git a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs index 1b5ac5926334..8692288f5c25 100644 --- a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs +++ b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs @@ -138,7 +138,7 @@ private void OnNativeTextChanging(TextBox sender, TextBoxTextChangingEventArgs a { if (!IsPassword) return; - + // As we are obfuscating the text by ourselves, we are setting the Text property directly on code many times. // This causes that we invoke the SelectionChanged event many times with SelectionStart = 0, // setting the cursor to the beginning of the TextBox. @@ -215,7 +215,6 @@ void UpdateInputScope() void ImmediateObfuscation() { UpdatePasswordIfNeeded(); - UpdateVisibleText(); } void DelayObfuscation() From d6f404884ca04972701fa0c67c9a4a6430d76878 Mon Sep 17 00:00:00 2001 From: Israel Soto Date: Thu, 5 May 2022 15:14:46 -0500 Subject: [PATCH 3/4] [Entry/Win] Fixed the odd behavior too when the Entry has a TextTranform set --- .../Core/Platform/Windows/Extensions/TextBoxExtensions.cs | 4 ++-- src/Core/src/Platform/Windows/MauiPasswordTextBox.cs | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs b/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs index 897bfa7a4a54..e7facd60f3f2 100644 --- a/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs +++ b/src/Controls/src/Core/Platform/Windows/Extensions/TextBoxExtensions.cs @@ -9,8 +9,8 @@ internal static class TextBoxExtensions { public static void UpdateText(this TextBox platformControl, InputView inputView) { - if (platformControl is MauiPasswordTextBox passwordBox && passwordBox.IsPassword) - passwordBox.Password = TextTransformUtilites.GetTransformedText(inputView.Text, inputView.TextTransform); + if (platformControl is MauiPasswordTextBox passwordBox) + passwordBox.Password = TextTransformUtilites.GetTransformedText(inputView.Text, passwordBox.IsPassword ? TextTransform.None : inputView.TextTransform); else platformControl.Text = TextTransformUtilites.GetTransformedText(inputView.Text, inputView.TextTransform); } diff --git a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs index 8692288f5c25..1e6dcd9208e4 100644 --- a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs +++ b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs @@ -135,11 +135,9 @@ protected override void OnKeyDown(KeyRoutedEventArgs e) } private void OnNativeTextChanging(TextBox sender, TextBoxTextChangingEventArgs args) - { - if (!IsPassword) - return; - - // As we are obfuscating the text by ourselves, we are setting the Text property directly on code many times. + { + // As we are obfuscating the text by ourselves or by transforming the text, + // we are setting the Text property directly on code many times. // This causes that we invoke the SelectionChanged event many times with SelectionStart = 0, // setting the cursor to the beginning of the TextBox. // To avoid this behavior let's save the current cursor position of the first time the Text is changing and From e07e1cc3c60f7d823a2efa007ebccf7258b36edf Mon Sep 17 00:00:00 2001 From: Israel Soto Date: Thu, 5 May 2022 16:22:44 -0500 Subject: [PATCH 4/4] [Entry/Win] Fixed odd cursor position behavior when a custom converter is set --- .../Platform/Windows/MauiPasswordTextBox.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs index 1e6dcd9208e4..7322fb3d7c3b 100644 --- a/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs +++ b/src/Core/src/Platform/Windows/MauiPasswordTextBox.cs @@ -54,6 +54,7 @@ static void OnPasswordPropertyChanged(DependencyObject dependencyObject, Depende CancellationTokenSource? _cts; bool _internalChangeFlag; int _cachedCursorPosition; + int _cachedTextLength; public MauiPasswordTextBox() { @@ -136,18 +137,25 @@ protected override void OnKeyDown(KeyRoutedEventArgs e) private void OnNativeTextChanging(TextBox sender, TextBoxTextChangingEventArgs args) { - // As we are obfuscating the text by ourselves or by transforming the text, + // As we are obfuscating the text by ourselves, transforming the text, or a user could be using a custom Converter; // we are setting the Text property directly on code many times. - // This causes that we invoke the SelectionChanged event many times with SelectionStart = 0, - // setting the cursor to the beginning of the TextBox. - // To avoid this behavior let's save the current cursor position of the first time the Text is changing and - // keep the same cursor position after each Text update. + // This causes that we invoke the SelectionChanged event many times with SelectionStart = 0, setting the cursor to + // the beginning of the TextBox. + // To avoid this behavior let's save the current cursor position of the first time the Text is updated by the user + // and keep the same cursor position after each Text update until a new Text update by the user happens. var updatedPassword = DetermineTextFromPassword(Password, SelectionStart, Text); if (Password != updatedPassword) + { _cachedCursorPosition = SelectionStart; + _cachedTextLength = updatedPassword.Length; + } else + { + // Recalculate the cursor position, as the Text could be modified by a user Converter + _cachedCursorPosition += (updatedPassword.Length - _cachedTextLength); SelectionStart = _cachedCursorPosition; + } } void OnNativeTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)