MVVM pattern, IDataErrorInfo and Binding to display error?
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:
ValidatesOnDataErrors must be True. This instructs WPF to look for and use the IDataError interface on the underlying object.
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).
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.
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.
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>
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"/>
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; } }
}
UI Look and feel.
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, 2022Comments
-
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?