question

KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 asked ·

Clarify INotifyCollectionChanged

I have a TreeView bound to an ObservableCollection with a ViewModel. The TreeView includes folder-like elements that will have child items. Most times opening the app we won't need to view those child items so I'm not filling them in when the app opens, rather each folder-like item has a place-holder item to indicate there ARE child items. I've included this in my TreeView XAML to call a routine when the TreeViewItem is expanded:

 TreeViewItem.Expanded="TVExp"

That is working fine but the new child items do not show in the TreeView unless I call .Refresh() on the data items.

Now, ObservableCollection states: "Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed." But that doesn't seem to really be the case as the child items added to the data don't show in the UI.

It appears I have to implement the INotifyCollectionChanged interface. True? VisualStudio (amazingly!) will add code designed to help with this but I'm not sure where to start. Does it get implemented on the ViewModel or on the ObservableCollection, or both?

I imagine it's implemented something like INotifyPropertyChanged but I've not manged to find a good example that's simple enough for my newbee .net knowledge to follow.

Can someone give an example of that, or point me to one?

Many thanks,
Ken




windows-wpf
· 1
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Ken, sample in which language?

0 Votes 0 ·
KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

Hi Again Peter! VB would be best.

Thank you (again!)

·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

Hi Ken, try to follow the demo. At first instead of child data dummy data will be included. The demo uses attached property (for TreeViewItem) to catch the Expanded event. If child data included only one dummy data object the real child data will be loaded.

XAML:

 <Window x:Class="Window024"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApp1.WpfApp024"
         mc:Ignorable="d"
         Title="Ken Krugh TreeView lazy loading" Height="450" Width="800">
   <Window.DataContext>
     <local:ViewModel/>
   </Window.DataContext>
   <Grid>
     <TreeView x:Name="FolderView"
               ItemsSource="{Binding Items}"
               VirtualizingPanel.VirtualizationMode="Recycling"
               VirtualizingPanel.IsVirtualizing="True">
       <TreeView.ItemContainerStyle>
         <Style TargetType="{x:Type TreeViewItem}">
           <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
           <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
           <Setter Property="local:TviBehavior.Expanded" Value="True"/>
         </Style>
       </TreeView.ItemContainerStyle>
       <TreeView.Resources>
         <HierarchicalDataTemplate DataType="{x:Type local:Data1}" ItemsSource="{Binding Children}">
           <StackPanel Orientation="Horizontal">
             <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
           </StackPanel>
         </HierarchicalDataTemplate>
         <HierarchicalDataTemplate DataType="{x:Type local:DataDummy}">
           <StackPanel Orientation="Horizontal">
             <TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
           </StackPanel>
         </HierarchicalDataTemplate>
         <HierarchicalDataTemplate DataType="{x:Type local:Data2}">
           <StackPanel Orientation="Horizontal">
             <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
           </StackPanel>
         </HierarchicalDataTemplate>
       </TreeView.Resources>
     </TreeView>
   </Grid>
 </Window>



 Imports System.Collections.ObjectModel
 Imports System.ComponentModel
 Imports System.Runtime.CompilerServices
    
 Namespace WpfApp024
    
   Public Class ViewModel
    
     Private col As New ObservableCollection(Of Data0)
     Private cvs As New CollectionViewSource
    
     Public ReadOnly Property Items As ICollectionView
       Get
         If cvs.Source Is Nothing Then
           cvs.Source = col
           Model.LoadFirstData(col)
         End If
         Return cvs.View
       End Get
     End Property
    
   End Class
    
   Public Class Data0
     Implements INotifyPropertyChanged
     Public Property Name As String
     Public Property IsExpanded As Boolean = False
    
     Private _IsSelected As Boolean = True
     Public Property IsSelected As Boolean
       Get
         Return Me._IsSelected
       End Get
       Set(value As Boolean)
         Me._IsSelected = value
         OnPropChanged()
       End Set
     End Property
    
     Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
     Friend Sub OnPropChanged(<CallerMemberName> Optional propName As String = "")
       RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
     End Sub
   End Class
    
   Public Class Data1
     Inherits Data0
     Public Property Children As New ObservableCollection(Of Data0)
   End Class
    
   Public Class Data2
     Inherits Data0
    
   End Class
    
   Public Class DataDummy
     Inherits Data0
    
   End Class
    
   Public Class TviBehavior
     Public Shared ReadOnly ExpandedProperty As DependencyProperty =
     DependencyProperty.RegisterAttached(
     "Expanded",
     GetType(Boolean),
     GetType(TviBehavior),
     New UIPropertyMetadata(False, AddressOf ViewModelChanged))
    
     Public Shared Function GetExpanded(tvi As TreeViewItem) As Boolean
       Return CType(tvi.GetValue(ExpandedProperty), Boolean)
     End Function
    
     Public Shared Sub SetExpanded(tvi As TreeViewItem, value As Boolean)
       tvi.SetValue(ExpandedProperty, value)
     End Sub
    
     Private Shared Sub ViewModelChanged(depObj As DependencyObject, e As DependencyPropertyChangedEventArgs)
       Dim item = TryCast(depObj, TreeViewItem)
       If item Is Nothing OrElse Not CType(e.NewValue, Boolean) Then Exit Sub
       If CType(e.NewValue, Boolean) Then
         AddHandler item.Expanded, AddressOf OnTreeViewItemExpanded
       Else
         RemoveHandler item.Expanded, AddressOf OnTreeViewItemExpanded
       End If
     End Sub
    
     Private Shared Sub OnTreeViewItemExpanded(sender As Object, e As RoutedEventArgs)
       If Not Object.ReferenceEquals(sender, e.OriginalSource) Then Exit Sub
       Dim item = TryCast(e.OriginalSource, TreeViewItem)
       Dim d = TryCast(item.DataContext, Data1)
       If (d.Children.Count = 1 AndAlso d.Children(0).GetType IsNot GetType(DataDummy)) Then Exit Sub
       d.Children.Clear()
       Model.LoadSecondData(d)
     End Sub
   End Class
    
   Module Model
     Friend Sub LoadFirstData(col As ObservableCollection(Of Data0))
       For i = 1 To 100
         Dim d As New Data1 With {.Name = $"Node {i}"}
         d.Children.Add(New DataDummy With {.Name = "Dummy"})
         col.Add(d)
       Next
     End Sub
     Friend Sub LoadSecondData(d1 As Data1)
       For i = 1 To 10
         Dim d2 As New Data2 With {.Name = $"Node {i}"}
         d1.Children.Add(d2)
       Next
     End Sub
   End Module
    
 End Namespace
·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

KenKrugh-6537 avatar image
0 Votes"
KenKrugh-6537 answered ·

Thanks again Peter but I've got yet another newbee question.

You seem to have implemented a custom property (is Attached Property the correct terminology?) that triggers on the ViewModel getting updated. Do I have that right?

Does that trigger INotifyCollectionChanged? Just trying to better understand because I had expected to see the code "Implements INotifyCollectionChanged" similar to the use of "Implements INotifyPropertyChanged" in the ViewModel.

All Best,
Ken

· 1 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Ken, the example has the base class Data0. This class has an IsSelected property. This is not an attached property (DependencyProperty.RegisterAttached) that can be bounded to other properties and responds to changes in the values of these other properties. The base class Data0 informs the UI via NotifyPropertyChanged if IsSelected has changed. This gives the UI the signal to update the display. The ViewModel has the Items property, which contains a view of a list (ObservableCollection) of objects of type Data0. The list implements INotifyCollectionChanged, INotifyPropertyChanged and thus signals changes in the list, not the content of the list elements. The TreeView only includes elements of type Data1, Data2 and DataDummy for the nodes (HierarchicalDataTemplate). These types inherit from Data0 and can therefore be included in the list of data elements. If a node is opened that contains only one DataDummy, it is reloaded. The changed Children in Data1 causes the UI to be updated.

0 Votes 0 ·