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