Silverlight 4 Rough Notes: Binding with INotifyDataErrorInfo

Note – these posts are put together after a short time with Silverlight 4 as a way of providing pointers to some of the new features that Silverlight 4 has to offer. I’m posting these from the PDC as Silverlight 4 is announced for the first time so please bear that in mind when working through these posts.

Whilst Silverlight 4 integrates the traditional IDataErrorInfo interface, it also brings in a new interface that adds additional capabilities called INotifyDataErrorInfo.

Why another interface? The existing interface is fine as far as it goes but it essentially has 2 capabilities;


  public interface IDataErrorInfo
  {
    string Error { get; }

    string this[string columnName] { get; }
  }

which could be described as;

  • “Tell me what’s wrong”
  • “Tell me what’s wrong with property X”

which is fine as far as it goes but there’s some things that it’s missing;

  • The Error property only allows you to return a single error whereas there might be more than one validation problem
  • Same issue really – the indexer only allows you to return a single error for a property whereas a property might have more than one validation problem
  • The error type is always a String – you might want to return something much richer than that
  • There’s no way to notify of errors – you can only respond. So, if a binding framework sees a change to property X then it can ask whether property X has any errors afterwards. However, what if this causes an error in property Y? How would the binding framework know?
  • Similarly – what if there’s a need to do a longer running asynchronous operation to figure out errors and then notify the UI? That’s not part of the current interface.

So, in addition to IDataErrorInfo, Silverlight 4 has an additional interface INotifyDataErrorInfo;

  public interface INotifyDataErrorInfo
  {
    bool HasErrors { get; }
    event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
    IEnumerable GetErrors(string propertyName);
  }

  public sealed class DataErrorsChangedEventArgs : EventArgs
  {
    public DataErrorsChangedEventArgs(string propertyName);
    public string PropertyName { get; }
  }

The new interface has a mechanism via which more than one error can be returned from the GetErrors() member which can be called with a property name or without one to find out the general state of the whole object. There’s also an ErrorsChanged event which provides a means by which a object can notify of errors.

I thought I’d have a go at representing my errors with a class rather than just with a string and so I went for something simple;

  public class ErrorInfo
  {
    public int ErrorCode { get; set; }
    public bool IsWarning { get; set; }
    public string ErrorMessage { get; set; }

    public override string ToString()
    {
      return (string.Format("{0}: {1}, {2}",
        IsWarning ? "Warning" : "Error",
        ErrorCode,
        ErrorMessage));
    }
  }

Then I set about thinking about INotifyDataErrorInfo – I figured that I’d most likely want this in some base-class and so I wrote out a quick one ( not sure it’s 100% right );

public class EntityBase : INotifyDataErrorInfo
  {
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public EntityBase()
    {
      currentErrors = new Dictionary<string, List<ErrorInfo>>();
    }
    public IEnumerable GetErrors(string propertyName)
    {
      if (string.IsNullOrEmpty(propertyName))
      {
        return (currentErrors.Values);
      }
      else
      {
        MakeOrCreatePropertyErrorList(propertyName);
        return (currentErrors[propertyName]);
      }
    }
    public bool HasErrors
    {
      get
      {
        return (currentErrors.Count > 0);
      }
    }
    void MakeOrCreatePropertyErrorList(string propertyName)
    {
      if (!currentErrors.ContainsKey(propertyName))
      {
        currentErrors[propertyName] = new List<ErrorInfo>();
      }
    }
    void FireErrorsChanged(string property)
    {
      if (ErrorsChanged != null)
      {
        ErrorsChanged(this, new DataErrorsChangedEventArgs(property));
      }
    }
    protected void ClearErrorFromProperty(string property, int errorCode)
    {
      MakeOrCreatePropertyErrorList(property);

      ErrorInfo error = 
        currentErrors[property].SingleOrDefault(e => e.ErrorCode == errorCode);

      if (error != null)
      {
        currentErrors[property].Remove(error);
        FireErrorsChanged(property);
      }
    }
    protected void AddErrorForProperty(string property, ErrorInfo error)
    {
      MakeOrCreatePropertyErrorList(property);

      if (currentErrors[property].SingleOrDefault(e => e.ErrorCode == error.ErrorCode) == null)
      {
        currentErrors[property].Add(error);
        FireErrorsChanged(property);
      }
    }
    Dictionary<string, List<ErrorInfo>> currentErrors;
  }

and you can see that this is mostly a Dictionary which maps between { property name } and { List of errors associated with that property right now } and it also tries to provide methods via which a derived class can add/remove errors from the collection ( based on the assumption that each error code I’ve defined ( 101, 102, etc ) can only appear once per property ).

With that in place I defined a class that derived from it and wrote out a bit of hard-coded validation logic;

  public class TaxPayer : EntityBase
  {
    public string FirstName
    {
      get
      {
        return (firstName);
      }
      set
      {
        firstName = value;
        ValidateRequiredStringProperty(101, "FirstName", value);
      }
    }
    void ValidateRequiredStringProperty(int errorCode, string property, string value)
    {
      if (string.IsNullOrEmpty(value))
      {
        AddErrorForProperty(
          property, new ErrorInfo()
          {
            ErrorCode = errorCode,
            ErrorMessage = string.Format("Property {0} is required", property),
            IsWarning = false
          });
      }
      else
      {
        ClearErrorFromProperty(property, errorCode);
      }
    }
    public string LastName
    {
      get
      {
        return (lastName);
      }
      set
      {
        lastName = value;
        ValidateRequiredStringProperty(102, "LastName", value);
      }
    }
    public int Age
    {
      get
      {
        return (age);
      }
      set
      {
        age = value;
        ValidateAge();
        ValidatePension();
      }
    }
    void ValidateAge()
    {
      if ((age < 16) || (age > 120))
      {
        AddErrorForProperty("Age", new ErrorInfo()
        {
          ErrorCode = 103,
          ErrorMessage = "Age outside of range",
          IsWarning = false
        });
      }
      else
      {
        ClearErrorFromProperty("Age", 103);
      }
    }
    void ValidatePension()
    {
      if ((Pension > 0.0m) && (age < 65))
      {
        AddErrorForProperty("Pension", new ErrorInfo()
        {
          ErrorCode = 104,
          ErrorMessage = "Too young for a pension?",
          IsWarning = true
        });
      }
      else
      {
        ClearErrorFromProperty("Pension", 104);
      }
      if (Pension > 100000.0m)
      {
        AddErrorForProperty("Pension", new ErrorInfo()
        {
          ErrorCode = 105,
          ErrorMessage = "Pension income over max allowed by app",
          IsWarning = false
        });
      }
      else
      {
        ClearErrorFromProperty("Pension", 105);
      }
    }
    public decimal Pension
    {
      get
      {
        return (pension);
      }
      set
      {
        pension = value;
        ValidatePension();
      }
    }    
    string firstName;
    string lastName;
    int age;
    decimal pension;
  }

It’s a bit of a weird class 🙂 but the idea is that we have a TaxPayer with some validation rules;

  • FirstName and LastName required.
  • Age between 16 and 120.
  • Pension income 0 for anyone under 65 and less than 100000 for anyone.

With that in place I felt like the base-class really needed tidying up to remove the need for all this Add/Clear stuff that I’d added but I’ve left it for now. I sketched out a little bit of data-bound UI to make use of this which is basicaly just;

  • some TextBoxes bound to the properties above – note the ValidatesOnNotifyDataErrors property. This is new and switches on the behaviour for using INotifyDataErrorInfo.
  • A ListBox which displays all the errors – there’s nothing new about this but I use the NotifyOnValidationError property to ensure that not only do we use INotifyDataErrorInfo but we also pass on any errors found via an event which I catch by setting up a handler for BindingValidationError. Note that this existed in Silverlight 3 and the only point of including it here is to show that both work together quite nicely.
    <Grid
        x:Name="LayoutRoot"
        Background="White"
        BindingValidationError="OnValidationError">
        <Grid.RowDefinitions>
            <RowDefinition
                Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <Border
            BorderThickness="1"
            BorderBrush="Silver"
            CornerRadius="3"
            Margin="3">
            <Grid
                Name="personGrid">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition
                        Width="Auto" />
                    <ColumnDefinition
                        Width="Auto"
                        MinWidth="192" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition
                        Height="Auto" />
                    <RowDefinition
                        Height="Auto" />
                    <RowDefinition
                        Height="Auto" />
                    <RowDefinition
                        Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock
                    Grid.Column="0"
                    Grid.Row="0"
                    HorizontalAlignment="Left"
                    Margin="3"
                    Text="First Name:"
                    VerticalAlignment="Center" />
                <TextBox
                    Grid.Column="1"
                    Grid.Row="0"
                    Margin="3"
                    Text="{Binding Path=FirstName,Mode=TwoWay,ValidatesOnNotifyDataErrors=True,NotifyOnValidationError=True}" />
                <TextBlock
                    Grid.Column="0"
                    Grid.Row="1"
                    HorizontalAlignment="Left"
                    Margin="3"
                    Text="Last Name:"
                    VerticalAlignment="Center" />
                <TextBox
                    Grid.Column="1"
                    Grid.Row="1"
                    Margin="3"
                    Text="{Binding Path=LastName,Mode=TwoWay,ValidatesOnNotifyDataErrors=True,NotifyOnValidationError=True}" />
                <TextBlock
                    Grid.Column="0"
                    Grid.Row="2"
                    HorizontalAlignment="Left"
                    Margin="3"
                    Text="Age:"
                    VerticalAlignment="Center" />
                <TextBox
                    Grid.Column="1"
                    Grid.Row="2"
                    Margin="3"
                    Text="{Binding Path=Age,Mode=TwoWay,ValidatesOnNotifyDataErrors=True,NotifyOnValidationError=True}" />
                <TextBlock
                    Grid.Column="0"
                    Grid.Row="3"
                    HorizontalAlignment="Left"
                    Margin="3"
                    Text="Pension:"
                    VerticalAlignment="Center" />
                <TextBox
                    Grid.Column="1"
                    Grid.Row="3"
                    Margin="3"
                    Text="{Binding Path=Pension,Mode=TwoWay,ValidatesOnNotifyDataErrors=True,NotifyOnValidationError=True}" />
            </Grid>
        </Border>

        <Border
            Grid.Row="1"
            Margin="3"
            BorderThickness="1"
            BorderBrush="Red">
            <StackPanel
                Grid.Row="1">
                <TextBlock
                    Margin="5"
                    Foreground="Red"
                    Text="All Errors" />
                <ListBox
                    Grid.Row="1"
                    MinHeight="192"
                    ItemsSource="{Binding Errors}">
                    <ListBox.ItemTemplate>
                        <DataTemplate>
                            <ListBoxItem
                                Content="{Binding ErrorContent}" />
                        </DataTemplate>
                    </ListBox.ItemTemplate>
                </ListBox>
            </StackPanel>
        </Border>
    </Grid>

Note also that the ListBoxItem is displaying the ErrorContent from the ValidationError class which is allowing it to pick up my own custom ErrorInfo objects rather than limiting me to a string or anything like that.

Then with some code behind that I set up the main DataContext to be the class itself so that it can pick up the Errors collection and I set the DataContext for the grid containing all the TextBoxes to be a new instance of my TaxPayer class;

  public partial class MainPage : UserControl
  {
    public ObservableCollection<ValidationError> Errors { get; set; }

    public MainPage()
    {
      InitializeComponent();
      this.Errors = new ObservableCollection<ValidationError>();

      this.Loaded += (s, e) =>
        {
          personGrid.DataContext = new TaxPayer() 
          { 
            FirstName = "Fred", 
            LastName = "Smith",
            Age = 66, 
            Pension = 15000.0m 
          };
          this.DataContext = this;
        };
    }
    private void OnValidationError(object sender, ValidationErrorEventArgs e)
    {
      switch (e.Action)
      {
        case ValidationErrorEventAction.Added:
          if (!Errors.Contains(e.Error))
          {
            this.Errors.Add(e.Error);
          }
          break;
        case ValidationErrorEventAction.Removed:
          this.Errors.Remove(e.Error);
          break;
        default:
          break;
      }
    }
  }

and then as I’m working with my UI the initial state is good;

image

and I can omit a First name causing an error;

image

and then fix that error;

image

and then I could change the age to be 12 which would cause two more errors – one around the age being too low and the other around the pension value being set for an age < 65;

image

and then I could change the pension value to be 100,001 making that another error on the same property;

image

note that the default UI in the TextBox template does not know how to handle multiple errors here so it displays one of them but the multiple errors are actually there as displayed in my ListBox at the bottom of the screen ( made possible by a combination of ValidatesOnNotifyErrors and NotifyOnValidationErrors ).

And so there’s now quite a few ways to do this kind of notification with binding;

  1. Throw exceptions in your setters. You need to switch on ValidatesOnExceptions for that.
  2. You use IDataErrorInfo. You need to switch on ValidatesOnDataErrors for that.
  3. You use INotifyDataErrorInfo. You need to switch on ValidatesOnNotifyDataErrors for that.
  4. And then ( optionally ) if you want these errors passed on as a routed event to pick up higher up in the UI tree somewhere then you can also combine with NotifyOnValidationError to achieve that.

Phew, lots of validation options in Silverlight 4 🙂