Search code examples
wpfwpfdatagrid

Validate row when underlying data changes


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.


Solution

  • 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