| WPF IDataErrorInfo and Databinding |
| Section: WPF | Rating: Not rated yet! |
 | CodeGod submitted this resource The member's homepage is http://www.codegod.de Visit the profile here |
Attachment
Introduction
Databinding in WPF is fun, but what to do when to display errors. In this article the author describes how to use the IDataError-interface in WPF with .NET 3.5 to display input errors in your WPF-Window.
Figure 1The concept
When you developed .NET-Software using WinForms you should know the IDataErrorInfo-interface to display errors on your forms by using the ErrorProvider-component. In .NET 3.5 this interface is also available for WPF. The example described in this article makes use of this interface when binding a Contact-object with the properties FirstName and LastName to TextBoxes on a WPF-Window. We "raise" an input-error if the data in FirstName or LastName is empty or the input's length is less than 5 characters. Then the Textbox relating to the error will have a red border and the cause of the input-error will be displayed in he TextBox's tooltip.
The Project
At first we have to create a new WPF Application in VS 2008. After that we rename the Window1-instance created by the project-template to WindowAddContact cause in our example we want to have a Window where we can create new contacts (persons).
We create a nice-looking header and some textboxes for that purpose. The XAML looks like this:
<Window x:Class="WpfXamlErrorHandling.WindowAddContact"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:AppCode="clr-namespace:WpfXamlErrorHandling"
Title="New contact" Height="239" Width="366" WindowStartupLocation="CenterScreen">
<Window.Resources>
<AppCode:Contact x:Key="dsContact"/>
</Window.Resources>
<Grid>
<!-- Define the Grid's rows here -->
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Header -->
<Border Name="borderHeader" Grid.Row="0" BorderBrush="Gray"
BorderThickness="1" CornerRadius="0">
<Border.Background>
<LinearGradientBrush StartPoint="0.0,0" EndPoint="0.0,1" Opacity="0.8">
<GradientStop Color="#EEEEEE" Offset="0.1" />
<GradientStop Color="#666699" Offset="0.35" />
<GradientStop Color="#3333CC" Offset="0.5" />
<GradientStop Color="#000099" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<Grid>
<StackPanel Margin="5" Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="#EEEEEEEE" />
<Setter Property="FontSize" Value="20" />
</Style>
</StackPanel.Resources>
<TextBlock Name="txtHeader" Width="170"
Height="27">Add a new contact</TextBlock>
</StackPanel>
</Grid>
</Border>
<!-- Content -->
<StackPanel Margin="5" Grid.Row="1" HorizontalAlignment="Left" Background="#FFDEDEDE"
ClipToBounds="True"></StackPanel>
<TextBox BorderBrush="Black" Margin="80,19.96,19,0" Name="txtFirstName" Height="22"
Grid.Row="1" VerticalAlignment="Top">
<Binding Source="{StaticResource dsContact}" UpdateSourceTrigger="LostFocus"
Path="FirstName" ValidatesOnDataErrors="True" />
</TextBox>
<Label Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="6,17.96,0,0"
Name="label1" VerticalAlignment="Top" Width="61">Firstname</Label>
<Label HorizontalAlignment="Left" Margin="6,53,0,0" Name="label2" Width="70"
Height="23" Grid.Row="1" VerticalAlignment="Top">Lastname</Label>
<TextBox BorderBrush="Black" Margin="80,54.04,19,74.96" Height="22"
Name="txtLastName" Grid.Row="1" VerticalAlignment="Top">
<Binding Source="{StaticResource dsContact}" UpdateSourceTrigger="LostFocus"
Path="LastName" ValidatesOnDataErrors="True" />
</TextBox>
<Button Grid.Row="1" Height="23" HorizontalAlignment="Right" Margin="0,0,19,15"
Name="btnOK" VerticalAlignment="Bottom" Width="75" Click="btnOK_Click">OK</Button>
</Grid>
</Window>
The Contact-class
As I said we want to create contacts, so we need a Contact-class for binding to the input-fields:
public class Contact : IDataErrorInfo
{
private string _firstName;
private string _lastName;
public string FirstName
{
get { return _firstName; }
set { _firstName = value; }
}
public string LastName
{
get { return _lastName; }
set { _lastName = value; }
}
#region IDataErrorInfo Members
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string result = null;
if( columnName == "FirstName" )
{
if (String.IsNullOrEmpty( FirstName) )
result = "Firstname has to be set!";
else if (FirstName.Length < 5)
result = "Firstname's length has to be at least 5 characters!";
}
else if (columnName == "LastName")
{
if (String.IsNullOrEmpty(LastName))
result = "LastName has to be set!";
else if (LastName.Length < 5)
result = "LastName's length has to be at least 5 characters!";
}
return result;
}
}
#endregion
}
The interesting thing about this class is that it implements the interface IDataErrorInfo. When doing so, we have to implement the property Error which we leave empty and an indexer where we validate our properties. See the implementation of the indexer: If the column to validate is FirstName or LastName we write the error-string into the result-variable. Now our databound textboxes know that they have to show an error.
Binding the Contact-object
How can a Contact-object be bound to the input-fields? Well, I don't want to explain the whole databinding-mechanism of WPF in this article cause it is too complex for this, but binding an object can be done like this:
1. Add Contact to the Window's resources in XAML:
<Window.Resources>
<AppCode:Contact x:Key="dsContact"/>
</Window.Resources>
2. Set a Binding-Tag to your input-field:
<TextBox BorderBrush="Black" Margin="80,19.96,19,0" Name="txtFirstName" Height="22"
Grid.Row="1" VerticalAlignment="Top">
<Binding Source="{StaticResource dsContact}"
UpdateSourceTrigger="LostFocus" Path="FirstName"
ValidatesOnDataErrors="True" />
</TextBox>
That's all. When you are entering data to your TextBox and leave it (see LostFocus-trigger), the value you entered is written to a Contact-object's property FirstName. Due to the fact that Contact implements IDataErrorInfo, an error is displayed if our criteria for valid input-data does not fit.
Control how errors are displayed
If an error occurs the TextBoxes display a red border by default. Quite nice, but we want to display the error's cause in the TextBox's tooltip. We can define this behaviour by using a Style in the Application's XAML:
<Application x:Class="WpfXamlErrorHandling.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="WindowAddContact.xaml">
<Application.Resources>
<Style TargetType="{x:Type TextBox}">
<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>
</Application.Resources>
</Application>
Isn't that easy? I really love WPF... Have fun!