Skip to content

Commit f5a504d

Browse files
UpDownBase: Fix event subscription mismatch (#3844)
* Fix event subscription mismatch which also fixes reported issue * Fixes NumericUpDown text update issues Fixes an issue where the NumericUpDown control was not properly updating the text in the TextBox when focus was lost after invalid input. Adds a LostFocus event handler to update the textbox value. Adds UI tests to verify that values are updated as expected. --------- Co-authored-by: Kevin Bost <[email protected]>
1 parent 1ab7331 commit f5a504d

File tree

3 files changed

+96
-12
lines changed

3 files changed

+96
-12
lines changed

src/MaterialDesignThemes.Wpf/UpDownBase.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,10 @@ public override void OnApplyTemplate()
201201
if (_decreaseButton != null)
202202
_decreaseButton.Click -= DecreaseButtonOnClick;
203203
if (_textBoxField != null)
204-
_textBoxField.TextChanged -= OnTextBoxFocusLost;
204+
{
205+
_textBoxField.TextChanged -= OnTextBoxTextChanged;
206+
_textBoxField.LostFocus -= OnTextBoxLostFocus;
207+
}
205208

206209
base.OnApplyTemplate();
207210

@@ -219,22 +222,29 @@ public override void OnApplyTemplate()
219222

220223
if (_textBoxField != null)
221224
{
222-
_textBoxField.LostFocus += OnTextBoxFocusLost;
225+
_textBoxField.TextChanged += OnTextBoxTextChanged;
226+
_textBoxField.LostFocus += OnTextBoxLostFocus;
223227
_textBoxField.Text = Value?.ToString();
224228
}
225229

226230
}
227231

228-
private void OnTextBoxFocusLost(object sender, EventArgs e)
232+
private void OnTextBoxLostFocus(object sender, EventArgs e)
233+
{
234+
if (_textBoxField is { } textBoxField)
235+
{
236+
textBoxField.Text = Value?.ToString();
237+
}
238+
}
239+
240+
private void OnTextBoxTextChanged(object sender, EventArgs e)
229241
{
230242
if (_textBoxField is { } textBoxField)
231243
{
232244
if (TryParse(textBoxField.Text, CultureInfo.CurrentUICulture, out T? value))
233245
{
234246
SetCurrentValue(ValueProperty, ClampValue(value));
235247
}
236-
//NB: Because setting ValueProperty will coerce the value, we re-assign back to the textbox here.
237-
textBoxField.Text = Value?.ToString();
238248
}
239249
}
240250

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,10 @@ public async Task IncreaseButtonClickWhenTextIsAboveMaximum_DoesNotIncreaseValue
187187

188188
[Theory]
189189
[Description("Issue 3781")]
190-
[InlineData("30")]
191-
[InlineData("abc")]
192-
[InlineData("2a")]
193-
public async Task LostFocusWhenTextIsInvalid_RevertsToOriginalValue(string inputText)
190+
[InlineData("30", 2.5)]
191+
[InlineData("abc", 2.5)]
192+
[InlineData("2a", 2)]
193+
public async Task LostFocusWhenTextIsInvalid_RevertsToOriginalValue(string inputText, decimal expectedValue)
194194
{
195195
await using var recorder = new TestRecorder(App);
196196

@@ -209,8 +209,8 @@ public async Task LostFocusWhenTextIsInvalid_RevertsToOriginalValue(string input
209209
await textBox.SendKeyboardInput($"{ModifierKeys.Control}{Key.A}{ModifierKeys.None}{inputText}");
210210
await button.MoveKeyboardFocus();
211211

212-
Assert.Equal("2.5", await textBox.GetText());
213-
Assert.Equal(2.5m, await decimalUpDown.GetValue());
212+
Assert.Equal(expectedValue.ToString(), await textBox.GetText());
213+
Assert.Equal(expectedValue, await decimalUpDown.GetValue());
214214

215215
recorder.Success();
216216
}

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

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
using System.ComponentModel;
1+
using System;
2+
using System.ComponentModel;
3+
using System.Windows.Controls;
4+
using System.Windows.Data;
5+
using Google.Protobuf.WellKnownTypes;
26
using MaterialDesignThemes.UITests.Samples.UpDownControls;
37

48
namespace MaterialDesignThemes.UITests.WPF.UpDownControls;
@@ -215,4 +219,74 @@ public async Task NumericUpDown_WhenValueEqualsMinimum_DisableButtons(int value,
215219

216220
recorder.Success();
217221
}
222+
223+
[Fact]
224+
[Description("Issue 3827")]
225+
public async Task NumericUpDown_WhenBindingUpdateTriggerIsPropertyChanged_ItUpdatesBeforeLoosingFocus()
226+
{
227+
await using var recorder = new TestRecorder(App);
228+
//Arrange
229+
var numericUpDown = await LoadXaml<NumericUpDown>("""
230+
<materialDesign:NumericUpDown Tag="{Binding Value, RelativeSource={RelativeSource Self}, UpdateSourceTrigger=PropertyChanged}" Maximum="10" Minimum="1" />
231+
""");
232+
233+
var textBox = await numericUpDown.GetElement<TextBox>("PART_TextBox");
234+
//Act
235+
await textBox.MoveKeyboardFocus();
236+
await textBox.SendKeyboardInput($"{ModifierKeys.Control}{Key.A}{ModifierKeys.None}4");
237+
238+
//Act
239+
object? tag = await numericUpDown.GetTag();
240+
241+
//Assert
242+
Assert.Equal("4", tag?.ToString());
243+
244+
recorder.Success();
245+
}
246+
247+
[Fact]
248+
[Description("Issue 3827")]
249+
public async Task NumericUpDown_WhenBindingUpdateTriggerIsLostFocus_ItDoesNotUpdateUntilItLoosesFocus()
250+
{
251+
await using var recorder = new TestRecorder(App);
252+
//Arrange
253+
var userControl = await LoadUserControl<BoundNumericUpDown>();
254+
var numericUpDown = await userControl.GetElement<NumericUpDown>();
255+
var buttonToFocus = await userControl.GetElement<Button>("btnToFocus");
256+
await numericUpDown.SetValue(2);
257+
258+
static void SetBindingToLostFocus(NumericUpDown numericUpDown)
259+
{
260+
var binding = new Binding(nameof(NumericUpDown.Value))
261+
{
262+
Path = new(nameof(BoundNumericUpDownViewModel.Value)),
263+
UpdateSourceTrigger = UpdateSourceTrigger.LostFocus
264+
};
265+
BindingOperations.SetBinding(numericUpDown, NumericUpDown.ValueProperty, binding);
266+
}
267+
await numericUpDown.RemoteExecute(SetBindingToLostFocus);
268+
269+
var textBox = await numericUpDown.GetElement<TextBox>("PART_TextBox");
270+
271+
static int GetViewModelValue(NumericUpDown numericUpDown)
272+
{
273+
return ((BoundNumericUpDownViewModel)numericUpDown.DataContext).Value;
274+
}
275+
276+
//Act
277+
await textBox.MoveKeyboardFocus();
278+
await textBox.SendKeyboardInput($"{ModifierKeys.Control}{Key.A}{ModifierKeys.None}4");
279+
280+
//Act
281+
int valueBeforeLostFocus = await numericUpDown.RemoteExecute(GetViewModelValue);
282+
await textBox.SendKeyboardInput($"{Key.Tab}");
283+
int valueAfterLostFocus = await numericUpDown.RemoteExecute(GetViewModelValue);
284+
285+
286+
//Assert
287+
Assert.Equal("2", valueBeforeLostFocus.ToString());
288+
Assert.Equal("4", valueAfterLostFocus.ToString());
289+
290+
recorder.Success();
291+
}
218292
}

0 commit comments

Comments
 (0)