WPF DataGrid validation errors not clearing

2019-01-11 02:55发布

So I have a WPF DataGrid, which is bound to an ObservableCollection. The collection has validation on its members, through IDataErrorInfo. If I edit a cell in a way so as to be invalid, and then tab away from it before hitting enter, then come back and make it valid, the cell will stop showing invalid, however, the "!" at the head of the row will still be there, and the ToolTip will reference the previous, invalid value.

14条回答
Bombasti
2楼-- · 2019-01-11 03:21

My scenario was like this:

  1. Model implements IDataErrorInfo
  2. Custom Row Validation rule based on WPF DataGrid Practical Examples -Validation with IDataErrorInfo , that combined all errors from Model using IDataErrorInfo.

    <DataGrid.RowValidationRules>
        <local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
    </DataGrid.RowValidationRules>
    
  3. ValidatesOnDataErrors=True, ValidatesOnExceptions=True, NotifyOnValidationError=True within the binding (which I started with)

This caused multiple access to my validation Engine and eventualy left my DataGrid in inconsistent state (Error notification on row header even when row Valid).

The solution was to remove switches from the binding (point 3.)

I suggest reading through Clearing a DataGrid row validation error too.

查看更多
地球回转人心会变
3楼-- · 2019-01-11 03:21

Not using Mode=TwoWay for DataGridTextColumns solves one version of the problem, however it seems that this problem can appear out of nowhere for other reasons as well.

(Anyone who has a good explanation as of why not using Mode=TwoWay solves this in the first place is probably close to a solution to this problem)

The same thing just happened to me with a DataGridComboBoxColumn so I tried to dig a little deeper.

The problem isn't the Binding in the Control that displays the ErrorTemplate inside DataGridHeaderBorder. It is binding its Visibility to Validation.HasError for the ancestor DataGridRow (exactly as it should be doing) and that part is working.

Visibility="{Binding (Validation.HasError),
                     Converter={StaticResource bool2VisibilityConverter},
                     RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}"/>

The problem is that the validation error isn't cleared from the DataGridRow once it is resolved. In my version of the problem, the DataGridRow started out with 0 errors. When I entered an invalid value it got 1 error so, so far so good. But when I resolved the error it jumped up to 3 errors, all of which were the same.

Here I tried to resolve it with a DataTrigger that set the ValidationErrorTemplate to {x:Null} if Validation.Errors.Count wasn't 1. It worked great for the first iteration but once I cleared the error for the second time it was back. It didn't have 3 errors anymore, it had 7! After a couple of more iterations it was above 10.

I also tried to clear the errors manually by doing UpdateSource and UpdateTarget on the BindingExpressions but no dice. Validation.ClearInvalid didn't have any effect either. And looking through the source code in the Toolkit didn't get me anywhere :)

So I don't have any good solutions to this but I thought I should post my findings anyway..

My only "workaround" so far is to just hide the ErrorTemplate in the DataGridRowHeader

<DataGrid ...>
    <DataGrid.RowStyle>
        <Style TargetType="DataGridRow">
            <Setter Property="ValidationErrorTemplate" Value="{x:Null}"/>
        </Style>
    </DataGrid.RowStyle>
    <!-- ... -->
</DataGrid>
查看更多
淡お忘
4楼-- · 2019-01-11 03:22

If you are seeing an increasing number of errors similar to Meleak, I would be interested to know how your error collection gets populated. In Meleaks version of the problem, he sees three errors (and more) after resolving the invalid data.

In my Data Validation code, I remove the previous instance of a particular error then re-add every time the data changes. For reference, here is a sample:

The Validation Plumbing

#Region " Validation workers "

    Private m_validationErrors As New Dictionary(Of String, String)
    Private Sub AddError(ByVal ColName As String, ByVal Msg As String)
        If Not m_validationErrors.ContainsKey(ColName) Then
            m_validationErrors.Add(ColName, Msg)

        End If
    End Sub
    Private Sub RemoveError(ByVal ColName As String)
        If m_validationErrors.ContainsKey(ColName) Then
            m_validationErrors.Remove(ColName)
        End If
    End Sub


    Public ReadOnly Property [Error]() As String Implements System.ComponentModel.IDataErrorInfo.Error
        Get
            If m_validationErrors.Count > 0 Then
                Return "Shipment data is invalid"
            Else
                Return Nothing
            End If
        End Get
    End Property

    Default Public ReadOnly Property Item(ByVal columnName As String) As String Implements System.ComponentModel.IDataErrorInfo.Item
        Get
            If m_validationErrors.ContainsKey(columnName) Then
                Return m_validationErrors(columnName).ToString
            Else
                Return Nothing
            End If
        End Get
    End Property

#End Region

A Property Being Validated

    Private Sub OnZIPChanged()
        Me.RemoveError("ZIP")
        If _ZIP Is Nothing OrElse _ZIP.Trim = "" Then
            Me.AddError("ZIP", "Please enter a ZIP Code")
        Else
            Select Case _ZIP.Length
                Case 5

                Case 10

                Case Else
                    Me.AddError("ZIP", "Please enter a ZIP Code")
            End Select
        End If
        OnPropertyChanged("CanShip")
    End Sub

So, when the property Changed handler is run, if an error exists in the ValidationErrors dictionary, it is removed, then the value is checked, and it if does not match requirements, an error is added to the dictionary. This helps ensure that only one instance of any error is present in that entities validation error dictionary.

查看更多
神经病院院长
5楼-- · 2019-01-11 03:24

My workaround was not to use Validation.Errors, but use DataGridRow.Item property. If your DataGrid is bound to business objects which implement IDataErrorInfo interface, then you can add IsNotValid property (or IsValid), and make sure Error property returns all errors associated with the object. Then customize default style for DataGridRowHeader:

<Style x:Key="{x:Type DataGridRowHeader}" TargetType="{x:Type DataGridRowHeader}">
    ...
    <Control SnapsToDevicePixels="false"
             Visibility="{Binding RelativeSource={RelativeSource 
                          AncestorType={x:Type DataGridRow}}, 
                          Path=Item.IsNotValid, Converter={StaticResource 
                          Bool2VisibilityConverter}}"
             Template="{Binding RelativeSource={RelativeSource 
                        AncestorType={x:Type DataGridRow}}, 
                        Path=ValidationErrorTemplate}" />

    ...
</Style>

Also in DataGridRow style customize ValidationErrorTemplate, so that it shows error message from DataGridRow.Item.Error proeprty.

查看更多
We Are One
6楼-- · 2019-01-11 03:28

My workaround was to simply remove the property UpdateSourceTrigger="LostFocus" from the binding declaration in each datagridcolumn.

查看更多
爷、活的狠高调
7楼-- · 2019-01-11 03:29

try removing the Mode=TwoWay for each of the DataGridTextColumns from each of the Binding elements.

查看更多
登录 后发表回答