Validate row when underlying data changes

房东的猫 提交于 2021-02-04 08:08:27

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!