Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NumericUpDown: Shouldn't there be a single event for when the user made a change that should be acted upon? #1750

Open
cwenger opened this issue Jun 3, 2023 · 6 comments

Comments

@cwenger
Copy link

cwenger commented Jun 3, 2023

When the user is editing a NumericUpDown, every keystroke raises a ValueChanged event. This is not ideal when it's linked to something that you don't want changed more frequently than necessary. In the past I have dealt with this by handling the KeyUp, LostFocus, and Spinned events like this:

private void TheIntegerUpDown_KeyUp(object sender, KeyEventArgs e)
{
	if (e.Key == Key.Enter || e.Key == Key.Return)
		if (TheIntegerUpDown.Value.HasValue)
			Update(TheIntegerUpDown.Value);
}

private void TheIntegerUpDown_LostFocus(object sender, RoutedEventArgs e)
{
	if (TheIntegerUpDown.Value.HasValue)
		Update(TheIntegerUpDown.Value);
}

private void TheIntegerUpDown_Spinned(object sender, Xceed.Wpf.Toolkit.SpinEventArgs e)
{
	if (TheIntegerUpDown.Value.HasValue && TheIntegerUpDown.Increment.HasValue)
	{
		if (e.Direction == Xceed.Wpf.Toolkit.SpinDirection.Increase)
			Update(TheIntegerUpDown.Value + TheIntegerUpDown.Increment);
		else if (e.Direction == Xceed.Wpf.Toolkit.SpinDirection.Decrease)
			Update(TheIntegerUpDown.Value - TheIntegerUpDown.Increment);
	}
}

This seem like a lot of code to handle something conceptually very simple. I see there is an UpdateValueOnEnterKey property which could simplify this a bit, but when that's set to true, using the up/down spinner buttons no longer raises the ValueChanged event. Furthermore, let's say you needed to look at multiple NumericUpDowns whenever a spinner button on one is used. You would need some mechanism to indicate one particular NumericUpDown's new value, while the others are unchanged.

I can envision a few ways of solving this, if an elegant solution does not exist already:

  • An ActionableValueChanged event (couldn't come up with a better name) that's only raised when the user is typing and hits enter/return, the NumericUpDown loses focus, or a spinner button is pressed.
  • A SpinCompleted event so one can easily get the NumericUpDown's Value after the change has been applied.
@XceedDanP
Copy link
Collaborator

XceedDanP commented Jun 9, 2023 via email

@cwenger
Copy link
Author

cwenger commented Jun 9, 2023

@XceedDanP I'm not using binding.

@XceedBoucherS
Copy link
Collaborator

Hi,

Currently, yes, every typed value in the TextBox of the NumericUpDown raises a ValueChanged event. The same when a spinner button is clicked.

If you set the "UpdateValueOnEnterKey" property to True, then typing values in the TextBox of the NumericUpDown won't raise the ValueChanged event. Also, as you pointed out, clicking the Spinner buttons won't raise a ValueChanged event because we are considered in an edit mode, until a lost focus or Enter key press.

If you want to use the advantage of the "UpdateValueOnEnterKey" property to raise a ValueChanged on Enter key press and lost focus, but also on Spinner button clicks, you could:
-Set the "UpdateValueOnEnterKey" to True
-Add a callback for DoubleUpDown.Spinned and in the callback, call doubleUpDown.CommitInput() to sync the Value and Text property (this will produce a change on the Value property and raise the ValueChange event):

`
<xctk:DoubleUpDown Value="25"
UpdateValueOnEnterKey="True"
ValueChanged="DoubleUpDown_ValueChanged"
Spinned="DoubleUpDown_Spinned"/>
private void DoubleUpDown_ValueChanged( object sender, RoutedPropertyChangedEventArgs e )
{
System.Diagnostics.Debug.WriteLine( " Change " );
}

private void DoubleUpDown_Spinned( object sender, Xceed.Wpf.Toolkit.SpinEventArgs e )
{
  if( sender is DoubleUpDown doubleUpDown )
  {
    doubleUpDown.CommitInput();
  }
}`

Please note that the Spinned event will tell you which NumericUpDown is being spinned(with the sender) and if it's an Increase or a Decrease(with the SpinEventArgs).
The ValueChanged event will tell you which NumericUpDown's Value is being changed(with the sender) and the old and new value(with the RoutedPropertyChangedEventArgs).

@cwenger
Copy link
Author

cwenger commented Aug 7, 2023

This seems like a reasonable solution, but for some reason the Spinned event is not propagating to a ValueChanged event until focus is changed. So if I output e.NewValue in the ValueChanged event handler, it's actually showing the old value. Using essentially the same code as yours:

        private void TheDoubleUpDown_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            System.Diagnostics.Debug.WriteLine($"value changed to {e.NewValue}");
        }

        private void TheDoubleUpDown_Spinned(object sender, Xceed.Wpf.Toolkit.SpinEventArgs e)
        {
            if (sender is DoubleUpDown dud)
                dud.CommitInput();
        }

Note that if you put a breakpoint inside the Spinned event it changes the focus, making the behavior seem correct.

EDIT: Could also be related to the Spinned event being raised before the value is changed instead of after. But for reasons I don't understand, even adding a breakpoint on the very last end brace of the Spinned event handler also gives the desired behavior.

@cwenger
Copy link
Author

cwenger commented Aug 7, 2023

Here is one possible solution, adding a short asynchronous delay inside the Spinned event to allow the value to be updated before CommitInput() is called. But it feels pretty hacky with an arbitrary 10 ms delay.

        private void TheDoubleUpDown_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            System.Diagnostics.Debug.WriteLine($"value changed to {e.NewValue}");
        }

        private async void TheDoubleUpDown_Spinned(object sender, Xceed.Wpf.Toolkit.SpinEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("spinned");
            if (sender is DoubleUpDown dud)
            {
                await Task.Delay(10);
                dud.CommitInput();
            }
        }

@XceedBoucherS
Copy link
Collaborator

Hi,

This is happening because if the Value is "25", on a click of a Spinner, the Spinned event is raised. In the callback, you call
doubleUpDown.CommitInput();
which takes the content of the TextBoxBox ("25") and put it in the "DoubleUpDown.Value" and then a ValueChanged event is raised(with "25"). When this is done, the actual incrementation from the Spinner click is done : in your case, setting the TextBox.Text to "26".

This will be fixed in v4.6.
In the meantime, if you have the source code, you can go in file Xceed.Wpf.Toolkit/Primitives/UpDownBase.cs,
in method : virtual void OnSpin( SpinEventArgs e )
and move the following code at the end of the method:
// Raise the Spinned event to user
EventHandler handler = this.Spinned;
if( handler != null )
{
handler( this, e );
}
This will force an incrementation (in your case Update the TextBox.Text property) on a spinner click and then raise the Spinned event (where your callback will put TextBox.Text in DoubleUpDown.Value, resulting in a ValueChanged event with the new value).

Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants