I am creating a WPF application with the MVVM pattern.
Validation errors are managed by using INotifyDataErrorInfo. The ViewModel implements INotifyDataErrorInfo.
For System.Windows.Controls controls it works great, but it doesn’t work for custom controls.
In my application there is a custom button, like:
xaml:
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type local:MyButtonWithValidation}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyButtonWithValidation}">
<Grid>
<Button x:Name="PART_Button"
Command="{TemplateBinding Command}"
Content="{TemplateBinding Content}">
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
C#:
using System.Windows;
namespace MyApp.Controls;
public class MyButtonWithValidation: System.Windows.Controls.Button
{
static MyButtonWithValidation()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButtonWithValidation), new FrameworkPropertyMetadata(typeof(MyButtonWithValidation)));
}
}
If I use this button, it is visible and it executes the command bound to it and even the validation is working as expected (in case of validation error, the button is disabled and the validation message is written below the button in red):
<local:MyButtonWithValidation
Command="{Binding SearchCommand}"
Content="Search">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</local:MyButtonWithValidation>
Validation errors are managed by using INotifyDataErrorInfo. The ViewModel implements INotifyDataErrorInfo, like this:
private readonly Dictionary<string, List<string>> _errorsByPropertyName = new Dictionary<string, List<string>>();
public bool HasErrors => _errorsByPropertyName.Any();
public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
public IEnumerable GetErrors(string? propertyName)
{
return propertyName is null || !_errorsByPropertyName.ContainsKey(propertyName)
? null
: _errorsByPropertyName[propertyName];
}
In some cases I just apply a validation error, like:
AddError(nameof(SearchCommand), "Search validation error..."); // adds a new key-value pair ("SearchCommand", new List<string>() { "Search validation error..." }) to _errorsByPropertyName
As a result, there will be a validation error, and the error message is visible under the button.
Now, I want to move the Validation ErrorTemplate into MyButtonWithValidation:
<Style BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type local:MyButtonWithValidation}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyButtonWithValidation}">
<Grid>
<Button x:Name="PART_Button"
Command="{TemplateBinding Command}"
Content="{TemplateBinding Content}">
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<AdornedElementPlaceholder x:Name="textBox" />
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock
Foreground="Red"
Text="{Binding ErrorContent}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In this case, if I use MyButtonWithValidation like this, it doesn’t work as expected any more:
<local:MyButtonWithValidation
Command="{Binding SearchCommand}"
Content="Search">
</local:MyButtonWithValidation>
In this case a red border is created around the button and the error message is not visible. It is like the validation error would be “handled” by WPF and not the validation error template.
Why is this and how could it be fixed?