When WPF first shipped, there was a noticeable lack of certain controls we’ve become used to in Win32 and WinForms: Calendar, DateTimePicker, and NumericUpDown. WPF 4 adds Calendar and DatePicker, but not anything for numeric entry.
For my solution I wanted something that behaved very similarly to the WinForms NumericUpdown control.
Some of the specifications:
- Allows user to set Value, MaxValue, MinValue, Increment, and LargeIncrement.
- Text directly entered is limited to numbers
- Pasted text is not intercepted, but when the control has lost focus it will be validated and reset to the previous value if necessary
- Two buttons, for increment and decrement
- Holding down the buttons with the mouse causes the number to increment continuously
- Up and down increment and decrement by Interval
- Page Up and Page Down increment and decrement by LargeInterval
- This version only supports integers
Creating the control
To begin, create a new WPF project and add a new User Control called NumericEntryControl. This will create a pair of .cs and .xaml files.
In the XAML file, change the <Grid> root element to be a <DockPanel>.
Before we add the controls, let’s add some properties to our user control to hold the values the controls will use. These are dependency properties in order to take advantage of all the WPF goodness like data binding and animation. Let’s also add standard .Net property wrappers.
Creating an incrementing TextBox
Add a TextBox inside the <DockPanel> and bind its text to the value we created in our control:
This will create a TextBox that sizes itself with its parent (a feature I wanted, but is not strictly necessary) and its text will be bound to the Value in our UserControl.
Handling text input
It used to be that you pointed with a mouse and entered text with a keyboard. However, it is common now to enter text with a stylus, gestures, or some future method not invented yet. Thankfully, WPF supports generic text input handling so you don’t have to concern yourself with the specific hardware.
This prevents anything except numbers from being entered, whether via character recognition or keyboard. It does not, however, prevent the user from pasting non-numeric text into the box. We’ll handle that later.
Validating Text input
It’s problematic to validate and correct user input as they are entering it. For example, if you set the MaxValue to 100, then every time you enter 1000, it jumps to 100, it can be jarring. It’s a similar situation with text pasted into the control. What the NumericUpDown control does is handle these sort of situations when the control loses focus.
To prepare for this, when the control gains focus, we need to save the last valid value so we have something to restore to.
When the control loses focus, we need to first verify that it is a number and if so, clip it to the bounds of our MinValue and MaxValue. If anything fails, set it back to the previous value.
Handle arrow keys
Just because WPF can handle text input from a variety of sources in a hardware-agnostic way doesn’t mean we should ignore the particular strengths of the keyboard. Specifically, we should handle the up and down arrows.
IncrementValue() and DecrementValue() are pulled out as their own method because they’re used in the button-handling code as well (see below).
The code so far is a perfectly usable textbox that accepts only numbers and can be incremented using the keyboard. Typically, however, we also need to support the mouse, and for that we need buttons (unless you want to do something exotic like programs like Photoshop and Lightroom do, where text boxes have support for incrementing gestures—that’s another article).
A button you can hold down
Adding buttons to increment once per click is pretty easy, but we really want to be able to hold down the buttons and have the TextBox increment. Let’s start by adding the XAML for the buttons:
Note that the button Width property is bound to its own ActualHeight property, and the Height property is bound to the TextBox’s ActualHeight. This has the effect of keeping the buttons square, the same height as the TextBox. It’s an effect I wanted, but you can easily dispose of it. With these buttons, our control finally takes shape:
How fast should we increment?
Before writing the code to do the incrementing as we hold the button down, it’s worth considering how fast the incrementing should occur. Ideally, we would want it to increment at about the same rate as if we were holding down the up key on the keyboard. The keyboard repeat rate is an operating system value that we can retrieve.
There are actually two values:
The delay rate is how long we should wait before starting the repetition, and is given in multiples of 250ms. Roughly speaking, humans can determine and control actions with lengths of time of about 200ms, so 250ms is a good value to start with. Any shorter and the repetition might start when it was not intended (say, if they just click the button instead of holding it down).
The keyboard speed is the number of times per second we should repeat—sort of. The value can be 0, so because of the way I use it below I want to sure it’s at least 1.
To allow us to hold the button down, we need to override the default mouse handling of a button which is to disable the standard LeftMouseButtonDown/Up messages. Instead, we need to handle the PreviewMouseLeftButtonDown message and its corresponding Up message.
When we handle the down message, we need to set a timer for the keyboard delay value. When we handle the timer’s tick, we need to increment (or decrement) and change the timer’s interval to the repeat speed. This repeat speed is calculated merely by dividing 1000ms (1s) by the rate per second. There may be better ways, but this gets pretty close to the rate experienced by the keyboard on my computer. Finally, when the mouse button is released we need to stop the timer. We also do a final increment, which will cover the case where the user clicks instead of holds.
We also need to capture the mouse in case the user moves it off the button—otherwise the timer will just keep incrementing forever.
Here’s the code:
And voila! A NumericEntryControl that’s basic and easy-to-use for both keyboard and mouse.
This isn’t the last word in numeric entry controls, by any means. There are many ways to accomplish it, and this is one that worked well for me. There are a number of further enhancements you could do (and perhaps should do):
- More validation
- Ensure that MaxValue >= MinValue
- Set focus to the TextBox when the control gains focus (maybe)
- Define strokes to increment and decrement with a stylus
- The user can change the keyboard repeat rate through Control Panel. This program could be modified to listen for updates to this value.
There’s also another, really cool feature that I will add to this in a future post.
Found this article helpful? Want a resource of hundreds of similar, how-to tips? I’ve written a book that covers topics like this and more in C# 4. Check out C# 4.0 How-To!
Check out my latest book, the essential, in-depth guide to performance for all .NET developers:
Writing High-Performance.NET Code, 2nd Edition by Ben Watson. Available for pre-order: