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