I have written a quick example project you can download here: blog.sln. Please note, for this solution to work properly, you will need to download the latest version of MVVM Light Toolkit. Also, I'm assuming you have minimum experience with MVVM and binding. If not, I suggest you read this article.
To start using IDataErrorInfo, you will need what I like to refer to as a Presenter object. For now, just consider a Presenter something similar to a ViewModel. In a future blog post, I'll discuss why I choose Presenter over ViewModel. Trust me, there are many benefits, especially if you use Resharper.
In the example you can download, I have created a MainWindowPresenter which extends/inherits ViewModelBase and implements the IDataErrorInfo object. Since this is such a simple example, there is no need to break things down into more presenters.
When you implement IDataErrorInfo, there are two properties that need implemented: Error and Item. I rarely implement the Error property because it only returns a single error describing what is wrong with the entire object. However, the Item property returns a string which indicates what failed with the validation of a property being bound to. This means, that for each property that is available in my Presenter, when that property setter is called, this Item property is called. So each time a property value changes, I have a hook to validate the value. By using Item, I can generate a more granular error message as opposed to a single error message for the object.
The example you can download is a simple WPF application. It has 2 fields and button. The idea is to enter a Last Name and First Name and then if you click the button, it will display a message formatting the name Last Name first. For this example, our business logic is Last Name is a required field but First Name is optional. We should not allow a user to press the Show Name button unless they have at least entered Last Name. We should also provide some visual cue that Last Name is required. In this example, I have chosen to display a dark red border around the Last Name TextBox with a soft red background.
Here is my code for the Item property:
public string this[string columnName]
{
get
{
switch (columnName)
{
case "LastName":
return string.IsNullOrWhiteSpace(LastName) ?
"Please provide a Last Name" :
null;
default:
return null;
}
}
}
The columnName parameter is the name of the public property you are binding to. In this case, I am binding to the LastName public string property. When LastName's setter is called, the code above is called to validate the value. In this case, if LastName is null or whitespace, I return a string message explaining why the validation failed, else I return null. By returning null from the Item property, you indicate validation is successful. By returning a string, you are indicating validation failed.
The next step is on the XAML side in the Binding of the Last Name TextBox. To enable validation you must set ValidatesOnDataErrors to True and NotifyOnValidationError to True. Below is my XAML for the LastName TextBox:
<TextBox Style="{StaticResource ValidatingTextBox}"
Text="{Binding Path=LastName,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True,
NotifyOnValidationError=True}" />
By setting UpdateSourceTrigger to PropertyChanged, I am telling my Binding to update the LastName property each time the Text value changes. This ensures as soon as someone types into the LastName TextBox, we start validating.
The next items to discuss involve the styles associated with this Window. I have a DefaultTextBoxStyle that sets the basics of the TextBox such as Height, Width, and alignments. I then have a ValidatingTextBox style that investigates the Validation result of a TextBox and changes the color and tooltip if there is a Validation error. Below are the styles:
<Style x:Key="DefaultTextBox" TargetType="TextBox">
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Margin" Value="5,0,0,0" />
<Setter Property="Width" Value="150" />
<Setter Property="Height" Value="25" />
</Style>
<Style x:Key="ValidatingTextBox" TargetType="TextBox" BasedOn="{StaticResource DefaultTextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderBrush" Value="#D10000" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="#FFBACD" />
</Trigger>
</Style.Triggers>
</Style>
Looking at the ValidatingTextBox style, you can there is a Style trigger based on property Validation.HasError. When Validation.HasError is true, I access the Validation.Errors collection and grab the ErrorContent of the first error in the collection. This will display the string result from the Item property as the tooltip. So by simply implementing IDataErrorInfo and returning a string if a binding value isn't valid, with this style, I can provide a tooltip message and a visual cue something is wrong.
The enabling/disabling of the button is handled by the MVVM Light ToolKit's RelayCommand object. When creating a RelayCommand, you provide the executing method as the first parameter, and a validation as the second. The validation method just needs to return true if it is possible to run the execution method. I simply provide a method that returns true if LastName is not null or whitespace.
That's it! Following this example, you should be able to show visual cues in your UI when required data is missing, in an improper format, outside an acceptable range, etc...
Stay tuned for the next blog. I will explain the pattern we use at my current place of employment, named CCP (Controller, Context, Presenter).
No comments:
Post a Comment