This article is an introduction to input validation in Windows Store apps. That sounds ambitious. On one hand, some apps really need decent data entry validation. On the other hand, the Store app runtime does not come with Data Annotations, and it does not come with the IDataErrorInfo and INotifyDataErrorInfo interfaces. In an ideal MVVM XAML world we would just define data constraints in the ViewModel, and then let the binding mechanism automatically update the Views. Controls would display their ErrorTemplate and an informative localized message as long as their respective value is invalid. The current Store runtime supports almost nothing of this out of the box, so let's expand the box.
The View
Let's first test drive some existing controls that allow validation in the View layer. Filip Skakun's WinRT XAML Toolkit on CodePlex has a couple of controls and extensions that faciliate input validation:
- The WatermarkTextBox is a TextBox control with a watermark. OK, it doesn't really validate, but you can use the watermark text to provide guidance to the user.
- TextBoxValidationExtensions is a set of extensions that allow to specify the format of the requested text input as well as brushes to use to highlight a TextBox with valid or invalid Text.
- The NumericUpDown allows you to display and manipulate a numeric value through direct text input, buttons, or cool swipe manipulations. It derives from RangeBase (like the Slider) so it’s impossible to enter an illegal value.
Here's how these controls look like in full action (for the moment, just ignore the validation messages at the bottom):
If -just like me- you're reluctant to link and deploy giant dll’s with your app, then I have good news. You can easily pluck the relevant classes right out of the source code, and embed these in your app. That's exactly what I did:
I do the same with other frameworks and toolkits. After all, if your app only uses RelayCommand and EventAggregator, you're not going to link to the entire MVVMLight *and* Caliburn.Micro, are you ? I know there are pros and cons to this approach, but you get good code samples to learn from, you can tweak the code to better suit your app’s need, and you have the full source code in the project. To a lot of customers, dependencies to large foreign dll’s are a real showstopper.
Let's come back to the topic. Since the WaterMarkTextBox inherits from TextBox, the ValidationExtensions also apply to it. Here’s an example of the ‘Age’ textbox from the sample app. It’s mandatory, numeric, and it comes with an explanatory watermark. You can’t specify a whole ErrorTemplate (yet ?), but you can specify background colors for the valid and invalid states. That's a lot better than the TextBox that ships with the runtime:
<ctl:WatermarkTextBox Text="{Binding Age, Mode=TwoWay}"
WatermarkText="Enter age here. Required numeric field."
Extensions:TextBoxValidationExtensions.Format="NonEmptyNumeric"
Extensions:TextBoxValidationExtensions.InvalidBrush="#FFCC99" />
My favorite input control of the WinRT Xaml Toolkit is the NumericUpDown. It allows you to enter a value within a range. You can type directly in a TextBox, or press the up and down buttons. But you can also change the value through manipulation: tap on the textbox (or click left mouse button) and slide/drag upward to increase, or downward to decrease. That's a very intuitive and addictive gesture! The control also has a built-in value bar (like in Excel).
Here’s how the XAML looks like:
<ctl:NumericUpDown Value="0"
Minimum="0"
Maximum="15"
SmallChange="1"
ValueFormat="F0"
ValueBarVisibility="Collapsed" />
The ViewModel
That went easy, right? Let’s switch to second gear, and define data constraints in the ViewModel. For that, we need to resurrect ye olde IDataErrorInfo:
namespace System.ComponentModel
{
public interface IDataErrorInfo
{
string Error { get; }
string this[string columnName] { get; }
}
}
I implemented the interface in the ViewModel in a classic straightforward way: the Error property is populated by concatenating the error messages from the property iterator:
public string Error
{
get
{
/// Note from the author: there's an opportunity for refactoring here. I know. Really.
string error = string.Empty;
string property = this["FirstName"];
if (!string.IsNullOrEmpty(property))
{
error += property + Environment.NewLine;
}
property = this["LastName"];
if (!string.IsNullOrEmpty(property))
{
error += property + Environment.NewLine;
}
property = this["Age"];
if (!string.IsNullOrEmpty(property))
{
error += property + Environment.NewLine;
}
return error.Trim();
}
}
The iterator allows for the appropriate validation messages:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "FirstName":
if (string.IsNullOrEmpty(firstName))
{
return "First Name is required.";
}
return string.Empty;
case "LastName":
if (string.IsNullOrEmpty(lastName))
{
return "Last Name is required.";
}
return string.Empty;
case "Age":
if (string.IsNullOrEmpty(age))
{
return "Age is required.";
}
if (!IsNumeric(age))
{
return "Age should be numeric.";
}
return string.Empty;
default:
return string.Empty;
}
}
}
Here you see e.g. that the Age TextBox spawns another error message than in the previous screen shot:
The (re-)calculation of the Error property is triggered by an event handler on PropertyChanged:
public MainPageViewModel()
{
this.PropertyChanged += MainPageViewModel_PropertyChanged;
}
private void MainPageViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "Error" && e.PropertyName != "HasErrors")
{
this.OnPropertyChanged("Error");
this.OnPropertyChanged("HasErrors");
}
}
A little voice keeps telling me that it may well be possible to integrate most of the code into BindableBase, to form a DataErrorInfoBase or so. But I decided to ignore that little voice for the moment.
I decorated the ViewModel with an HasError property to facilitate data binding:
public bool HasErrors
{
get
{
return !string.IsNullOrEmpty(this.Error);
}
}
The ‘thank you’ message in the sample app only appears when the whole ViewModel is valid:
<TextBlock Text="Thank you very much. Existence as you know it, is over now."
Visibility="{Binding HasErrors, Converter={StaticResource ReverseBooleanToVisibilityConverter}}"/>
Here’s how it looks like:
Conclusion
Frankly, I just got closer to WPF validation than I expected and with a lot less code than I expected. This is a relief. I now strongly believe we will have enterprise level input validation in a very near future.
The Code
Here's the code for the sample app. It was written with Visual Studio 2012 Express: U2UConsult.Win8.TextBoxSample.zip (145.85 kb)
Enjoy!
Diederik