Silverlight 4 provides some very powerful data form capabilities out of the box. You will no longer have to throw an exception just to send a validation error or even worry about hard-coding labels. With support for data annotations,
IDataErrorInfo
Let’s explore the surface of some of these powerful new features!
IDataErrorInfo
With support for
IDataErrorInfo
INotifyDataErrorInfo
public abstract class BaseEntity : INotifyPropertyChanged, IDataErrorInfo
{
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
protected void RemoveErrors(string prop)
{
if (_errors.ContainsKey(prop))
{
_errors.Remove(prop);
}
}
protected void AddError(string prop, string error)
{
if (_errors.ContainsKey(prop))
{
_errors[prop].Add(error);
}
else
{
_errors.Add(prop, new List<string> { error });
}
}
protected void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual string Error
{
get { return string.Empty; }
}
public virtual string this[string columnName]
{
get
{
System.Text.StringBuilder retVal = new System.Text.StringBuilder();
if (_errors.ContainsKey(columnName))
{
bool first = true;
foreach (string error in _errors[columnName])
{
retVal.Append(error);
if (first)
{
first = false;
}
else
{
retVal.Append(Environment.NewLine);
}
}
}
return retVal.ToString();
}
}
}
There are a few things going on here. First, we implement
INotifyPropertyChanged
OnPropertyChanged
Next, we implement
IDataErrorInfo
Environment.NewLine
Data Annotations
Now you can build a
PersonInfo
System.ComponentModel.DataAnnotations
c:Program FilesMicrosoft SDKsSilverlight
v4.0LibrariesClient
public class PersonInfo : BaseEntity
{
private string _firstName, _lastName;
private int _age;
[Display(Name="First Name")]
[Required]
public string FirstName
{
get { return _firstName; }
set
{
if (string.IsNullOrEmpty(value.Trim()))
{
AddError("FirstName", "First name is required.");
}
else
{
RemoveErrors("FirstName");
if (!value.Equals(_firstName))
{
_firstName = value;
OnPropertyChanged("FirstName");
}
}
}
}
[Display(Name="Last Name")]
[Required]
public string LastName
{
get { return _lastName; }
set
{
if (string.IsNullOrEmpty(value.Trim()))
{
AddError("LastName", "Last name is required.");
}
else
{
RemoveErrors("LastName");
if (!value.Equals(_lastName))
{
_lastName = value;
OnPropertyChanged("LastName");
}
}
}
}
[Display(Name="Age (Years)")]
[Required]
public int Age
{
get { return _age; }
set
{
if (value 130)
{
AddError("Age", "Age must be a valid integer between 18 and 130.");
}
else
{
RemoveErrors("Age");
if (!value.Equals(_age))
{
_age = value;
OnPropertyChanged("Age");
}
}
}
}
}
You’ll probably pull out validations into a handler / rule set but basically what I’m doing is validating the value, setting an error or clearing all errors if it passes, then calling the property changed event if the value truly changes. Note the use of annotations to specify required properties as well as “friendly names” for the properties.
Now that our class is prepped, we can get to work on the XAML.
Implicit Styles
With support for implicit styles, we can set a style based on a target type and style it that way – allowing for themes to be set externally. We’ll take advantage of that to set the width for our
TextBox
PersonInfo
MainPage.xaml
<UserControl.Resources>
<local:PersonInfo x_Key="Person"/>
<Style TargetType="TextBox">
<Setter Property="Width" Value="200"/>
</Style>
</UserControl.Resources>
Data Input Helpers
Next, add a reference to
System.Windows.Controls.Data.Input
PersonInfo
xmlns_local="clr-namespace:DataForm"
xmlns_dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"
Great, now let’s get to work! I’m going to show you the full snippet of XAML and then explain the pieces:
<Grid x_Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource Person}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<dataInput:Label Target="{Binding ElementName=tbFirstName}"
Grid.Row="0" Grid.Column="0"/>
<StackPanel Orientation="Horizontal"
Grid.Row="0" Grid.Column="1">
<TextBox x_Name="tbFirstName"
Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue=''}"/>
<dataInput:DescriptionViewer Description="Please enter your first name."
Target="{Binding ElementName=tbFirstName}"/>
</StackPanel>
<dataInput:Label Target="{Binding ElementName=tbLastName}"
Grid.Row="1" Grid.Column="0"/>
<StackPanel Orientation="Horizontal"
Grid.Row="1" Grid.Column="1">
<TextBox x_Name="tbLastName"
Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue=''}" />
<dataInput:DescriptionViewer Description="Please enter your last name."
Target="{Binding ElementName=tbLastName}"/>
</StackPanel>
<dataInput:Label Target="{Binding ElementName=tbAge}"
Grid.Row="2" Grid.Column="0"/>
<StackPanel Orientation="Horizontal"
Grid.Row="2" Grid.Column="1">
<TextBox x_Name="tbAge"
Text="{Binding Path=Age, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue='0'}"/>
<dataInput:DescriptionViewer Description="Please enter a valid age between 18 and 130."
Target="{Binding ElementName=tbAge}"/>
</StackPanel>
<dataInput:ValidationSummary Grid.Row="3" Grid.Column="0"
Grid.ColumnSpan="2"/>
</Grid>
Some of these helper classes are made available via the toolkit for Silverlight 3 as well.
Labels
The label class lets us bind to an element like a textbox. It will pull the name of the databound property from the annotations and display it. If a validation error is thrown, it will turn red to further reinforce the error. If the field is tagged as required, the label will automatically bold itself to indicate a required field.
Learn more about the Label class
New Bindings
If you take a look at the textbox contorls, you’ll see we’ve added new binding parameters. Specifically,
ValidatesOnDataErrors
FallbackValue
Description Viewer
The description viewer control provides an easy way to show tips and hints and is similar to the
ToolTip
Learn more about the description viewer
Validation Summary
Finally, we add a validation summary control. The default behavior is to list all errors. When the user clicks on an error, it gives focus to the UI element that caused the error.
Learn more about the validation summary class
Putting it all Together
When you tie this all together and run it (without any additional code behind), this is an example of what you’ll see:
- Note the labels automatically pulled from the on the entity class
DisplayAttribute
- The labels are bold due to the annotation
RequiredAttribute
- The labels on properties with errors are colored red
- I clicked on the information glyph next to the first name text box and was the hint to enter my first name
- I clicked on the red triangle in the upper right corner of the text box for age and was shown the error that my age is not in the valid range
- The control has summarized my errors (if I click on one, I’ll be taken to the control with the issue)
ValidationSummary
As you can see, that’s quite a bit of functionality right out of the box, and it allows a nice, clean separation of things like attributes and descriptions from the user interface that presents them.