How do you implement validation in custom controls? I am looking to replicate the standard validation logic you would see with a TextBox data-bound to a model or view-model that
To implement validation you should add the "ValidationStates" group to the VisualStateManager of the control.
I will illustrate the simple custom control TestControl
with the TestProperty
property.
Style in the Generic.xaml, depending on the state displays the blue text or the red border with the first error message:
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock Text="{TemplateBinding TestProperty}" Foreground="Blue" />
<Border x:Name="InvalidBorder" BorderBrush="Red" BorderThickness="2" Visibility="Collapsed">
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource TemplatedParent}}" Foreground="Red" FontWeight="Bold" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There are 3 states:
Here is the code of the control, it contains only one property:
public class TestControl : Control
{
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public string TestProperty
{
get { return (string)GetValue(TestPropertyProperty); }
set { SetValue(TestPropertyProperty, value); }
}
public static readonly DependencyProperty TestPropertyProperty =
DependencyProperty.Register("TestProperty", typeof(string), typeof(TestControl), new PropertyMetadata(null));
}
After that if you use the IDataErrorInfo, the correct xaml is:
<local:TestControl TestProperty="{Binding SomeModelProperty, ValidatesOnDataErrors=True}" />
For the INotifyDataErrorInfo, the correct xaml is even simplier:
<local:TestControl TestProperty="{Binding SomeModelProperty}" />