问题
Imagine a DataGrid with its ItemsSource
set to an ObservableCollection
. This collection provides a view model for each row in the DataGrid
. The view model in turn provides the data that is displayed in one row and a command that may change this data. Additionally, I added a rule to the RowValidationRules
property of DataGrid
. This validation rule works fine in case I enter invalid data.
However, if I change the invalid data to valid data via the command the view model provides, the row validation rule only gets triggered again if the current row in DataGrid
loses focus. Hence, the displayed data may be actually valid, but the DataGrid
still displays a red exclamation mark showing it has invalid data. This remains the case until the current row loses focus or I enter valid data again.
How do I force a second validation of the current row? I already set ValidatesOnTargetUpdated="True"
but this didn't solve the problem. I also have implemented the INotifyPropertyChanged interface but this also didn't fix the problem.
Solution
As user mm8 pointed out, INotifyDataErrorInfo
is the approach to go. I removed the row validation rule and exposed a property named HasErros
in my view model that proxies the HasErrors
property of my model that in turn implements INotifyDataErrorInfo
. Next I added a custom RowValidationErrorTemplate
<DataGrid.RowValidationErrorTemplate>
<ControlTemplate>
<Grid>
<Ellipse Width="12" Height="12" Fill="Red"/>
<Label Content="!" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Foreground="White" FontSize="11"/>
</Grid>
</ControlTemplate>
</DataGrid.RowValidationErrorTemplate>
and created the following custom style for DataGridRowHeader
<Style x:Key="MyDataGridRowHeaderStyle" TargetType="{x:Type DataGridRowHeader}">
<!-- ... -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridRowHeader}">
<Border>
<Grid>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<Control SnapsToDevicePixels="True"
Template="{Binding ValidationErrorTemplate, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"
Visibility="{Binding Path=HasErrors, UpdateSourceTrigger=PropertyChanged, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
</Border>
<!-- ... -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note the binding of Visibility
. The HasErrors
property is the proxy property I mentioned above.
And finally, use that style in the DataGrid
as follows
<DataGrid RowHeaderStyle="{StaticResource MyDataGridRowHeaderStyle}"
...
An implementation of BoolToVisibilityConverter
can be found here.
回答1:
Instead of adding a ValidationRule
to the RowValidationRules
property of the DataGrid
you could implement the INotifyDataErrorInfo interface in the view model class and raise its ErrorChanged
event whenever you want to refresh the status of a row/item.
This is the MVVM way of implementing data validation. Using a ValidationRule
is not.
WPF 4.5: Validating Data in Using the INotifyDataErrorInfo Interface: https://social.technet.microsoft.com/wiki/contents/articles/19490.wpf-4-5-validating-data-in-using-the-inotifydataerrorinfo-interface.aspx
回答2:
You can handle CellEditEnding
, find the Rows and call UpdateSources
of the BindingGroup
:
private void dataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
DataGrid dg = sender as DataGrid;
foreach (var r in dg.Items)
{
DataGridRow row = dg.ItemContainerGenerator.ContainerFromItem(r) as DataGridRow;
if (row != null)
row.BindingGroup.UpdateSources();
}
}
Also, note to set UpdateSourceTrigger=PropertyChanged
for your bindings too.
Edit
Note that you can also use EventTrigger
:
<DataGrid x:Name="dataGrid1" ItemsSource="{Binding Models}" DataContext="{Binding}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="CellEditEnding" >
<i:InvokeCommandAction Command="{Binding PCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=DataGrid, Mode=FindAncestor}}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
in which PCommand
is in the ViewModel and:
private void DoPCommand(object parameter)
{
DataGrid dg = parameter as DataGrid;
if (dg != null)
foreach (var r in dg.Items)
{
DataGridRow row = dg.ItemContainerGenerator.ContainerFromItem(r) as DataGridRow;
if (row != null)
row.BindingGroup.UpdateSources();
}
}
来源:https://stackoverflow.com/questions/42476236/validate-row-when-underlying-data-changes