I have implemented validation within my application using DataAnnotations and INotifyDataError and can successfully show when an error occurs and what the error is. I wish to change the default error template to style the textbox in which the value was entered.
This works correctly for me if the TextBox exists in the same UserControl as where the binding to the underlying data model is created.
However I have many inputs and have therefore decided to extract a UserControl to encapsulate the label and textbox. The problem is that having done this I can no longer get the textbox to indicate the error I get the default the red box around the entire control.
I have tried a couple of suggestions such as having the child control implement INotifyDataError but I have had no luck so far. This post is the same issue and I could not find a solution using it so I'm hoping with more tags this may draw more attention and a solution Show Validation Error in UserControl
Here is a small sample I have produced that shows the problem. The top entry styles the TextBox to red if an invalid age is entered but the bottom user control simply has a red box around it.
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:ValidatorTest" mc:Ignorable="d" x:Class="ValidatorTest.MainWindow"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<ResourceDictionary>
<CollectionViewSource x:Key="customerViewSource" d:DesignSource="{d:DesignInstance {x:Type local:Customer}, CreateList=True}"/>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true" >
<Setter Property="Foreground" Value="Red"/>
<Setter Property="Background" Value="MistyRose"/>
<Setter Property="BorderBrush" Value="Red"/>
<Setter Property="BorderThickness" Value="1.0"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid DataContext="{StaticResource customerViewSource}">
<Grid x:Name="grid1" HorizontalAlignment="Left" Margin="19,27,0,0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Content="Age:" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center"/>
<TextBox x:Name="ageTextBox" HorizontalAlignment="Left" Height="23" Margin="3"
Text="{Binding Age, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}"
VerticalAlignment="Center"
Width="120"
/>
</StackPanel>
<local:UnitInput Grid.Row="1"
Label="Age"
Value="{Binding Age, Mode=TwoWay, ValidatesOnExceptions=True}"/>
</Grid>
</Grid>
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CollectionViewSource customerViewSource = ((CollectionViewSource)(this.FindResource("customerViewSource")));
customerViewSource.Source = new [] { new Customer{
Age = 26}};
}
}
using System.ComponentModel.DataAnnotations;
public class Customer : ModelBase
{
private double? age;
[Range(21, 55)]
[Required(ErrorMessage = "Age is required.")]
public double? Age
{
get
{
return this.age;
}
set
{
this.ValidateProperty(() => this.Age, value);
if (!double.Equals(value, this.age))
{
this.age = value;
this.RaisePropertyChanged(() => this.Age);
}
}
}
}
<UserControl x:Class="ValidatorTest.UnitInput"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Label}" VerticalAlignment="Center" Width="100"/>
<TextBox Grid.Column="1" HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="{Binding Value, Mode=TwoWay}" HorizontalContentAlignment="Right" VerticalAlignment="Top" Width="80" Margin="5"/>
</Grid>
/// <summary>
/// Interaction logic for UnitInput.xaml
/// </summary>
public partial class UnitInput : UserControl
{
public UnitInput()
{
this.InitializeComponent();
}
public string Label
{
get
{
return (string)GetValue(LabelProperty);
}
set
{
SetValue(LabelProperty, value);
}
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(UnitInput), new PropertyMetadata("Label"));
public string Value
{
get
{
return (string)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(UnitInput), new PropertyMetadata(null));
}
Thanks in advance for your suggestions.