Data validation – Silverlight versus WPF part 2

In a previous blog post I pointed out that quite a difference between data validation between Silverlight and WFP. As I don’t think adding data validation in the UI is a good thing I focused on validation in the business object by throwing an exception when the value was not acceptable. As Beth pointed out WPF also supports the IDataErrorInfo interface as she demonstrates here.

I think using the IDataErrorInfo interface is superior to throwing exceptions. After all exceptions should be exceptional and users entering invalid data in real applications is not very exceptional  [:(]. However Silverlight only contains a subset of the .NET framework and doesn’t include the IDataErrorInfo interface.

So using the IDataErrorInfo in Silverlight will option will not work right?

Wrong, it is a very simple interface and we can just add it ourselves. We can even add it using the same namespace System.ComponentModel to when the interface gets added to Silverlight we can just remove our copy [:)].

So this is my Silverlight implementation of the IDataErrorInfo interface:

namespace System.ComponentModel
{
    // Summary:
    //     Provides the functionality to offer custom error information that a user
    //     interface can bind to.
    public interface IDataErrorInfo
    {
        // Summary:
        //     Gets an error message indicating what is wrong with this object.
        //
        // Returns:
        //     An error message indicating what is wrong with this object. The default is
        //     an empty string ("").
        string Error { get; }

        // Summary:
        //     Gets the error message for the property with the given name.
        //
        // Parameters:
        //   columnName:
        //     The name of the property whose error message to get.
        //
        // Returns:
        //     The error message for the property. The default is an empty string ("").
        string this[string columnName] { get; }
    }
}


I just copied this code by right clicking the IDataErrorInfo in my WPF application and selecting all the code shown.



So how do WPF and Silverlight compare when using the IDataErrorInfo interface?



 



The sample business object is the same person as in the pervious blog post. Only this time I did implement both the IDataErrorInfo and the INotifyPropertyChanged interface. The INotifyPropertyChanged is not really needed in WPF here, it will work just as well without it, but is a good practice to do every time.



public class Person : IDataErrorInfo, INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            if (string.IsNullOrEmpty(_firstName))
                _errors["FirstName"] = "The first name cannot be empty.";
            else
                _errors["FirstName"] = null;
            OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            _lastName = value;
            if (string.IsNullOrEmpty(_lastName))
                _errors["LastName"] = "The last name cannot be empty.";
            else
                _errors["LastName"] = null;
            OnPropertyChanged("LastName");
        }
    }

    private Dictionary<string, string> _errors = new Dictionary<string, string>();
    public string Error
    {
        get
        {
            StringBuilder sb = new StringBuilder();
            foreach (var item in _errors)
                if (item.Value != null)
                    sb.AppendLine(item.Value);

            return sb.ToString();
        }
    }

    public string this[string columnName]
    {
        get
        {
            string result = null;
            _errors.TryGetValue(columnName, out result);

            return result;
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;




The WPF application



The XAML in the WPF application is simple, even simpler as previous blog post and looks like this:



<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Firstname:" Grid.Row="0" Grid.Column="0"/>
    <TextBox Text="{Binding FirstName, ValidatesOnDataErrors=True}" 
             Grid.Row="0" Grid.Column="1"/>
    <TextBlock Text="Lastname:" 
               Grid.Row="1" Grid.Column="0"/>
    <TextBox Text="{Binding LastName, ValidatesOnDataErrors=True}" 
             Grid.Row="1" Grid.Column="1"/>
    <Button Content="Close" Grid.Row="2" Grid.Column="0"/>
</Grid>


Nice, no longer do we need to add the ValidationRules collection and add the ExceptionValidationRule to it, instead all we have to do it set the ValidatesOnDataErrors to true. Much nicer [:)].



image



 



How about the Silverlight application?



The Silverlight XAML isn’t much more complicated and looks like this:


<Grid x:Name="LayoutRoot" Background="White">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="150"/>
        <ColumnDefinition />
        <ColumnDefinition  Width="20"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <TextBlock Text="Firstname:" Grid.Row="0" Grid.Column="0"/>
    <TextBox Text="{Binding FirstName, Mode=TwoWay}" 
             Grid.Row="0" Grid.Column="1"/>
    <local:ErrorStatus PropertyName="FirstName"  Grid.Row="0" Grid.Column="2"/>
    <TextBlock Text="Lastname:" Grid.Row="1" Grid.Column="0"/>
    <TextBox Text="{Binding LastName, Mode=TwoWay}" 
             Grid.Row="1" Grid.Column="1"/>
    <local:ErrorStatus PropertyName="LastName" Grid.Row="1" Grid.Column="2"/>
    <Button Content="Close" Grid.Row="2" Grid.Column="0" Click="Button_Click" />
</Grid>

The main difference is the addition of two local:ErrorStatus elements. Most of the extra complexity is in this control which is needed because Silverlight has no default way to display validation errors. Please note that there is no longer a BindingValidationError handler involved, everything the app needs is done in XAML, much better than the approach when throwing exceptions. Also note that all I need to do is set the PropertyName attribute to indicate which property validation to display. When run this page looks like this:



image



 



The ErrorStatus control



All “magic” happens inside of the ErrorStatus control. This is a real simple user control. Now if I wanted to create a reusable control I would need to put some more work into it and use the VisualStateManager with the States&Parts model but this simple control is enough to demonstrate the solution.



The ErrorStatus.xaml looks like this:



<UserControl x:Class="SilverlightApplication5.ErrorStatus"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="16" Height="16">
    <Grid x:Name="LayoutRoot" >
        <Ellipse Fill="Red"/>
        <TextBlock Foreground="White" 
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Stretch" 
                   FontWeight="Bold" >!</TextBlock>
    </Grid>
</UserControl>


Real simple, just a red circle with a white exclamation mark in it.



The code is not very complex either. Most of it is related to either checking if the DataContext changed, see this post for an explanation, and when a property value has changed.



public partial class ErrorStatus : UserControl
{
    public ErrorStatus()
    {
        InitializeComponent();
        SetBinding(MyDataContextProperty, new Binding());
    }

    public static readonly DependencyProperty MyDataContextProperty =
        DependencyProperty.Register("MyDataContext", 
                                    typeof(object), 
                                    typeof(ErrorStatus), 
                                    new PropertyMetadata(DataContextChanged));

    public static readonly DependencyProperty PropertyNameProperty =
        DependencyProperty.Register("PropertyName",
                                    typeof(string),
                                    typeof(ErrorStatus),
                                    null);

    private static void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        INotifyPropertyChanged person;
        ErrorStatus errorStatus = (ErrorStatus)sender;

        person = e.OldValue as INotifyPropertyChanged;
        if (person != null)
            person.PropertyChanged -= errorStatus.person_PropertyChanged;

        person = e.NewValue as INotifyPropertyChanged;
        if (person != null)
            person.PropertyChanged += errorStatus.person_PropertyChanged;

        errorStatus.UpdateStatus(e.NewValue);
    }

    public string PropertyName
    {
        get { return (string)GetValue(PropertyNameProperty); }
        set { SetValue(PropertyNameProperty, value); }
    }

    private void person_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == PropertyName)
            UpdateStatus(sender);
    }

    private void UpdateStatus(object sender)
    {
        IDataErrorInfo person = sender as IDataErrorInfo;
        if (person != null)
        {
            string errorText = person[PropertyName];
            if (string.IsNullOrEmpty(errorText))
                Opacity = 0;
            else
                Opacity = 100;

            ToolTipService.SetToolTip(this, errorText);
        }
    }
}


The real work happens in the UpdateStatus() function right at the bottom. This function is called every time the target property changes or the DataContext is set. Basically it checks if there are any errors and makes the control visible or invisible depending on the result. Besides the control’s visibility it also sets the tooltip text to the error. The only other thing worth mentioning is that I make the control invisible by using the Opacity instead of the Visibility property. The reason is that I do not want to change the layout of the controls on the page just because the validation status changed. WPF has a Visibility option of Visibility.Hidden but unfortunately Silverlight doesn’t support this. Not a big problem as making a control transparent is just a good in this case.



Conclusion



Using the IDataErrorInfo is a much nicer approach and creating just one, pretty simple, control in Silverlight makes it work just as easy in Silverlight as WPF. So much better than throwing exceptions [:)]



Download the source



Enjoy!



[f1]
[f2]

5 thoughts on “Data validation – Silverlight versus WPF part 2

  1. Thanks a lot for this, I’ve grown so used to IDataErrorInfo that it is just a pain to live without it. Obviously, I would really prefer to have it in Silverlight, but this looks great as well!

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>