In a WPF DataGrid, I want to show a validation result in a small box inside the cell.
I managed to do so for a single column by binding to the Validation.Errors
data structure (see the code below).
This is what I got and it's pretty close to the desired outcome; now I want to implement it for all the columns.
In order to make the solution reusable over multiple columns I tried to move it into a ControlTemplate. I couldn't find a way to establish the binding of Validation.Errors
again from inside the control template (See the code below). As a result, the red label is always empty.
The working solution is based on following code:
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Label x:Name="x" Content="{Binding Name}"/>
<Label Padding="2" HorizontalAlignment="Right" VerticalAlignment="Top" Height="15" Width="44" FontSize="8" Foreground="White" Background="Red"
Content="{Binding ElementName='x', Path='(Validation.Errors)[0].ErrorContent'}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
It works by binding the Label "x" to the Name property of my example datacontext.
<Label x:Name="x" Content="{Binding Name}"/>
Then, the error label in turn binds to the former Label (via its name) and gets the Validation.Errors
information (graphics formats removed here for clearness).
<Label Content="{Binding ElementName='x', Path='(Validation.Errors)[0].ErrorContent'}"/>
This proves that the result is achieveable, but this solution cannot be reused over multiple columns without repeating it over and over again.
In order to have a reusable template, i tried to wrap all my cell contorls (label x and label with x's errors) into a ControlTemplate; it will be used by a Label component that is what i'll actually have on the grid.
The wrapping code is this (bewlow there is the complete code):
<Label Content="{Binding Name}">
<Label.Template>
<ControlTemplate TargetType="Label">
//my controls
</ControlTemplate>
</Label.Template>
</Label>
I had to change the line:
<Label x:Name="x" Content="{Binding Name}"/>
to this:
<Label x:Name="x" Content="{TemplateBinding Content}"/>
But the Label dedicated to the errors doesnt work anymore (graphics configuration removed):
I can guess that it doesn't work because only the content property is trasfered form the templated label to the inner label x; the content and not the entire 'state' of the property including the validation errors collection. But how can I access those errors then?
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name}">
<Label.Template>
<ControlTemplate TargetType="Label">
<Grid>
<Label x:Name="x" Content="{TemplateBinding Content}"/>
<Label Padding="2" HorizontalAlignment="Right" VerticalAlignment="Top" Height="15" Width="44" FontSize="8" Foreground="White" Background="Red"
Content="{Binding ElementName='x', Path='(Validation.Errors)[0].ErrorContent'}"/>
</Grid>
</ControlTemplate>
</Label.Template>
</Label>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class ViewModel
{
public ObservableCollection<Person> People { get; } = new ObservableCollection<Person>() { new Person { Name = "Alan" } };
}
public class Person: INotifyDataErrorInfo
{
public string Name { get; set; }
public bool HasErrors => true;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public IEnumerable GetErrors(string propertyName)
{
yield return "Some error";
}
}
Instead of Binding to Validation.Errors of Label 'x', you can refer to Validation.Errors of the TemplatedParent, i.e. Main Label. I was able to extract the ControlTemplate to window resource, and use this resource as Label Template, so we can reuse this template.
<Window.Resources>
<ControlTemplate TargetType="Label" x:Key="Lbl">
<Grid>
<Label x:Name="x" Content="{TemplateBinding Content}"/>
<Label Padding="2" HorizontalAlignment="Right" VerticalAlignment="Top" Height="15" Width="44" FontSize="8" Foreground="White" Background="Red"
Content="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource TemplatedParent}}"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Name}"
Template="{StaticResource Lbl}">
</Label>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>