MVVM pattern, IDataErrorInfo and Binding to display error?

11,173

Solution 1

I was looking at the same sample just a few minutes ago. Your guess is righ. In this code sample they removed default ErrorTemplate from TextBox control so it would't show red rectangle. Instead of using ErrorTemplate they create ContentProvider with content bound to validation error of specific text box.

Solution 2

When you bind to an object that supports IDataErrorInfo, there are several features of the WPF Binding class to consider:

  1. ValidatesOnDataErrors must be True. This instructs WPF to look for and use the IDataError interface on the underlying object.

  2. The attached property Validation.HasError will be set to true on the target object if the source object's IDataError interface reported a validation problem. You can then use this property with trigger to change the tooltip of the control to display the validation error message (I'm doing this in my current project and the end user's love it).

  3. The Validation.Errors attached property will contain an enumeration of any ValidationResult errors resulting from the last validation attempt. If you're going with the tooltip approach, use an IValueConverter to retrieve only the first item... otherwise you run into binding errors for displaying the error message itself.

  4. The binding class exposes NotifyOnValidationError, which when True, will cause routed events to bubble up from the bound control every time a validation rule's state changes. This is useful if you want to implement an event handler in the container of the bound controls, and then add and remove the validation messages to/from a listbox.

There are samples on MSDN for doing both style of feedback (the tooltips as well as the listbox), but I'll paste below the code I roled to implement the tooltip feedback on my DataGridCells and TextBoxes...

The DataGridCell style:

   <Style TargetType="{x:Type dg:DataGridCell}"
           x:Key="DataGridCellStyle">

      <Setter Property="ToolTip"
              Value="{Binding Path=Column.(ToolTipService.ToolTip),RelativeSource={RelativeSource Self}}" />

      <Style.Triggers>
        <Trigger Property="Validation.HasError"
                 Value="True">
          <Setter Property="ToolTip"
                  Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors), Converter={StaticResource ErrorContentConverter}}" />
        </Trigger>
      </Style.Triggers>

    </Style>

The TextBox style:

     <Style x:Key="ValidatableTextBoxStyle" TargetType="TextBox">
  <!--When the control is not in error, set the tooltip to match the AutomationProperties.HelpText attached property-->
  <Setter Property="ToolTip"
          Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=(AutomationProperties.HelpText)}" />

          <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
              <Setter Property="ToolTip"
                      Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}" />
            </Trigger>
          </Style.Triggers>
        </Style>

The ErrorContentConverter (for retrieving the first validation error message for the tooltip):

Imports System.Collections.ObjectModel

Namespace Converters

    <ValueConversion(GetType(ReadOnlyObservableCollection(Of ValidationError)), GetType(String))> _
    Public Class ErrorContentConverter
        Implements IValueConverter

        Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
            Dim errors As ReadOnlyObservableCollection(Of ValidationError) = TryCast(value, ReadOnlyObservableCollection(Of ValidationError))
            If errors IsNot Nothing Then
                If errors.Count > 0 Then
                    Return errors(0).ErrorContent
                End If
            End If
            Return String.Empty
        End Function

        Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
            Throw New NotImplementedException()
        End Function

    End Class

End Namespace

...and finally an example of using the style in a textbox:

    <TextBox Text="{Binding Path=EstimatedUnits,ValidatesOnDataErrors=True,NotifyOnValidationError=True}"
             Style="{StaticResource ValidatableTextBoxStyle}"
             AutomationProperties.HelpText="The number of units which are likely to sell in 1 year." />

Solution 3

Here is code I used for both displaying the error in Tooltip Or A small bubble next to the control.

  1. Define the Style.

    <Style x:Key="TextBoxValidationStyle" TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="true">
                <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},  Path=(Validation.Errors)[0].ErrorContent}"/>
            </Trigger>
        </Style.Triggers>
    </Style>
    
    <Style x:Key="TextboxErrorBubbleStyle" TargetType="{x:Type TextBox}" BasedOn="{StaticResource ResourceKey=TextBoxValidationStyle}">
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel LastChildFill="true">
    
                        <Border Background="Red" DockPanel.Dock="right" Margin="5,0,0,0" Width="10" Height="10" CornerRadius="10"
                            ToolTip="{Binding ElementName=customAdorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
    
                            <TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center" FontWeight="Bold" Foreground="white"/>
                        </Border>
    
                        <AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
                            <Border BorderBrush="red" BorderThickness="1" />
                        </AdornedElementPlaceholder>
    
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

  2. Using with Control.

    <TextBox Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnDataErrors=True,NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"
                Style="{StaticResource TextBoxValidationStyle}" Width="100" Margin="3 5 3 5"/>
     <TextBox Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnDataErrors=True,NotifyOnValidationError=True, UpdateSourceTrigger=PropertyChanged}"
                Style="{StaticResource TextboxErrorBubbleStyle}" Width="100" Margin="0 5 3 5"/>
    

  3. Sample Model Class.

    class Customer : INotifyPropertyChanged, IDataErrorInfo { private string firstName; private string lastName;

    public string FirstName
    {
        get { return firstName; }
        set
        {
            if (firstName != value)
            {
                firstName = value;
                RaisePropertyChanged("FirstName");
            }
        }
    }
    
    public string LastName
    {
        get { return lastName; }
        set
        {
            if (lastName != value)
            {
                lastName = value;
                RaisePropertyChanged("LastName");
            }
        }
    }
    public string Error
    {
        get { throw new System.NotImplementedException(); }
    }
    
    public string this[string columnName]
    {
        get
        {
            string message = null;
            if (columnName == "FirstName" && string.IsNullOrEmpty(FirstName))
            {
                message = "Please enter FirstName";
            }
            if (columnName == "LastName" && string.IsNullOrEmpty(LastName))
            {
                message = "Please enter LastName";
            }
            return message;
        }
    }
    

    }

  4. UI Look and feel.

enter image description here

Share:
11,173
Patrick Desjardins
Author by

Patrick Desjardins

Senior Software Developer at Netflix [California, Los Gatos] Senior Software Developer at Microsoft [Washington, Redmond] Working for Microsoft Cloud and Enterprise, mostly on Team Services Dashboards, Kanban and Scaled Agile project Microsoft Teams (first release) Microsoft MVP 2013 and 2014 [Quebec, Montreal]

Updated on June 09, 2022

Comments

  • Patrick Desjardins
    Patrick Desjardins almost 2 years

    On MSDN Magazine it has a good article about MVVM and they are binding the validation error in the Xaml to Validation.ErrorTemplate="{x:Null}". I do not get it why and how they can display from the IDataErrorInfo the error? Anyone can light me on how to get the error message displayed to the screen with the MVVM approach?