Thursday, March 12, 2009

Handling Error in WPF - IDataErrorInfo

You can validate user input in a bound UI element, reject invalid data, and give feedback to the user as to why the input is invalid using the IDataErrorInfo implementation. The IDataErrorInfo interface is the standard construct in .NET for supporting the validation of CLR objects. It returns error messages for properties that are in an invalid state. Typically, you would implement a business rules engine or validation framework to determine whether any property values are invalid. Create a class that implements IDataErrorInfo, and specify custom validation logic that returns error messages when a property value is invalid. Set the ValidatesOnErrors property of a System.Windows.Data.Binding to True. Optionally, create a custom control template to override the default Validation.ErrorTemplate, and change the appearance of a UI element when its data is invalid.

Code

<Window.Resources>
<local:DateTimeConverter x:Key="dateTimeConverter" />
<Style x:Key="textBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="VerticalAlignment" Value="Center" /> </Style>
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">
<Setter Property="FontFamily" Value="Calibri" />
<Setter Property="FontSize" Value="14" />
<Setter Property="Margin" Value="8 0 0 0" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel DockPanel.Dock="Right">
<AdornedElementPlaceholder />
<Image Source="/Images/Error.png" Width="25" Height="25" ToolTip="{Binding Path=AdornedElement.ToolTip, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}"/>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="Last Name: " Style="{StaticResource textBlockStyle}" Grid.Row="1" />
<TextBlock Text="Location: " Style="{StaticResource textBlockStyle}" Grid.Row="2" />
<TextBlock Text="Date Of Joining: " Style="{StaticResource textBlockStyle}" Grid.Row="3" />
<TextBlock Text="First Name: " Style="{StaticResource textBlockStyle}" />
<TextBox Name="txtFirstName" Style="{StaticResource textBoxStyle}" Grid.Column="1" Grid.Row="0" Width="150" Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtLastName" Style="{StaticResource textBoxStyle}" Grid.Column="1" Grid.Row="1" Width="150" Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtLocation" Style="{StaticResource textBoxStyle}" Grid.Column="1" Grid.Row="2" Width="150" Text="{Binding Path=Location, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtDateOfJoining" Style="{StaticResource textBoxStyle}" Grid.Column="1" Grid.Row="3" Width="150" Text="{Binding Path=DateOfJoining, Converter={StaticResource dateTimeConverter}, Mode=TwoWay, ValidatesOnDataErrors=True}" />
</Grid>

this.DataContext = new Programmer
{
FirstName = "Prajeesh",
LastName = "Prathap",
Location = "Florida",
DateOfJoining = new DateTime(2005, 8, 1)
};

public class Programmer : Model, IDataErrorInfo
{
private string m_FirstName;
public string FirstName
{
get { return m_FirstName; }
set
{
m_FirstName = value;
RaisePropertyChanged("FirstName");
}
}

----
----

private DateTime m_DateOfJoining;
public DateTime DateOfJoining
{
get { return m_DateOfJoining; }
set
{
m_DateOfJoining = value;
RaisePropertyChanged("DateOfJoining");
}
}

#region IDataErrorInfo Members

public string Error
{
get { return string.Empty; }
}

public string this[string columnName]
{
get
{
// Return an empty string if there are no errors
string message = string.Empty;
switch (columnName)
{
case "FirstName":
if (string.IsNullOrEmpty(m_FirstName))
message = "First name cannot be empty.";
break;
----
----
----
case "DateOfJoining":
if (DateOfJoining.CompareTo(DateTime.Today) == 1)
message = "DateOfJoining cannot be greater than today's date.";
break;
default:
break;
}
return message;
}
}

#endregion
}

public class DateTimeConverter : IValueConverter
{
#region IValueConverter Members

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((DateTime)value).ToShortDateString();
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
DateTime dateOfJoining;
if (DateTime.TryParse(value.ToString(), out dateOfJoining))
return dateOfJoining;
else
return string.Empty;
}

#endregion
}


Output


No comments: