Silverlight 3 introduced the idea of types throwing exceptions from their setters as a way of reporting errors back to the UI. That works well but there are other ways of achieving that kind of error status from a type and the IDataErrorInfo interface has been around for a while as a way of doing this ( introduced in Windows Forms and then added to WPF V3.5 at a later point ).
It’s a simple enough interface;
// Summary:
// Defines properties that data entity classes can implement to provide custom
// validation support.
public interface IDataErrorInfo
{
// Summary:
// Gets a message that describes any validation errors for the object.
//
// Returns:
// The validation error on the object, or null or System.String.Empty if there
// are no errors present.
string Error { get; }
// Defines properties that data entity classes can implement to provide custom
// validation support.
public interface IDataErrorInfo
{
// Summary:
// Gets a message that describes any validation errors for the object.
//
// Returns:
// The validation error on the object, or null or System.String.Empty if there
// are no errors present.
string Error { get; }
// Summary:
// Gets a message that describes any validation errors for the specified property
// or column name.
//
// Parameters:
// columnName:
// The name of the property or column to retrieve validation errors for.
//
// Returns:
// The validation error on the specified property, or null or System.String.Empty
// if there are no errors present.
string this[string columnName] { get; }
}
whereby a caller can enquire about the general state of an object or about specific properties. Lots of people have implemented this in the past for Windows Forms applications and might;
- be very familiar with the interface so want to keep using it
- want to avoid the model of “have to throw an exception in a setter in order to validate the object”
- have a bunch of code that already uses IDataErrorInfo and want to keep using it
and so Silverlight 4 adds support for IDataErrorInfo. I can go and make myself a pretend class like this one;
public class Person : IDataErrorInfo
{
public string FirstName
{
get
{
return (firstName);
}
set
{
firstName = value;
}
}
public string LastName
{
get
{
return (lastName);
}
set
{
lastName = value;
}
}
public int Age
{
get
{
return (age);
}
set
{
age = value;
}
}
[Display(AutoGenerateField=false)]
public string Error
{
get { return (null); }
}
[Display(AutoGenerateField = false)]
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstName":
if (string.IsNullOrEmpty(firstName))
{
error = "Provide a first name";
}
break;
case "LastName":
if (string.IsNullOrEmpty(lastName))
{
error = "Provide a last name";
}
break;
case "Age":
if ((age < 0) || (age > 120))
{
error = "Age out of range";
}
break;
}
return (error);
}
}
string firstName;
string lastName;
int age;
}
// Gets a message that describes any validation errors for the specified property
// or column name.
//
// Parameters:
// columnName:
// The name of the property or column to retrieve validation errors for.
//
// Returns:
// The validation error on the specified property, or null or System.String.Empty
// if there are no errors present.
string this[string columnName] { get; }
}
whereby a caller can enquire about the general state of an object or about specific properties. Lots of people have implemented this in the past for Windows Forms applications and might;
- be very familiar with the interface so want to keep using it
- want to avoid the model of “have to throw an exception in a setter in order to validate the object”
- have a bunch of code that already uses IDataErrorInfo and want to keep using it
and so Silverlight 4 adds support for IDataErrorInfo. I can go and make myself a pretend class like this one;
public class Person : IDataErrorInfo
{
public string FirstName
{
get
{
return (firstName);
}
set
{
firstName = value;
}
}
public string LastName
{
get
{
return (lastName);
}
set
{
lastName = value;
}
}
public int Age
{
get
{
return (age);
}
set
{
age = value;
}
}
[Display(AutoGenerateField=false)]
public string Error
{
get { return (null); }
}
[Display(AutoGenerateField = false)]
public string this[string columnName]
{
get
{
string error = null;
switch (columnName)
{
case "FirstName":
if (string.IsNullOrEmpty(firstName))
{
error = "Provide a first name";
}
break;
case "LastName":
if (string.IsNullOrEmpty(lastName))
{
error = "Provide a last name";
}
break;
case "Age":
if ((age < 0) || (age > 120))
{
error = "Age out of range";
}
break;
}
return (error);
}
}
string firstName;
string lastName;
int age;
}
then I can wrap it up into a UI by feeding it as the DataContext of a DataGrid as below;
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
this.DataContext =
new Person[] { new Person() { FirstName = "Fred", LastName = "Smith", Age = 22 } };
};
}
}
with the corresponding XAML file;
<UserControl x:Class="SilverlightApplication25.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
this.Loaded += (s, e) =>
{
this.DataContext =
new Person[] { new Person() { FirstName = "Fred", LastName = "Smith", Age = 22 } };
};
}
}
with the corresponding XAML file;
<UserControl x:Class="SilverlightApplication25.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dg="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<dg:DataGrid
ItemsSource="{Binding}" />
</Grid>
</UserControl>
<dg:DataGrid
ItemsSource="{Binding}" />
</Grid>
</UserControl>
and that gives me a UI that knows about its errors as in;
Naturally – this isn’t just a DataGrid thing, it applies to regular controls and DataForms and so on and should help in getting a bunch of code across to Silverlight 4. In terms of making use from regular controls, if we switch our UI to be something like;
<UserControl
x:Class="SilverlightApplication25.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<Style
TargetType="Button">
<Setter
Property="Margin"
Value="5" />
</Style>
<Style
TargetType="TextBlock">
<Setter
Property="Margin"
Value="5" />
</Style>
<Style
TargetType="TextBox">
<Setter
Property="Margin"
Value="5" />
</Style>
</UserControl.Resources>
<StackPanel
x:Name="LayoutRoot"
Background="White"
BindingValidationError="OnValidationError">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="First Name " />
<TextBox
MinWidth="192"
Text="{Binding Person.FirstName,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Last Name " />
<TextBox
MinWidth="192"
Text="{Binding Person.LastName,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Age " />
<TextBox
MinWidth="192"
Text="{Binding Person.Age,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<Button
Content="Submit" IsEnabled="{Binding NoErrors}"/>
</StackPanel>
</UserControl>
x:Class="SilverlightApplication25.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<Style
TargetType="Button">
<Setter
Property="Margin"
Value="5" />
</Style>
<Style
TargetType="TextBlock">
<Setter
Property="Margin"
Value="5" />
</Style>
<Style
TargetType="TextBox">
<Setter
Property="Margin"
Value="5" />
</Style>
</UserControl.Resources>
<StackPanel
x:Name="LayoutRoot"
Background="White"
BindingValidationError="OnValidationError">
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="First Name " />
<TextBox
MinWidth="192"
Text="{Binding Person.FirstName,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Last Name " />
<TextBox
MinWidth="192"
Text="{Binding Person.LastName,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<StackPanel
Orientation="Horizontal">
<TextBlock
Text="Age " />
<TextBox
MinWidth="192"
Text="{Binding Person.Age,Mode=TwoWay,ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />
</StackPanel>
<Button
Content="Submit" IsEnabled="{Binding NoErrors}"/>
</StackPanel>
</UserControl>
note that I’ve set the ValidatesOnDataErrors property of these TextBoxes to be True which causes them to do the right thing around IDataErrorInfo. I’ve also set the NotifyOnValidationError but that’s a Silverlight 3 thing and I’m really using that to count errors as they occur and get fixed so that I can enable/disable my Submit button based on that.
The code behind this I changed to;
public partial class MainPage : UserControl, INotifyPropertyChanged
{
public MainPage()
{
InitializeComponent();
{
public MainPage()
{
InitializeComponent();
this.person = new Person() { FirstName = "Fred", LastName = "Smith", Age = 22 };
this.Loaded += (s, e) =>
{
this.DataContext = this;
};
}
public Person Person
{
get
{
return (person);
}
}
public bool NoErrors
{
get
{
return (errorCount == 0);
}
}
void FirePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private void OnValidationError(object sender, ValidationErrorEventArgs e)
{
switch (e.Action)
{
case ValidationErrorEventAction.Added:
errorCount++;
break;
case ValidationErrorEventAction.Removed:
errorCount--;
break;
default:
break;
}
FirePropertyChanged("NoErrors");
}
int errorCount;
Person person;
public event PropertyChangedEventHandler PropertyChanged;
}
and most of that is really around having a NoErrors property to enable/disable the button – the bits around IDataErrorInfo are as they were in the DataGrid example with the one exception of using the new NotifyOnValidationError on the binding directive and that gives me a UI made up of TextBoxes with the validation display being pretty much the same;
{
this.DataContext = this;
};
}
public Person Person
{
get
{
return (person);
}
}
public bool NoErrors
{
get
{
return (errorCount == 0);
}
}
void FirePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
private void OnValidationError(object sender, ValidationErrorEventArgs e)
{
switch (e.Action)
{
case ValidationErrorEventAction.Added:
errorCount++;
break;
case ValidationErrorEventAction.Removed:
errorCount--;
break;
default:
break;
}
FirePropertyChanged("NoErrors");
}
int errorCount;
Person person;
public event PropertyChangedEventHandler PropertyChanged;
}
and most of that is really around having a NoErrors property to enable/disable the button – the bits around IDataErrorInfo are as they were in the DataGrid example with the one exception of using the new NotifyOnValidationError on the binding directive and that gives me a UI made up of TextBoxes with the validation display being pretty much the same;

No comments:
Post a Comment