IDataErrorInfo Interface

[ This article is for Windows Phone 8 developers. If you’re developing for Windows 10, see the latest documentation. ]

Defines members that data entity classes can implement to provide custom validation support.

Namespace: System.ComponentModel
Assembly: System.Windows (in System.Windows.dll)

Syntax

Public Interface IDataErrorInfo
public interface IDataErrorInfo

The IDataErrorInfo type exposes the following members.

Properties

Name Description
Error Gets a message that describes any validation errors for the object.
Item Gets a message that describes any validation errors for the specified property or column name.

Top

Remarks

This interface enables data entity classes to implement custom validation rules and expose validation results to the user interface. You typically implement this interface to provide relatively simple validation logic.

To provide asynchronous or server-side validation logic, implement the INotifyDataErrorInfo interface instead.

In general, new entity classes should implement INotifyDataErrorInfo for the added flexibility instead of implementing IDataErrorInfo. The IDataErrorInfo support enables you to use many existing entity classes that are written for the full .NET Framework. Other validation options are also available, such as the use of data attributes. For more information, see Data binding for Windows Phone 8.

IDataErrorInfo Members

The IDataErrorInfo..::.Item property returns a single validation error message. The Error property returns a single error message for the entire object. Each of these single messages can represent multiple errors. If you want to represent multiple errors by using multiple custom error objects, implement the INotifyDataErrorInfo interface instead. This enables more detailed error reporting in the user interface (UI).

Data Binding Support

The XAML binding engine provides built-in support for IDataErrorInfo. To enable this support, bind a control to a property of an entity that implements IDataErrorInfo, and set the Binding..::.ValidatesOnDataErrors property to true. Then, whenever a bound entity property changes value, the binding engine validates the new value by passing the property name to IDataErrorInfo..::.Item. Note that the binding engine never uses the Error property, although you can use it in custom error reporting to display object-level errors.

The binding engine uses the validation results to update the Validation..::.Errors collection for that binding. First, the binding engine removes any existing errors for the bound property that originate from exceptions or from IDataErrorInfo validation. Then, if the new value is not valid, the binding engine adds a new error for the property.

You can use ValidatesOnExceptions and ValidatesOnDataErrors at the same time; however, any exceptions that occur will prevent IDataErrorInfo validation.

Error Reporting

The Validation..::.Errors collection provides a binding source for error reporting in the user interface. Several controls bind to this collection from their default control templates.

To provide custom error reporting, you can replace the default templates with customized versions. You can also set the Binding..::.NotifyOnValidationError property to true and handle the FrameworkElement..::.BindingValidationError event. This event occurs each time an error is added or removed for a particular binding. Some controls require this mechanism instead of using the Validation..::.Errors collection.

Examples

The following example code demonstrates how to implement IDataErrorInfo. This example uses the same error reporting structure as the example for the INotifyDataErrorInfo interface. However, in the IDataErrorInfo implementation, multiple errors for a single property are combined into a single error message. This limits the options for custom error reporting. Additionally, the IDataErrorInfo interface does not support the future addition of asynchronous validation rules.

To test this example, type in different values and change the focus to the other text box. Here are the values that you can test:

  • Id less than 5.

  • Id greater than 10.

  • Name with spaces.

  • Name with more than 5 characters.

Imports System.ComponentModel

Public Class Product
    Implements IDataErrorInfo

    Private idValue As Integer
    Public Property Id As Integer
        Get
            Return idValue
        End Get
        Set(ByVal value As Integer)
            If IsIdValid(value) AndAlso
                idValue <> value Then idValue = value
        End Set
    End Property

    Private nameValue As String
    Public Property Name As String
        Get
            Return nameValue
        End Get
        Set(ByVal value As String)
            If IsNameValid(value) AndAlso
                nameValue <> value Then nameValue = value
        End Set
    End Property

    ' Validates the Id property, updating the errors collection as needed.
    Public Function IsIdValid(ByVal value As Integer) As Boolean

        Dim isValid = True

        If value < 5 Then
            AddError("Id", ID_ERROR, False)
            isValid = False
        Else
            RemoveError("Id", ID_ERROR)
        End If

        If value > 10 Then
            AddError("Id", ID_WARNING, True)
        Else
            RemoveError("Id", ID_WARNING)
        End If

        Return isValid

    End Function

    ' Validates the Name property, updating the errors collection as needed.
    Public Function IsNameValid(ByVal value As String) As Boolean

        Dim isValid = True

        If value.Contains(" ") Then
            AddError("Name", NAME_ERROR, False)
            isValid = False
        Else
            RemoveError("Name", NAME_ERROR)
        End If

        If (value.Length > 5) Then
            AddError("Name", NAME_WARNING, True)
        Else
            RemoveError("Name", NAME_WARNING)
        End If

        Return isValid

    End Function

    Private errors As New Dictionary(Of String, List(Of String))
    Private ReadOnly ID_ERROR = "Value cannot be less than 5."
    Private ReadOnly ID_WARNING = "Value should not be greater than 10."
    Private ReadOnly NAME_ERROR = "Value must not contain any spaces."
    Private ReadOnly NAME_WARNING = "Value should be 5 characters or less."

    ' Adds the specified error to the errors collection if it is not already 
    ' present, inserting it in the first position if isWarning is false. 
    Public Sub AddError(ByVal propertyName As String, ByVal [error] As String,
                        ByVal isWarning As Boolean)

        If Not errors.ContainsKey(propertyName) Then _
            errors(propertyName) = New List(Of String)()

        If Not errors(propertyName).Contains([error]) Then
            If isWarning Then
                errors(propertyName).Add([error])
            Else
                errors(propertyName).Insert(0, [error])
            End If
        End If

    End Sub

    ' Removes the specified error from the errors collection if it is present. 
    Public Sub RemoveError(ByVal propertyName As String, ByVal [error] As String)

        If errors.ContainsKey(propertyName) AndAlso
            errors(propertyName).Contains([error]) Then

            errors(propertyName).Remove([error])
            If errors(propertyName).Count = 0 Then errors.Remove(propertyName)

        End If

    End Sub

    Public ReadOnly Property [Error] As String Implements IDataErrorInfo.Error
        Get
            Throw New NotImplementedException()
        End Get
    End Property

    ReadOnly Property Item(ByVal propertyName As String) As String _
        Implements IDataErrorInfo.Item
        Get
            Return If(Not errors.ContainsKey(propertyName), Nothing,
                String.Join(Environment.NewLine, errors(propertyName)))
        End Get
    End Property

End Class
using System;
using System.ComponentModel;
using System.Collections.Generic;

namespace IDataErrorInfoExample
{

    public class Product : IDataErrorInfo
    {
        private int idValue;

        public int Id
        {
            get { return idValue; }
            set { if (IsIdValid(value) && idValue != value) idValue = value; }
        }

        private string nameValue;
        public string Name
        {
            get { return nameValue; }
            set { if (IsNameValid(value) && nameValue != value) nameValue = value; }
        }

        // Validates the Id property, updating the errors collection as needed.
        public bool IsIdValid(int value)
        {
            bool isValid = true;

            if (value < 5)
            {
                AddError("Id", ID_ERROR, false);
                isValid = false;
            }
            else RemoveError("Id", ID_ERROR);

            if (value > 10) AddError("Id", ID_WARNING, true);
            else RemoveError("Id", ID_WARNING);

            return isValid;
        }

        // Validates the Name property, updating the errors collection as needed.
        public bool IsNameValid(string value)
        {
            bool isValid = true;

            if (value.Contains(" "))
            {
                AddError("Name", NAME_ERROR, false);
                isValid = false;
            }
            else RemoveError("Name", NAME_ERROR);

            if (value.Length > 5) AddError("Name", NAME_WARNING, true);
            else RemoveError("Name", NAME_WARNING);

            return isValid;
        }

        private Dictionary<String, List<String>> errors =
            new Dictionary<string, List<string>>();
        private const string ID_ERROR = "Value cannot be less than 5.";
        private const string ID_WARNING = "Value should not be greater than 10.";
        private const string NAME_ERROR = "Value must not contain any spaces.";
        private const string NAME_WARNING = "Value should be 5 characters or less.";

        // Adds the specified error to the errors collection if it is not already 
        // present, inserting it in the first position if isWarning is false. 
        public void AddError(string propertyName, string error, bool isWarning)
        {
            if (!errors.ContainsKey(propertyName))
                errors[propertyName] = new List<string>();

            if (!errors[propertyName].Contains(error))
            {
                if (isWarning) errors[propertyName].Add(error);
                else errors[propertyName].Insert(0, error);
            }
        }

        // Removes the specified error from the errors collection if it is present. 
        public void RemoveError(string propertyName, string error)
        {
            if (errors.ContainsKey(propertyName) &&
                errors[propertyName].Contains(error))
            {
                errors[propertyName].Remove(error);
                if (errors[propertyName].Count == 0) errors.Remove(propertyName);
            }
        }

        #region IDataErrorInfo Members

        public string Error
        {
            get { throw new NotImplementedException(); }
        }

        public string this[string propertyName]
        {
            get
            {
                return (!errors.ContainsKey(propertyName) ? null :
                    String.Join(Environment.NewLine, errors[propertyName]));
            }
        }

        #endregion
    }
}
<phone:PhoneApplicationPage x:Class="IDataErrorInfoExample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="800" d:DesignWidth="480"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}">

  <Grid x:Name="LayoutRoot" Background="Transparent" Margin="10">

    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>

        <TextBlock Grid.Column="0" Margin="3" Text="Product Id"/>
        <StackPanel Grid.Row="1" x:Name="IdField" Orientation="Vertical">
            <TextBox KeyDown="DataField_KeyDown"
                Text="{Binding Id, Mode=TwoWay, 
                ValidatesOnDataErrors=True, 
                NotifyOnValidationError=True}" 
                BindingValidationError="OnBindingValidationError"
                BorderThickness="2"
                Width="250" HorizontalAlignment="Left"/>
            <TextBlock Foreground="Red" TextWrapping="Wrap" Width="400" Margin="0,10"/>
        </StackPanel>

        <TextBlock Grid.Row="2" Margin="3" Text="Product Name"/>
        <StackPanel Grid.Row="3" x:Name="NameField" Orientation="Vertical">
            <TextBox KeyDown="DataField_KeyDown"
                Text="{Binding Name, Mode=TwoWay, 
                ValidatesOnDataErrors=True, 
                NotifyOnValidationError=True}" 
                BindingValidationError="OnBindingValidationError"
                BorderThickness="2"
                Width="250" HorizontalAlignment="Left"/>
            <TextBlock Foreground="Red" TextWrapping="Wrap" Width="400" Margin="0,10" />
        </StackPanel>
    </Grid>
</phone:PhoneApplicationPage>
Partial Public Class MainPage
    Inherits PhoneApplicationPage

    Public Sub New()
        InitializeComponent()
        LayoutRoot.DataContext = New Product() With {.Id = 10, .Name = "food"}
    End Sub

    ' Commits text box values when the user presses ENTER. This makes it 
    ' easier to experiment with different values in the text boxes.
    Private Sub DataField_KeyDown(ByVal sender As System.Object,
                                ByVal e As System.Windows.Input.KeyEventArgs)
        If e.Key = System.Windows.Input.Key.Enter Then CType(sender, TextBox) _
            .GetBindingExpression(TextBox.TextProperty).UpdateSource()
    End Sub

    Private Shared ReadOnly _errorBrush As Brush = New SolidColorBrush(Colors.Red)

    Private Sub OnBindingValidationError(sender As System.Object, e As System.Windows.Controls.ValidationErrorEventArgs)
        Dim dataBox As TextBox = TryCast(sender, TextBox)
        Dim dataPanel As StackPanel = TryCast(dataBox.Parent, StackPanel)
        Dim errorBlock As TextBlock = dataPanel.Children.OfType(Of TextBlock)().[Single]()

        If e.Action = ValidationErrorEventAction.Added Then
            dataBox.BorderBrush = _errorBrush
            errorBlock.Text = e.[Error].ErrorContent.ToString()
        Else
            dataBox.BorderBrush = DirectCast(Resources("PhoneBackgroundBrush"), Brush)
            errorBlock.Text = String.Empty
        End If

    End Sub
End Class
using System.Windows.Controls;
using System.Windows.Media;
using System.Linq;

namespace IDataErrorInfoExample
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            InitializeComponent();
            LayoutRoot.DataContext = new Product() { Id = 10, Name = "food" };
        }

        // Commits text box values when the user presses ENTER. This makes it 
        // easier to experiment with different values in the text boxes.
        private void TextBox_KeyDown(object sender,
            System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == System.Windows.Input.Key.Enter) (sender as TextBox)
                .GetBindingExpression(TextBox.TextProperty).UpdateSource();
        }

        private void DataField_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {

            if (e.Key == System.Windows.Input.Key.Enter) (sender as TextBox)
                .GetBindingExpression(TextBox.TextProperty).UpdateSource();
        }

        private static readonly Brush _errorBrush = new SolidColorBrush(Colors.Red);

        private void OnBindingValidationError(object sender, ValidationErrorEventArgs e)
        {
            TextBox dataBox = sender as TextBox;
            StackPanel dataPanel = dataBox.Parent as StackPanel;
            TextBlock errorBlock = dataPanel.Children.OfType<TextBlock>().Single();

            if (e.Action == ValidationErrorEventAction.Added)
            {
                dataBox.BorderBrush = _errorBrush;
                errorBlock.Text = e.Error.ErrorContent.ToString();
            }
            else
            {
                dataBox.BorderBrush = (Brush)Resources["PhoneBackgroundBrush"];
                errorBlock.Text = string.Empty;
            }
        }
    }
}

Version Information

Windows Phone OS

Supported in: 8.1, 8.0, 7.1

Platforms

Windows Phone

See Also

Reference

System.ComponentModel Namespace

INotifyDataErrorInfo

Other Resources

Data binding for Windows Phone 8