Numeric data binding validation #1

Input validation is an important aspect of form-driven applications, and certain MVVM mechanisms allow us to incorporate easy data validation into our apps – e.g., define a CanExecute condition on any ViewModel command to detect invalid data while being entered, and prevent clicking a Save or Send button as long as some input fields are filled with invalid values. However, most validation techniques (and most articles that can be found on this topic) focus on business logic data validation – for example, a date value needs to be within a certain time span, or two numeric values representing start and end values of some interval need to ensure that the start value is smaller than the end value.

In contrast, today I’d like to cover the technical aspects of data validation: Imagine a numeric value implemented as int property in the ViewModel: How is this value to be represented within the View layer? There exist several types of input controls for numeric values that fall within a distinct range (e.g., a slider control), but if the target property may reach any integer value it will most likely be represented by a simple text box, since this allows the user to freely enter any desired value.

The problem with this approach is that a default text box allows the user to enter any character, not only digits. Even if we would restrict the control to only accept numeric characters, several problems remain, e.g.:

  • The “-” symbol needs to be allowed since it is necessary to tag negative numbers, but it may only occur at the beginning of the number, not inbetween the digits.
  • When extending the example to allow decimal numbers, there are different symbols representing the decimal point, depending on the current input culture.
  • In general, disallowing certain characters from being entered does not result in a good user experience: When pressing a physical button on the keyboard, the user would expect some reaction on the screen – instead, the information is just ignored.

On the WPF platform, the framework helps us avoiding these shortcomings through certain automatic validation checks built into the TextBox control. To check this out, create a bindable ViewModel property of type int in any WPF project, and bind it to a TextBox using the PropertyChanged trigger value to ensure that changes in the text box are instantly forwarded to the ViewModel:

<TextBox Text="{Binding Number, UpdateSourceTrigger=PropertyChanged}"/>

Run the app, and start typing into the text box: As long as you enter digits, these will be passed on to the ViewModel property, but when typing any non-numeric character the text box border color will turn red to indicate invalid input data:

TextBox validation WPF

In addition, WPF supports the global KeepTextBoxDisplaySynchronizedWithTextProperty setting I’ve discussed recently, which allows us to control whether the text box should show the current target property’s numeric value, or the actual input characters.

When testing the same scenario in a Windows Store App (no matter if Windows 8 or 10, WinRT or UWP), you’ll be disappointed: Obviously, numeric characters are forwarded to the ViewModel to populate the target int property while non-numeric symbols are not, but the TextBox does not visualize its current state in any way. Let’s add a TextBlock control and bind it to the to same ViewModel property, to see what’s going on there:

<StackPanel>
	<TextBox Text="{Binding Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" x:Name="Box"/>
	<TextBlock Text="{Binding Number}"/>
</StackPanel>

When running the app and entering digits, we can watch the target property instantly being changed and getting each digit appended on key press. When entering any illegal character (or removing the content completely), the target property keeps its previous value. This means that the ViewModel contains data different from the values displayed to the user – a potentially dangerous situation, just imagine someone hitting the Send button after entering 0.25 into a text box, not knowing that the target property only accepts int values and therefore silently ignores the decimal point, sending the value 25 to some backend service!

To inspect what’s going on behind the scenes, we can register a simple value converter to the binding and register breakpoints on the following two lines:

public class NumberConverter : IValueConverter
{
	public object Convert(object value, Type targetType, object parameter, string language)
	{
		return value.ToString();
	}

	public object ConvertBack(object value, Type targetType, object parameter, string language)
	{
		var input = value as string;
		int output;
		if (int.TryParse(input, out output))
			return output;
		else
			return DependencyProperty.UnsetValue;
	}
}

This illustrates what the .NET framework does automatically behind the scenes when binding a user control containing a string value to any numeric property: The first few lines are simple, for preparing the numeric property to be displayed we only need to convert it to a string value. In the other direction, the text value entered by the user needs to be parsed: if successful, the result is passed on to the target property – if not, there is no valid data to be forwarded to the ViewModel, the target property will keep its previous (valid) value, and the TextBox content visible to the user starts to differ from the actual data stored on the business layer.

What can we do about it? The converter-based approach just seen is handy for demonstration (as we can inspect which values are passed between View and ViewModel), but not applicable to real-life applications, as we can not react to invalid data from within the converter. And even if we could, what if we wanted to register another converter that’s necessary for some business logic conversion?

Follow me to part #2 of this blog post to see how to implement a reusable solution that allows detection of invalid input data and instant user notification!