Skip to content

Commit b702bc8

Browse files
corvinszKeboo
andauthored
fixes #3654 set IsTabStop in to False in the default NumericUpDown control (#3656)
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 Co-authored-by: Kevin Bost <[email protected]>
1 parent d065de9 commit b702bc8

File tree

3 files changed

+117
-7
lines changed

3 files changed

+117
-7
lines changed

src/MaterialDesignThemes.Wpf/UpDownBase.cs

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.ComponentModel;
1+
using System.ComponentModel;
22
using System.Globalization;
33

44
namespace MaterialDesignThemes.Wpf;
@@ -191,9 +191,7 @@ public override void OnApplyTemplate()
191191
if (_textBoxField != null)
192192
_textBoxField.TextChanged -= OnTextBoxFocusLost;
193193

194-
_increaseButton = GetTemplateChild(IncreaseButtonPartName) as RepeatButton;
195-
_decreaseButton = GetTemplateChild(DecreaseButtonPartName) as RepeatButton;
196-
_textBoxField = GetTemplateChild(TextBoxPartName) as TextBox;
194+
base.OnApplyTemplate();
197195

198196
if (_increaseButton != null)
199197
_increaseButton.Click += IncreaseButtonOnClick;
@@ -207,7 +205,6 @@ public override void OnApplyTemplate()
207205
_textBoxField.Text = Value?.ToString();
208206
}
209207

210-
base.OnApplyTemplate();
211208
}
212209

213210
private void OnTextBoxFocusLost(object sender, EventArgs e)
@@ -279,6 +276,55 @@ public class UpDownBase : Control
279276
protected RepeatButton? _decreaseButton;
280277
protected RepeatButton? _increaseButton;
281278

279+
static UpDownBase()
280+
{
281+
EventManager.RegisterClassHandler(typeof(UpDownBase), GotFocusEvent, new RoutedEventHandler(OnGotFocus));
282+
}
283+
284+
// Based on work in MahApps
285+
// https://github.com/MahApps/MahApps.Metro/blob/f7ba30586e9670f07c2f7b6553d129a9e32fc673/src/MahApps.Metro/Controls/NumericUpDown.cs#L966
286+
private static void OnGotFocus(object sender, RoutedEventArgs e)
287+
{
288+
// When NumericUpDown gets logical focus, select the text inside us.
289+
// If we're an editable NumericUpDown, forward focus to the TextBox element
290+
if (!e.Handled)
291+
{
292+
var numericUpDown = (UpDownBase)sender;
293+
if (numericUpDown.Focusable && e.OriginalSource == numericUpDown)
294+
{
295+
// MoveFocus takes a TraversalRequest as its argument.
296+
var focusDirection = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift)
297+
? FocusNavigationDirection.Previous
298+
: FocusNavigationDirection.Next;
299+
300+
var request = new TraversalRequest(focusDirection);
301+
// Gets the element with keyboard focus.
302+
// And change the keyboard focus.
303+
if (Keyboard.FocusedElement is UIElement elementWithFocus)
304+
{
305+
elementWithFocus.MoveFocus(request);
306+
}
307+
else
308+
{
309+
numericUpDown.Focus();
310+
}
311+
312+
e.Handled = true;
313+
}
314+
}
315+
}
316+
317+
public override void OnApplyTemplate()
318+
{
319+
_increaseButton = GetTemplateChild(IncreaseButtonPartName) as RepeatButton;
320+
_decreaseButton = GetTemplateChild(DecreaseButtonPartName) as RepeatButton;
321+
_textBoxField = GetTemplateChild(TextBoxPartName) as TextBox;
322+
323+
base.OnApplyTemplate();
324+
}
325+
326+
public void SelectAll() => _textBoxField?.SelectAll();
327+
282328
public object? IncreaseContent
283329
{
284330
get => GetValue(IncreaseContentProperty);

tests/MaterialDesignThemes.UITests/WPF/UpDownControls/DecimalUpDownTests.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
1+
using System.ComponentModel;
2+
3+
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
24

35
public class DecimalUpDownTests(ITestOutputHelper output) : TestBase(output)
46
{
@@ -115,4 +117,34 @@ public async Task MaxAndMinAssignments_CoerceValueToBeInRange()
115117
Assert.Equal(3, await numericUpDown.GetMinimum());
116118
Assert.Equal(3, await numericUpDown.GetMaximum());
117119
}
120+
121+
[Fact]
122+
[Description("Issue 3654")]
123+
public async Task InternalTextBoxIsFocused_WhenGettingKeyboardFocus()
124+
{
125+
await using var recorder = new TestRecorder(App);
126+
127+
// Arrange
128+
var stackPanel = await LoadXaml<StackPanel>("""
129+
<StackPanel>
130+
<TextBox />
131+
<materialDesign:DecimalUpDown />
132+
</StackPanel>
133+
""");
134+
135+
var textBox = await stackPanel.GetElement<TextBox>("/TextBox");
136+
var part_textBox = await stackPanel.GetElement<TextBox>("PART_TextBox");
137+
138+
// Act
139+
await textBox.MoveKeyboardFocus();
140+
await Task.Delay(50);
141+
await textBox.SendInput(new KeyboardInput(Key.Tab));
142+
await Task.Delay(50);
143+
144+
// Assert
145+
Assert.False(await textBox.GetIsFocused());
146+
Assert.True(await part_textBox.GetIsFocused());
147+
148+
recorder.Success();
149+
}
118150
}

tests/MaterialDesignThemes.UITests/WPF/UpDownControls/NumericUpDownTests.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
1+
using System.ComponentModel;
2+
3+
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
24

35

46
public class NumericUpDownTests(ITestOutputHelper output) : TestBase(output)
@@ -116,4 +118,34 @@ public async Task MaxAndMinAssignments_CoerceValueToBeInRange()
116118
Assert.Equal(3, await numericUpDown.GetMinimum());
117119
Assert.Equal(3, await numericUpDown.GetMaximum());
118120
}
121+
122+
[Fact]
123+
[Description("Issue 3654")]
124+
public async Task InternalTextBoxIsFocused_WhenGettingKeyboardFocus()
125+
{
126+
await using var recorder = new TestRecorder(App);
127+
128+
// Arrange
129+
var stackPanel = await LoadXaml<StackPanel>("""
130+
<StackPanel>
131+
<TextBox />
132+
<materialDesign:NumericUpDown />
133+
</StackPanel>
134+
""");
135+
136+
var textBox = await stackPanel.GetElement<TextBox>("/TextBox");
137+
var part_textBox = await stackPanel.GetElement<TextBox>("PART_TextBox");
138+
139+
// Act
140+
await textBox.MoveKeyboardFocus();
141+
await Task.Delay(50);
142+
await textBox.SendInput(new KeyboardInput(Key.Tab));
143+
await Task.Delay(50);
144+
145+
// Assert
146+
Assert.False(await textBox.GetIsFocused());
147+
Assert.True(await part_textBox.GetIsFocused());
148+
149+
recorder.Success();
150+
}
119151
}

0 commit comments

Comments
 (0)