WPF Datagrid,如何从 ControlTemplate 访问验证错误

WPF Datagrid, how to access validation errors from a ControlTemplate

在 WPF DataGrid 中,我想在单元格内的小框中显示验证结果。
我设法通过绑定到 Validation.Errors 数据结构(参见下面的代码)对单个列执行此操作。
这就是我得到的,它非常接近预期的结果;现在我想为所有列实现它。

问题

为了使解决方案可在多个列上重复使用,我尝试将其移动到 ControlTemplate 中。我找不到从控件模板内部再次建立 Validation.Errors 绑定的方法(请参阅下面的代码)。结果,红色标签始终为空。

可行的单列解决方案

工作解决方案基于以下代码:

<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>

无需全部阅读:这里是相关部分

它的工作原理是将标签 "x" 绑定到我的示例数据上下文的名称 属性。

<Label x:Name="x" Content="{Binding Name}"/>

然后,错误标签依次绑定到之前的标签(通过其名称)并获取Validation.Errors信息(为清楚起见,此处删除了图形格式)。

<Label Content="{Binding ElementName='x', Path='(Validation.Errors)[0].ErrorContent'}"/>

这证明结果是可以实现的,但是这个解决方案不能在多列上重复使用,除非一遍又一遍地重复。

包装尝试

为了拥有一个可重复使用的模板,我尝试将我所有的单元格控件(标签 x 和带有 x 错误的标签)包装到一个 ControlTemplate 中;它将由标签组件使用,这就是我在网格上实际拥有的组件。
包装代码是这样的(下面是完整的代码):

<Label Content="{Binding Name}">
    <Label.Template>
       <ControlTemplate TargetType="Label">
          //my controls
       </ControlTemplate>
    </Label.Template>
</Label>

关于"my contols"

我不得不换行:

<Label x:Name="x" Content="{Binding Name}"/>

对此:

<Label x:Name="x" Content="{TemplateBinding Content}"/>

但是专用于错误的标签不再起作用(图形配置已删除):

我猜它不起作用,因为只有内容 属性 从模板化标签转移到内部标签 x; 属性 的内容而不是整个 'state',包括验证错误集合。 但是我怎样才能访问这些错误呢

代码

<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";
    }
}

不是绑定到标签'x'的Validation.Errors,而是可以参考TemplatedParent的Validation.Errors,即Main Label。 我能够将 ControlTemplate 提取到 window 资源,并将此资源用作标签模板,因此我们可以重用此模板。

<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>