|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSince the dawn of software development, the single biggest threat to reliable information has been end-users. After decades of the existence of computers, why do we still need to inform users that "the Start Date should always be less than the End Date", or that "First Name is a required field"? Since Under Windows Forms, we had the Table of Contents
Welcome to WPF ValidationIf you never spent much time with Windows Forms or data binding, I'm hoping that this article will remain simple enough to follow along. Validation in Windows Presentation Foundation takes an approach very similar to ASP.NET validation, in that most "business rules" are enforced on the user interface and applied to specific controls and bindings. This approach is quite simple to understand and implement, but some proponents of "rich domain models" and object-oriented designs (including myself) have some problems with it. As we approach the end of the article, I'll discuss why this is and a way to do things differently, but still leveraging some of the strengths of WPF. On the other hand, if you did spend a lot of time using Windows Forms, and made heavy use of the Warning: All of the sample code was written using the BETA 2 release of .NET 3.0, and subsequently, some of it may have changed. Hopefully, the concepts will stay the same though, otherwise this will be a very wasted article :) Very Simple Validation: ExceptionValidationRuleMost of the time when we talk about validation, we're talking about validating user input (other kinds of validation are outside the scope of this article). Let's look at the most simple kind of built-in validation offered by WPF - the public class Customer
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Writing classes is fun, but you'll probably never convince people to pay you unless you can add some kind of UI to let them interact with your classes. Since this article is about Windows Presentation Foundation, let's use XAML to create our UI. As we're smart developers, we'll also use data binding: <TextBox Text="{Binding Path=Name}" />
Before we go any further, it's important to note that the odd-looking markup above is actually just a kind of shorthand for writing this: <TextBox>
<TextBox.Text>
<Binding Path="Name" />
</TextBox.Text>
</TextBox>
Now, let's say one of your requirements is that customer names are mandatory. To implement this constraint, you could change your customer's public string Name
{
get { return _name; }
set
{
_name = value;
if (String.IsNullOrEmpty(value))
{
throw new ApplicationException("Customer name is mandatory.");
}
}
}
Using WPF Validation Rules on our data binding, we can display this error automatically. All we need to do is make use of the <TextBox>
<TextBox.Text>
<Binding Path="Name">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
If you ran this code, you'd see something which looks like this: Displaying ErrorsWindows Presentation Foundation has a static class inside the
By default, the To achieve the look you're after, you could define your own control template: <Application.Resources>
<ControlTemplate x:Key="TextBoxErrorTemplate">
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">!!!!</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Application.Resources>
The <TextBox
Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
[...]
<TextBox>
Or, to save having to set the <Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt">
!!!!
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, green borders are all well and good, but they don't exactly tell the users what they've done wrong. Since we included the error message in the exception we were throwing earlier, we can make use of the static <Style TargetType="{x:Type TextBox}">
[... SNIP: The code from above ...]
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Now, if we mouse over the All we need to do is place that style in our application resources, ensure we apply any validation rules to our bindings, and voila! On a more advanced note, if you would rather show the errors somewhere else than in the <Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right"
Foreground="Orange"
FontSize="12pt"
Text="{Binding ElementName=MyAdorner,
Path=AdornedElement.(Validation.Errors)
[0].ErrorContent}">
</TextBlock>
<Border BorderBrush="Green" BorderThickness="1">
<AdornedElementPlaceholder Name="MyAdorner" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Customized Validation RulesI know what you're thinking: "Do I have to throw exceptions?" I'm glad you asked. If you remember our customer's If you hunt around the framework, you'll see that the public abstract class ValidationRule
{
public abstract ValidationResult Validate(
object value,
CultureInfo culture);
}
When we created our binding earlier, I had some XAML that resembled this: <Binding Path="Name">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
The truth is, we could have used anything that inherits from the namespace MyValidators
{
public class StringRangeValidationRule : ValidationRule
{
private int _minimumLength = -1;
private int _maximumLength = -1;
private string _errorMessage;
public int MinimumLength
{
get { return _minimumLength; }
set { _minimumLength = value; }
}
public int MaximumLength
{
get { return _maximumLength; }
set { _maximumLength = value; }
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value,
CultureInfo cultureInfo)
{
ValidationResult result = new ValidationResult(true, null);
string inputString = (value ?? string.Empty).ToString();
if (inputString.Length < this.MinimumLength ||
(this.MaximumLength > 0 &&
inputString.Length > this.MaximumLength))
{
result = new ValidationResult(false, this.ErrorMessage);
}
return result;
}
}
}
Our validator has three properties: a First, we'll need to reference it in our XML namespaces at the top of the file: <Window [...]
xmlns:validators="clr-namespace:MyValidators" />
Now we can add it to our binding's <Binding Path="Name">
<Binding.ValidationRules>
<validators:StringRangeValidationRule
MinimumLength="1"
ErrorMessage="A name is required." />
</Binding.ValidationRules>
</Binding>
If you've added the styles I gave above, that should be all you need to do to show validation messages. Controlling When to Validate: UpdateSourceTriggersIf you ran any of the sample code above, you'll notice that the validation only takes place when you hit Tab or click away from the text box. That's a good default for most cases, but if you'd like validation to take place at other times, you can use the The
To make use of the <TextBox>
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
For more information on Where Does an ErrorProvider Fit in?We've already discussed that the
Defining business rules in markup may not be such a problem for some applications, but in applications with rich business objects such as Trial Balance, or applications using rich frameworks such as CSLA, or in cases where the rules are shared across many screens or applications, you may find this to be a big limitation. If this isn't a problem for you, you might not get much out of the rest of this article. This means that the ValidationRules approach isn't very useful to some of us. However, all is not lost, as we can still make use of the static IDataErrorInfoThe
To refresh your memory, this is how we would have used this interface in .NET 2.0: public class Customer : IDataErrorInfo
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public string Error
{
get
{
return this[string.Empty];
}
}
public string this[string propertyName]
{
get
{
string result = string.Empty;
propertyName = propertyName ?? string.Empty;
if (propertyName == string.Empty || propertyName == "Name")
{
if (string.IsNullOrEmpty(this.Name))
{
result = "Name cannot be blank!";
}
}
return result;
}
}
}
Back then, all we had to do was bind a grid to our Sadly, More on IDataErrorInfo...The MSDN entry for IDataErrorInfo is a good starting point. For a deeper discussion of the Creating our ErrorProviderThe <validators:ErrorProvider>
<StackPanel>
<TextBox Text="{Binding Path=Name}" />
<TextBox Text="{Binding Path=Age}" />
</StackPanel>
</validators:ErrorProvider>
My Cycling through the control hierarchy in WPF is done using the Using the WPF
ConclusionWhether you choose to use custom If your applications have a very rich domain model, you may find the Of course, if none of these approaches suit your requirements, you could always hold out for .NET 4.0 and the introduction of Special ThanksI would like to give a special thanks to Paul Czywcynski of TempWorks who has done a lot of testing with my I'd also like to thank Mike Brown, who pointed me to the | ||||||||||||||||||||