From 528695cabce79c82a5dc0a9158ef191fcda2a9ea Mon Sep 17 00:00:00 2001 From: Kevin Bost Date: Thu, 2 Jan 2025 21:48:43 -0800 Subject: [PATCH] fixes #3654 set IsTabStop in to False in the default NumericUpDown control focus PART_TextBox of NumericUpDown control if it gets focus and enable moving focus with tab (and shift+tab) #3654. Also removed the IsTapStop on the Template because that is not longer needed renamed variable (copy paste mistake) Applying code suggestions 31659b --- src/MaterialDesignThemes.Wpf/UpDownBase.cs | 56 +++++++++++++++++-- .../WPF/UpDownControls/DecimalUpDownTests.cs | 34 ++++++++++- .../WPF/UpDownControls/NumericUpDownTests.cs | 34 ++++++++++- 3 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/MaterialDesignThemes.Wpf/UpDownBase.cs b/src/MaterialDesignThemes.Wpf/UpDownBase.cs index a99a7ccb2c..5e56db6edd 100644 --- a/src/MaterialDesignThemes.Wpf/UpDownBase.cs +++ b/src/MaterialDesignThemes.Wpf/UpDownBase.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.Globalization; namespace MaterialDesignThemes.Wpf; @@ -191,9 +191,7 @@ public override void OnApplyTemplate() if (_textBoxField != null) _textBoxField.TextChanged -= OnTextBoxFocusLost; - _increaseButton = GetTemplateChild(IncreaseButtonPartName) as RepeatButton; - _decreaseButton = GetTemplateChild(DecreaseButtonPartName) as RepeatButton; - _textBoxField = GetTemplateChild(TextBoxPartName) as TextBox; + base.OnApplyTemplate(); if (_increaseButton != null) _increaseButton.Click += IncreaseButtonOnClick; @@ -207,7 +205,6 @@ public override void OnApplyTemplate() _textBoxField.Text = Value?.ToString(); } - base.OnApplyTemplate(); } private void OnTextBoxFocusLost(object sender, EventArgs e) @@ -279,6 +276,55 @@ public class UpDownBase : Control protected RepeatButton? _decreaseButton; protected RepeatButton? _increaseButton; + static UpDownBase() + { + EventManager.RegisterClassHandler(typeof(UpDownBase), GotFocusEvent, new RoutedEventHandler(OnGotFocus)); + } + + // Based on work in MahApps + // https://github.com/MahApps/MahApps.Metro/blob/f7ba30586e9670f07c2f7b6553d129a9e32fc673/src/MahApps.Metro/Controls/NumericUpDown.cs#L966 + private static void OnGotFocus(object sender, RoutedEventArgs e) + { + // When NumericUpDown gets logical focus, select the text inside us. + // If we're an editable NumericUpDown, forward focus to the TextBox element + if (!e.Handled) + { + var numericUpDown = (UpDownBase)sender; + if (numericUpDown.Focusable && e.OriginalSource == numericUpDown) + { + // MoveFocus takes a TraversalRequest as its argument. + var focusDirection = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift) + ? FocusNavigationDirection.Previous + : FocusNavigationDirection.Next; + + var request = new TraversalRequest(focusDirection); + // Gets the element with keyboard focus. + // And change the keyboard focus. + if (Keyboard.FocusedElement is UIElement elementWithFocus) + { + elementWithFocus.MoveFocus(request); + } + else + { + numericUpDown.Focus(); + } + + e.Handled = true; + } + } + } + + public override void OnApplyTemplate() + { + _increaseButton = GetTemplateChild(IncreaseButtonPartName) as RepeatButton; + _decreaseButton = GetTemplateChild(DecreaseButtonPartName) as RepeatButton; + _textBoxField = GetTemplateChild(TextBoxPartName) as TextBox; + + base.OnApplyTemplate(); + } + + public void SelectAll() => _textBoxField?.SelectAll(); + public object? IncreaseContent { get => GetValue(IncreaseContentProperty); diff --git a/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/DecimalUpDownTests.cs b/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/DecimalUpDownTests.cs index d4cad60086..522450e680 100644 --- a/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/DecimalUpDownTests.cs +++ b/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/DecimalUpDownTests.cs @@ -1,4 +1,6 @@ -namespace MaterialDesignThemes.UITests.WPF.UpDownControls; +using System.ComponentModel; + +namespace MaterialDesignThemes.UITests.WPF.UpDownControls; public class DecimalUpDownTests(ITestOutputHelper output) : TestBase(output) { @@ -115,4 +117,34 @@ public async Task MaxAndMinAssignments_CoerceValueToBeInRange() Assert.Equal(3, await numericUpDown.GetMinimum()); Assert.Equal(3, await numericUpDown.GetMaximum()); } + + [Fact] + [Description("Issue 3654")] + public async Task InternalTextBoxIsFocused_WhenGettingKeyboardFocus() + { + await using var recorder = new TestRecorder(App); + + // Arrange + var stackPanel = await LoadXaml(""" + + + + + """); + + var textBox = await stackPanel.GetElement("/TextBox"); + var part_textBox = await stackPanel.GetElement("PART_TextBox"); + + // Act + await textBox.MoveKeyboardFocus(); + await Task.Delay(50); + await textBox.SendInput(new KeyboardInput(Key.Tab)); + await Task.Delay(50); + + // Assert + Assert.False(await textBox.GetIsFocused()); + Assert.True(await part_textBox.GetIsFocused()); + + recorder.Success(); + } } diff --git a/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/NumericUpDownTests.cs b/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/NumericUpDownTests.cs index 1f72221804..c34b5fe662 100644 --- a/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/NumericUpDownTests.cs +++ b/tests/MaterialDesignThemes.UITests/WPF/UpDownControls/NumericUpDownTests.cs @@ -1,4 +1,6 @@ -namespace MaterialDesignThemes.UITests.WPF.UpDownControls; +using System.ComponentModel; + +namespace MaterialDesignThemes.UITests.WPF.UpDownControls; public class NumericUpDownTests(ITestOutputHelper output) : TestBase(output) @@ -116,4 +118,34 @@ public async Task MaxAndMinAssignments_CoerceValueToBeInRange() Assert.Equal(3, await numericUpDown.GetMinimum()); Assert.Equal(3, await numericUpDown.GetMaximum()); } + + [Fact] + [Description("Issue 3654")] + public async Task InternalTextBoxIsFocused_WhenGettingKeyboardFocus() + { + await using var recorder = new TestRecorder(App); + + // Arrange + var stackPanel = await LoadXaml(""" + + + + + """); + + var textBox = await stackPanel.GetElement("/TextBox"); + var part_textBox = await stackPanel.GetElement("PART_TextBox"); + + // Act + await textBox.MoveKeyboardFocus(); + await Task.Delay(50); + await textBox.SendInput(new KeyboardInput(Key.Tab)); + await Task.Delay(50); + + // Assert + Assert.False(await textBox.GetIsFocused()); + Assert.True(await part_textBox.GetIsFocused()); + + recorder.Success(); + } }