From 90582e977734d40d2023537186ebcf79f826291e Mon Sep 17 00:00:00 2001 From: Paul DiPietro Date: Mon, 27 Mar 2017 12:26:56 -0500 Subject: [PATCH] [WinRT/UWP] Enable selection in password entry (#677) * [WinRT/UWP] Enable selection in password entry * Remove unnecessary if --- .../Bugzilla45277.cs | 50 +++++++++++++++ ...rin.Forms.Controls.Issues.Shared.projitems | 1 + Xamarin.Forms.Platform.WinRT/FormsTextBox.cs | 64 ++++++++++++++++--- 3 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla45277.cs diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla45277.cs b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla45277.cs new file mode 100644 index 000000000..6a6baf545 --- /dev/null +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Bugzilla45277.cs @@ -0,0 +1,50 @@ +using Xamarin.Forms.CustomAttributes; +using Xamarin.Forms.Internals; + +#if UITEST +using Xamarin.UITest; +using NUnit.Framework; +#endif + +namespace Xamarin.Forms.Controls.Issues +{ + [Preserve(AllMembers = true)] + [Issue(IssueTracker.Bugzilla, 45277, "[WinRT/UWP] Entry with IsPassword = true does not allow selection of characters", PlatformAffected.WinRT)] + public class Bugzilla45277 : TestContentPage + { + protected override void Init() + { + var passwordTextLabel = new Label + { + Text = "[No Password Text Yet]" + }; + var passwordEntry = new Entry + { + IsPassword = true, + Placeholder = "Enter password" + }; + passwordEntry.Completed += (sender, args) => DisplayAlert("Enter pressed", "OK", "Cancel"); + passwordEntry.TextChanged += (sender, args) => + { + passwordTextLabel.Text = passwordEntry.Text; + }; + + Content = new StackLayout + { + Children = + { + new Label + { + Text = "The below label should allow Paste and Select All commands; however, copying via keyboard should not work." + }, + passwordEntry, + new Entry + { + Placeholder = "Entry for easily testing your paste functionality" + }, + passwordTextLabel + } + }; + } + } +} \ No newline at end of file diff --git a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems index 8608dc72a..4369eee34 100644 --- a/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems +++ b/Xamarin.Forms.Controls.Issues/Xamarin.Forms.Controls.Issues.Shared/Xamarin.Forms.Controls.Issues.Shared.projitems @@ -173,6 +173,7 @@ + diff --git a/Xamarin.Forms.Platform.WinRT/FormsTextBox.cs b/Xamarin.Forms.Platform.WinRT/FormsTextBox.cs index 593c575ae..4301dd0a0 100644 --- a/Xamarin.Forms.Platform.WinRT/FormsTextBox.cs +++ b/Xamarin.Forms.Platform.WinRT/FormsTextBox.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -44,6 +45,7 @@ namespace Xamarin.Forms.Platform.WinRT bool _cachedSpellCheckSetting; CancellationTokenSource _cts; bool _internalChangeFlag; + int _cachedSelectionLength; public FormsTextBox() { @@ -237,19 +239,61 @@ namespace Xamarin.Forms.Platform.WinRT void OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs) { - if (!IsPassword) - { - return; - } + // Cache this value for later use as explained in OnKeyDown below + _cachedSelectionLength = SelectionLength; + } - // Prevent the user from selecting any text in the password box by forcing all selection - // to zero-length at the end of the Text - // This simulates the "do not allow clipboard copy" behavior the PasswordBox control has - if (SelectionLength > 0 || SelectionStart < Text.Length) + // Because the implementation of a password entry is based around inheriting from TextBox (via FormsTextBox), there + // are some inaccuracies in the behavior. OnKeyDown is what needs to be used for a workaround in this case because + // there's no easy way to disable specific keyboard shortcuts in a TextBox, so key presses are being intercepted and + // handled accordingly. + protected override void OnKeyDown(KeyRoutedEventArgs e) + { + if (IsPassword) { - SelectionLength = 0; - SelectionStart = Text.Length; + // The ctrlDown flag is used to track if the Ctrl key is pressed; if it's actively being used and the most recent + // key to trigger OnKeyDown, then treat it as handled. + var ctrlDown = Window.Current.CoreWindow.GetKeyState(VirtualKey.Control).HasFlag(CoreVirtualKeyStates.Down); + + // The shift, tab, and directional (Home/End/PgUp/PgDown included) keys can be used to select text and should otherwise + // be ignored. + if ( + e.Key == VirtualKey.Shift || + e.Key == VirtualKey.Tab || + e.Key == VirtualKey.Left || + e.Key == VirtualKey.Right || + e.Key == VirtualKey.Up || + e.Key == VirtualKey.Down || + e.Key == VirtualKey.Home || + e.Key == VirtualKey.End || + e.Key == VirtualKey.PageUp || + e.Key == VirtualKey.PageDown) + { + base.OnKeyDown(e); + return; + } + // For anything else, continue on (calling base.OnKeyDown) and then if Ctrl is still being pressed, do nothing about it. + // The tricky part here is that the SelectionLength value needs to be cached because in an example where the user entered + // '123' into the field and selects all of it, the moment that any character key is pressed to replace the entire string, + // the SelectionLength is equal to zero, which is not what's desired. Entering a key will thus remove the selected number + // of characters from the Text value. OnKeyDown is fortunately called before OnSelectionChanged which enables this. + else + { + // If the C or X keys (copy/cut) are pressed while Ctrl is active, ignore handing them at all. Undo and Redo (Z/Y) should + // be ignored as well as this emulates the regular behavior of a PasswordBox. + if ((e.Key == VirtualKey.C || e.Key == VirtualKey.X || e.Key == VirtualKey.Z || e.Key == VirtualKey.Y) && ctrlDown) + { + e.Handled = false; + return; + } + + base.OnKeyDown(e); + if (_cachedSelectionLength > 0 && !ctrlDown) + Text = Text.Remove(SelectionStart, _cachedSelectionLength); + } } + else + base.OnKeyDown(e); } void OnTextChanged(object sender, Windows.UI.Xaml.Controls.TextChangedEventArgs textChangedEventArgs)