Numeric data binding validation #2

In this blog post, I’d like to propose a solution to the missing data format validation in Store Apps, as discussed in part #1.

In order to build a reusable solution that can be referenced by any TextBox control, we’re going to create a XAML behavior. To do so, include a reference to the Xaml Behaviors SDK to your project: Right click References in the Solution Explorer, choose Windows > Extensions in the Reference Manager window, and select the Behaviors SDK (XAML) entry:

Add a reference to the Behaviors SDK

Our behavior is realized as separate class, let’s call it ValidationBehavior, that implements the IBehavior interface which means that it has two methods that are called when attaching the behavior to a user control (in our case, a TextBox) and when detaching it from the control, and one property that holds a reference to the control it is currently attached to. In addition, to be able to actually add the behavior to a user control in XAML code, it needs to be derived from DependencyObject:

public class ValidationBehavior : DependencyObject, IBehavior
{
	public void Attach(DependencyObject associatedObject)
	{
		//TODO
	}

	public void Detach()
	{
		//TODO
	}

	public DependencyObject AssociatedObject { get; private set; }
}

In XAML, we can now attach the behavior to the TextBox control after importing its local namespace:

<Page x:Class="MyApp.MainPage" ... xmlns:behaviors="using:MyApp.Behaviors">

	<TextBox Text="{Binding Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" x:Name="Box">
		<interactivity:Interaction.Behaviors>
			<behaviors:ValidationBehavior />
		</interactivity:Interaction.Behaviors>
	</TextBox>

Obviously, the behavior won’t do anything yet, but let’s start the app nevertheless and register a breakpoint within the behavior’s Attach method. This way, we can understand when the Attach method is called (when loading the page, since it has been attached in XAML at compile time), and inspect how the associatedObject parameter looks like: Although this parameter is specified as DependencyObject to allow the IBehavior interface to be used for generic behaviors that can be attached to any user control, we can see that in our case it actually represents the TextBox control we attached the behavior to. This information helps us in filling in the content of the Attach method:

public void Attach(DependencyObject associatedObject)
{
	var tb = associatedObject as TextBox;
	if (tb != null)
		tb.TextChanged += TextBoxOnTextChanged;

	AssociatedObject = associatedObject;
}

What does the Attach method do? First of all, cast the passed generic object to the expected TextBox type – this is valid, since our behavior is intended to be used only on text box controls. Next, register an event handler method to the text box’ TextChanged event. Finally, store a reference to the TextBox control within the AssociatedObject property – this is necessary to be able to clean up (unregister the event handler) when the behavior is detached:

public void Detach()
{
	var tb = AssociatedObject as TextBox;
	if (tb != null)
		tb.TextChanged -= TextBoxOnTextChanged;

	AssociatedObject = null;
}

Obviously, to decide whether the text entered in the TextBox is a valid numeric value, we need to inspect its content whenever it changes – therefore, we registered to the TextChanged event in the Attach method. The only thing we haven’t talked about is the content of this event handler method. We use a simple trick: Since the target property is of type int, we try to parse the current text to an int variable, and display an invalid data warning whenever this parsing process fails. Note that the result of the parsing process is never used, hence the variable named dummy. For the first version of our behavior, I decided to simply change the text box’ font color to red on invalid input, and back to black otherwise:

private void TextBoxOnTextChanged(object sender, TextChangedEventArgs args)
{
	var tb = sender as TextBox;
	if (tb != null)
	{
		try
		{
			var dummy = int.Parse(tb.Text);
		}
		catch (Exception)
		{
			tb.Foreground = new SolidColorBrush(Colors.Red);
			return;
		}
		tb.Foreground = new SolidColorBrush(Colors.Black);
	}
}

Run the app, enter a few digits, then non-numeric characters, and watch the TextBox change its color to a warning red:

Valid numeric TextBox content

Invalid numeric TextBox content

Although it works smoothly, this first version of our validation behavior is not yet ready for real-life applications:

  • An empty TextBox also contains invalid data: The bound target property contains a different value (the last valid numeric value) than displayed to the user (an empty string). However, this invalid state is not signalled to the user since there is no text that could be colored in red!
  • The approach of setting color values in the behavior’s code is not the perfect solution in general, as it does not take into account text box styles – what if a developer sets the default color to red, because that fits the app’s color scheme? In this case, an erroneous state could not be perceived by the user because it is also shown using red text color, while text after being corrected would be displayed in black which does not fit the app’s original look and feel.
  • At the moment, the behavior only allows checking for int values, for decimal numbers we’d need to create a separate behavior class that only differs in one line…

Due to these shortcomings, switch to part #3 of this blog post where I’ll suggest a few improvements to the current code base!