Introduction
The standard WPF controls provided by the .NET 3.5 SP1 framework do not contain a range of controls like numerical text boxes. Very often developers need these text boxes to limit the user input to accept integers or floating point, etc. I developed these controls while working on a hobby project of mine. While at it, I thought it would be nice to extend these controls to beyond numerical data types and in general, parse any text into strongly typed .NET objects. This article describes how to extend the library to target any .NET data type that you want to create, starting from plain text input.
Note: Unless and until mentioned otherwise, we will be referring to input that has to be parsed as a System.Double
object. We will call its text counterpart as floating point representation.
Background
The objective is to get textboxes that accept a text input that can be parsed into a strongly typed .NET object. Now, most people will enter text character by character (possibly press back spaces, or cursor keys and edit/remove previously entered characters or add new characters at any location) and so it is not sufficient to intercept the TextInput
event and check if Double.TryParse
returns true
or false
. That said, we also want to prevent users from entering characters that once entered will never allow the input to become a valid floating point number even if any characters are appended to their input. Also, we will like to notify them that they need to append characters that will make their input valid.
For example, in the following screenshot, the top row shows examples of input that can become valid (bottom row) after some specific characters are appended to the input.
Some input can never be made valid, meaning no character combination can be appended to rectify it. Example, -1.2. -1.. -- 12p 12e-1.
etc. We shall not allow the text box to reach such a state.
Since the main purpose of this article is to demonstrate how to extend this control, let's review its design in some detail. If I were to tell you to write a program to accept a string input character by character and determine if the input you have received so far constitutes a valid floating point number, chances are that you will start thinking in terms of a finite automata. Meaning you will define some states and jump from one state to other depending on some pre-defined (i.e., design time) rules. Some of these states would be final --meaning that if you are on it the input is valid, else it is invalid. While there is nothing wrong with this approach and you can build correct programs using this approach, the code will look a bit messy. Furthermore, if you were to extend this code to accept variations in the float representations (like accepting the thousand separator or the exponential part then you will have to add new states, new jumps, etc.
Now, let's utilize something known as a regular expression. Regular expressions are equivalent to a finite automata --meaning for every finite automata, there exists a regular expression (on the same set of alphabets) and vice versa. So, if we can define a regular expression for a valid floating point pattern, all we need is to find out if the input matches the regular expression. To do so, you will need to again write an automata code and the whole point will be defeated. But, some good people have already written code to match any text against any regular expression and it is available under the System.Text.RegularExpressions
namespace in .NET.
So first and foremost, we need two regular expressions: one for patterns that are valid and one of the patterns for which a character combination exists, that when appended to it shall make it valid. For System.Double
objects, these can be:
^[-+]?(\d+,)*\d+(\.?\d+)?([eE]?[-+]?\d+)?$
^[-+]?((\d+,)*(\d+\.?\d*)?)?([eE]([-+]?\d*)?)?$
respectively. Note that we have deliberately kept the patterns more restrictive than what Double.TryParse
can tolerate. That decision is purely aesthetic. You can make it as lenient as you want, as long as you know how to make System.Double
objects out of it.
Now, we also need to somehow disallow patterns other than the ones discussed above. So, we will handle the OnPreviewTextInput
event of TextBox
class and save the currently inputted text in it. After the text has been entered (i.e., the TextInput
event), we can see if it does not match the above patterns and restore that text. You can prove by induction that we shall never save non conformant inputs by this approach.
To provide visual hints, we declare two properties (auto-properties to be precise):
public Brush InvalidInputBrush { get; set; }
public Brush ValidInputBrush { get; set; }
protected override void OnPreviewTextInput(
System.Windows.Input.TextCompositionEventArgs e)
{
m_oldText = this.Text;
base.OnPreviewTextInput(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (String.IsNullOrEmpty(this.Text))
{
m_value = default(T);
this.Background = this.ValidInputBrush;
}
else if (this.ValidT.IsMatch(this.Text))
{
T t;
if (this.TryParse(this.Text, out t))
m_value = t;
else
this.RestoreOldText();
this.Background = this.ValidInputBrush;
}
else if (this.PotentialT.IsMatch(this.Text))
{
this.Background = this.InvalidInputBrush;
}
else
{
this.RestoreOldText();
}
}
private void RestoreOldText()
{
this.Text = m_oldText;
this.SelectionStart = this.Text.Length;
this.SelectionLength = 0;
}
private bool TryParse(string text_, out double d)
{
return Double.TryParse(text_, out d);
}
Now, to make it extendable, let's put all this code in an abstract class Parsable<T> : TextBox
that exposes the following contract (our brushes above are part of this contract):
public T Value { get; set; }
public bool IsValid { get; private set; }
public Brush InvalidInputBrush { get; set; }
public Brush ValidInputBrush { get; set; }
The abstract
methods are declared below:
protected abstract bool TryParse(string text_, out T t_);
protected abstract string ToText(T t_);
protected abstract Regex PotentialT { get; }
protected abstract Regex ValidT { get; }
You will notice that there is an abstract
method named ToText
. This method is used to set the text when this.Value
is assigned to.
So, to extend this, all you need to do is to implement these methods. Three examples of these are in the attached source code. As an exercise, try with Social Security Numbers. (If you have never designed a class for SSNs and have simply passed them as strings, then this should be another lesson in writing good code. If string objects have a special significance, then they should have a class of their own. This greatly simplifies your code architecture.)
Using the Code
To use this control in a WPF form, do this:
<my:LongIntegerTextBox Grid.Column="1" Grid.Row="1"
HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5,5,5,5"
Width="100" Height="30" InvalidInputBrush="LightPink" ValidInputBrush="White"/>
The property this.Value
will give you the most recent valid input by the user.
History
- 2nd August 2009: Version 1.0 upload