Skip to content

Commit bd2fbc8

Browse files
Keboocorvinsz
andauthored
Fix num up down coerce to max (#3787)
* added samples for the NumericUpDown control and tried to set up a first test (WIP) * Fixing clamping on NumericUpDown control Updated UI test, but need new XAMLTest before it will run --------- Co-authored-by: Corvin <[email protected]>
1 parent cbf1d1e commit bd2fbc8

File tree

6 files changed

+119
-5
lines changed

6 files changed

+119
-5
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+

2+
#if NET462
3+
namespace System.Diagnostics.CodeAnalysis;
4+
//
5+
// Summary:
6+
// Specifies that when a method returns System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue,
7+
// the parameter will not be null even if the corresponding type allows it.
8+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
9+
internal sealed class NotNullWhenAttribute : Attribute
10+
{
11+
//
12+
// Summary:
13+
// Initializes the attribute with the specified return value condition.
14+
//
15+
// Parameters:
16+
// returnValue:
17+
// The return value condition. If the method returns this value, the associated
18+
// parameter will not be null.
19+
public NotNullWhenAttribute(bool returnValue)
20+
{
21+
ReturnValue = returnValue;
22+
}
23+
24+
//
25+
// Summary:
26+
// Gets the return value condition.
27+
//
28+
// Returns:
29+
// The return value condition. If the method returns this value, the associated
30+
// parameter will not be null.
31+
public bool ReturnValue { get; }
32+
}
33+
#endif

src/MaterialDesignThemes.Wpf/UpDownBase.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
namespace MaterialDesignThemes.Wpf;
55

6+
using System.Diagnostics.CodeAnalysis;
7+
68

79
#if NET8_0_OR_GREATER
810
using System.Numerics;
@@ -20,7 +22,7 @@ public class UpDownBase<T> : UpDownBase
2022
private static T Clamp(T value, T min, T max) => T.Clamp(value, min, max);
2123
private static T Add(T value1, T value2) => value1 + value2;
2224
private static T Subtract(T value1, T value2) => value1 - value2;
23-
private static bool TryParse(string text, IFormatProvider? formatProvider, out T? value)
25+
private static bool TryParse(string text, IFormatProvider? formatProvider, [MaybeNullWhen(false)] out T value)
2426
=> T.TryParse(text, formatProvider, out value);
2527
private static int Compare(T value1, T value2) => value1.CompareTo(value2);
2628
#else
@@ -39,8 +41,9 @@ public class UpDownBase<T, TArithmetic> : UpDownBase
3941
private static T Clamp(T value, T min, T max) => _arithmetic.Max(_arithmetic.Min(value, max), min);
4042
private static T Add(T value1, T value2) => _arithmetic.Add(value1, value2);
4143
private static T Subtract(T value1, T value2) => _arithmetic.Subtract(value1, value2);
42-
private static bool TryParse(string text, IFormatProvider? formatProvider, out T? value)
44+
private static bool TryParse(string text, IFormatProvider? formatProvider, [NotNullWhen(true)] out T value)
4345
=> _arithmetic.TryParse(text, formatProvider, out value);
46+
4447
private static int Compare(T value1, T value2) => _arithmetic.Compare(value1, value2);
4548
#endif
4649

@@ -134,7 +137,7 @@ private static void OnNumericValueChanged(DependencyObject d, DependencyProperty
134137
if (value is T numericValue)
135138
{
136139
var upDownBase = ToUpDownBase(d);
137-
numericValue = Clamp(numericValue, upDownBase.Minimum, upDownBase.Maximum);
140+
numericValue = upDownBase.ClampValue(numericValue);
138141
return numericValue;
139142
}
140143
return value;
@@ -213,7 +216,7 @@ private void OnTextBoxFocusLost(object sender, EventArgs e)
213216
{
214217
if (TryParse(textBoxField.Text, CultureInfo.CurrentUICulture, out T? value))
215218
{
216-
SetCurrentValue(ValueProperty, value);
219+
SetCurrentValue(ValueProperty, ClampValue(value));
217220
}
218221
//NB: Because setting ValueProperty will coerce the value, we re-assign back to the textbox here.
219222
textBoxField.Text = Value?.ToString();
@@ -228,6 +231,9 @@ private void OnTextBoxFocusLost(object sender, EventArgs e)
228231

229232
private void OnDecrease() => SetCurrentValue(ValueProperty, Clamp(Subtract(Value, ValueStep), Minimum, Maximum));
230233

234+
private T ClampValue(T value)
235+
=> Clamp(value, Minimum, Maximum);
236+
231237
protected override void OnPreviewKeyDown(KeyEventArgs e)
232238
{
233239
if (e.Key == Key.Up)
@@ -364,6 +370,6 @@ public interface IArithmetic<T>
364370

365371
T Min(T value1, T value2);
366372

367-
bool TryParse(string text, IFormatProvider? formatProvider, out T? value);
373+
bool TryParse(string text, IFormatProvider? formatProvider, [NotNullWhen(true)] out T value);
368374
}
369375
#endif
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<UserControl x:Class="MaterialDesignThemes.UITests.Samples.UpDownControls.BoundNumericUpDown"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.UpDownControls"
6+
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
7+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
8+
d:DataContext="{d:DesignInstance local:BoundNumericUpDownViewModel,
9+
IsDesignTimeCreatable=False}"
10+
d:DesignHeight="450"
11+
d:DesignWidth="800"
12+
mc:Ignorable="d">
13+
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
14+
<materialDesign:NumericUpDown Value="{Binding Value}" />
15+
<Button x:Name="btnToFocus" Content="Button to focus" />
16+
</StackPanel>
17+
</UserControl>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace MaterialDesignThemes.UITests.Samples.UpDownControls;
2+
3+
/// <summary>
4+
/// Interaction logic for BoundNumericUpDown.xaml
5+
/// </summary>
6+
public partial class BoundNumericUpDown : UserControl
7+
{
8+
public BoundNumericUpDown()
9+
{
10+
ViewModel = new BoundNumericUpDownViewModel();
11+
DataContext = ViewModel;
12+
InitializeComponent();
13+
}
14+
15+
//I expose the ViewModel here so I can access it in the tests
16+
public BoundNumericUpDownViewModel ViewModel { get; }
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using CommunityToolkit.Mvvm.ComponentModel;
2+
3+
namespace MaterialDesignThemes.UITests.Samples.UpDownControls;
4+
5+
//If needed this can be refactored to be generic so all variations of the UpDownControl can use this as a VM
6+
public partial class BoundNumericUpDownViewModel : ObservableObject
7+
{
8+
[ObservableProperty]
9+
private int _value;
10+
}

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel;
2+
using MaterialDesignThemes.UITests.Samples.UpDownControls;
23

34
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
45

@@ -156,4 +157,34 @@ public async Task InternalTextBoxIsFocused_WhenGettingKeyboardFocus()
156157

157158
recorder.Success();
158159
}
160+
161+
[Fact(Skip = "Needs XAMLTest 1.2.3 or later")]
162+
public async Task NumericUpDown_ValueSetGreaterThanMaximum_CoercesToMaximum()
163+
{
164+
await using var recorder = new TestRecorder(App);
165+
166+
//Arrange
167+
var userControl = await LoadUserControl<BoundNumericUpDown>();
168+
var numericUpDown = await userControl.GetElement<NumericUpDown>();
169+
var buttonToFocus = await userControl.GetElement<Button>("btnToFocus");
170+
171+
//Set the current value to the maximum
172+
await numericUpDown.SetMaximum(10);
173+
await numericUpDown.SetValue(10);
174+
175+
//Act
176+
//Input a number greater than the maximum
177+
await numericUpDown.MoveKeyboardFocus();
178+
await numericUpDown.SendKeyboardInput($"99");
179+
180+
await buttonToFocus.MoveKeyboardFocus();
181+
182+
//Assert
183+
//The value and bound property in the VM should be set to the maximum
184+
Assert.Equal(10, await numericUpDown.GetValue());
185+
var viewModel = await userControl.GetProperty<BoundNumericUpDownViewModel>(nameof(BoundNumericUpDown.ViewModel));
186+
Assert.Equal(10, viewModel?.Value);
187+
188+
recorder.Success();
189+
}
159190
}

0 commit comments

Comments
 (0)