wpfvb.netvalidationlistcollectionviewinotifydataerrorinfo

How to listen to INotifyDataErrorInfo using a CollectionView


I have the following scenario:

XAML:

<ListView Name="lsv_edit_selectNode" Grid.Row="2" Grid.Column="0"  Grid.ColumnSpan="4" 
    Grid.RowSpan="17" IsSynchronizedWithCurrentItem="True" SelectionMode="Single" 
    ItemsSource="{Binding Path=Nodes.CollectionView, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}">

Where Nodes is a custom ObservableCollection that contains a ListCollectionView:

Public Class FilterableObservableCollection(Of T)
    Inherits ObservableCollection(Of T)
    Implements INotifyPropertyChanged, INotifyCollectionChanged
    Public Property CollectionView As ListCollectionView
        Get
            Return _collectionView
        End Get
        Protected Set(value As ListCollectionView)
            If value IsNot _collectionView Then
                _collectionView = value
                NotifyPropertyChanged()
            End If
        End Set
    End Property
    'etc.

T in this case is a Node object, with many properties including the one I'm interested in (call it NodeResults):

Public Class Node
    Inherits ObservableValidatableModelBase
        Public Property NodeResults as NodeResults
            Set
                SetAndNotify(_nodeResults, Value) ' INotifyPropertyChanged
            AddHandler _nodeResults.ErrorsChanged, AddressOf BubbleErrorsChanged ' INotifyDataErrorInfo?
             End Set
        End Property
        ' etc.

and NodeResults:

Public Class NodeResults
    Inherits ObservableValidatableModelBase

        ' many properties here, each validated using custom Data Annotations. This works, when I bind a text box directly to here, for example.

ObservableValidatableModelBaseImplements INotifyDataErrorInfo, and stores its errors in a collection called errors:

Private errors As New Dictionary(Of String, List(Of String))()
Public ReadOnly Property HasErrors As Boolean Implements INotifyDataErrorInfo.HasErrors
    Get
        Return errors.Any(Function(e) e.Value IsNot Nothing AndAlso e.Value.Count > 0)
    End Get
End Property

Public Function GetErrors(propertyName As String) As IEnumerable Implements INotifyDataErrorInfo.GetErrors
    Try
        If Not String.IsNullOrEmpty(propertyName) Then
            If If(errors?.Keys?.Contains(propertyName), False) _
                AndAlso If(errors(propertyName)?.Count > 0, False) Then ' if there are any errors, defaulting to false if null
                Return errors(propertyName)?.ToList() ' or Nothing if there are none.
            Else
                Return Nothing
            End If
        Else
            Return errors.SelectMany(Function(e) e.Value.ToList())
        End If
    Catch ex As Exception
        Return New List(Of String)({"Error getting errors for validation: " & ex.Message})
    End Try
End Function

Public Event ErrorsChanged As EventHandler(Of DataErrorsChangedEventArgs) Implements INotifyDataErrorInfo.ErrorsChanged

Public Sub NotifyErrorsChanged(propertyName As String)
    ErrorsChangedEvent?.Invoke(Me, New DataErrorsChangedEventArgs(propertyName))
End Sub

Public Sub BubbleErrorsChanged(sender As Object, e As DataErrorsChangedEventArgs)
    If TypeOf (sender) Is ObservableValidatableModelBase Then
        errors = DirectCast(sender, ObservableValidatableModelBase).errors
    End If
    NotifyErrorsChanged(String.Empty)
End Sub

What I want to happen is for the individual Nodes in the CollectionView to notify the ListView, so that it highlights the individual entries that are invalid (i.e. have a NodeResults that is invalid) on the screen.

My first instinct is that Node somehow needs to subscribe to and bubble the NodeResults' ErrorsChanged event, hence the BubbleErrorsChanged method on the ObservableValidatableModelBase class - but that doesn't seem to work.

Another possibility is - does ListView even have a default template for displaying validation exceptions? If not, should something like this work? (it doesn't...)

<ListView.ItemContainerStyle>
    <Style TargetType="ListViewItem">
        <Setter Property="Background" Value="{Binding Path=(Validation.Errors).CurrentItem, Converter={StaticResource ValidationExceptionToColourConverter}}"/>
     </Style>
 </ListView.ItemContainerStyle>

Where ValidationExceptionToColourConverter simply returns Brushes.Red or Brushes.White depending on whether the error is Nothing or not.

Note: Binding a text box directly to Nodes.NodeResults.SomeProperty works fine, giving the results I'm expecting.


Solution

  • I needed the following:

    <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
            <Setter Property="Background" Value="{Binding Path=HasErrors, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource BooleanToBrushConverter}}"/>
        </Style>
    </ListView.ItemContainerStyle>