Hi Again Peter! VB would be best.
Thank you (again!)
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
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
Hi Again Peter! VB would be best.
Thank you (again!)
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
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