Loading a large amount of data by the default procedure in our WPF TreeView control can take time and consume many resources. But a little-known trick in Windows Presentation Framework (WPF) is lazy loading, which gives you the ability to load data on demand quickly and without using expensive resources to hold all the data.
The WPF TreeView supports lazy loading, where TreeView loads the child nodes on demand when they are requested. The child nodes can be loaded from services or databases when the parent node is expanded. For example, if you are developing an application like the file explorer TreeView in Windows, a lot of time will be taken when you loop all the drives and files. In such cases, the best way to load the child nodes is to perform lazy loading (load on demand) when the child nodes are expanded.
In this article, we are going to implement lazy loading using MVVM pattern in just three steps, with the use case of a Windows file browser:
Create a class named Directory with the property Name, which denotes the name of the drive or directory, and the property FullName, which denotes the full path of the directory. The Directory class should be derived from NotificationObject, which has the implementation for the INotifyPropertyChanged interface.
Model
public class Directory : NotificationObject { private string name; private bool hasChildNodes; private string fullName; public string Name { get { return name; } set { name = value; RaisePropertyChanged("Name"); } } public string FullName { get { return fullName; } set { fullName = value; RaisePropertyChanged("FullName"); } } public bool HasChildNodes { get { return hasChildNodes; } set { hasChildNodes = value; RaisePropertyChanged("HasChildNodes"); } } }
Next, create a ViewModel class with the Directories property. This property holds the driver details of the system to create a file-exploring TreeView. When a user expands the driver, the child nodes will be loaded on demand.
ViewModel
public class ViewModel { public ObservableCollection<Directory> Directories { get; set; } public ViewModel() { this.Directories = this.GetDrives(); } private ObservableCollection<Directory> GetDrives() { ObservableCollection<Directory> directories = new ObservableCollection<Directory>(); DriveInfo[] driveInfos = DriveInfo.GetDrives(); foreach (DriveInfo driveInfo in driveInfos) { directories.Add(new Directory() { Name = driveInfo.Name, FullName = driveInfo.Name, HasChildNodes = true }); } return directories; } }
After creating the ViewModel class:
XAML
<Window.DataContext> <local:ViewModel/> </Window.DataContext> <Syncfusion:SfTreeView ItemsSource="{Binding Directories}" ItemHeight="30" HorizontalAlignment="Left" IsAnimationEnabled="True" Margin="25,0,0,0" VerticalAlignment="Top" Width="250"> <Syncfusion:SfTreeView.ItemTemplate> <DataTemplate> <Label VerticalContentAlignment="Center" Content="{Binding Name}" FocusVisualStyle="{x:Null}" /> </DataTemplate> </Syncfusion:SfTreeView.ItemTemplate> </Syncfusion:SfTreeView>
Now, TreeView will be displayed with two nodes. Let’s enable lazy loading in TreeView by creating LoadOnDemandCommand. This will control the expander visibility based on the return value of the CanExecute command. The loading of child nodes is done in the Execute command.
public class ViewModel { public ICommand TreeViewLoadOnDemandCommand { get; set; } public ViewModel() { TreeViewLoadOnDemandCommand = new DelegateCommand(ExecuteOnDemandLoading, CanExecuteOnDemandLoading); } }
Return true from the CanExecute method to show the expander for the node if the node has child nodes, or else return false. Here, the visibility of the expander is returned based on the HasChildNodes property in the Directory. The HasChildNodes property is set based on whether the directory has subdirectories or files when the node is populated.
private bool CanExecuteOnDemandLoading(object sender) { var hasChildNodes = ((sender as TreeViewNode).Content as Directory).HasChildNodes; if (hasChildNodes) return true; else return false; }
Load the child nodes in the Execute method. Call the Execute method when a user expands the tree node.
Show or hide the busy indicator in the place of the expander by setting TreeViewNode.ShowExpanderAnimation until the data is fetched.
Retrieve the inner directories and files from the GetDirectories method. Populate the child nodes by calling the TreeViewNode.PopulateChildNodes method by passing the child items collection that is obtained from GetDirectories.
During command execution, the expanding operation will not be handled by TreeView. So, you have to set the TreeViewNode.IsExpanded property to true to expand the tree node after populating child nodes.
You should skip the population of child items every time the node expands, based on the TreeViewNode.ChildNodes count.
private void ExecuteOnDemandLoading(object obj) { var node = obj as TreeViewNode; // Skip the repeated population of child items every time the node expands. if (node.ChildNodes.Count > 0) { node.IsExpanded = true; return; } //Animation starts for expander to show progress of load on demand. node.ShowExpanderAnimation = true; Directory Directory = node.Content as Directory; Application.Current.MainWindow.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(async () => { await Task.Delay(1000); //Fetching child items to add. var items = GetDirectories(Directory); // Populating child items for the node in on demand. node.PopulateChildNodes(items); if (items.Count() > 0) //Expand the node after child items are added. node.IsExpanded = true; //Stop the animation after load on demand is executed. If animation is not stopped, it remains after execution of load on demand. node.ShowExpanderAnimation = false; })); } public IEnumerable<Directory> GetDirectories(Directory directory) { var directories = new ObservableCollection<Directory>(); var dirInfo = new DirectoryInfo(directory.FullName); foreach (DirectoryInfo directoryInfo in dirInfo.GetDirectories()) { try { directories.Add(new Directory() { Name = directoryInfo.Name, HasChildNodes = directoryInfo.GetDirectories().Length > 0 || directoryInfo.GetFiles().Length > 0, FullName = directoryInfo.FullName }); } catch { } } foreach (FileInfo fileInfo in dirInfo.GetFiles()) { directories.Add(new Directory() { Name = fileInfo.Name, HasChildNodes = false, FullName = fileInfo.FullName }); } return directories; }
Finally, bind the TreeView.LoadOnDemandCommand property to the ViewModel’s TreeViewLoadOnDemandCommand property.
That’s all. Lazy loading implementation is completely done for WPF TreeView.
<Syncfusion:SfTreeView ItemsSource="{Binding Directories}" LoadOnDemandCommand="{Binding TreeViewLoadOnDemandCommand}" ItemHeight="30" HorizontalAlignment="Left" IsAnimationEnabled="True" Margin="25,0,0,0" VerticalAlignment="Top" Width="250"> <Syncfusion:SfTreeView.ItemTemplate> <DataTemplate> <Label VerticalContentAlignment="Center" Content="{Binding Name}" FocusVisualStyle="{x:Null}" /> </DataTemplate> </Syncfusion:SfTreeView.ItemTemplate> </Syncfusion:SfTreeView>
The complete project explained in this blog is available in this GitHub repository. Also, our demos are now available in the Microsoft Store, and our .NET Core demos are available in the App Center.
I hope, you enjoyed this blog about the procedure to perform lazy loading in the Syncfusion WPF TreeView control using MVVM pattern. You can learn more about WPF TreeView’s features here.
Syncfusion has over 90 essential WPF controls like DataGrid, Charts, Diagram, and PDF Viewer. Give these controls a shot by downloading the setup from our latest release.
For existing customers, the newest version is available for download from the license and downloads page. If you are not yet a customer, you can try our 30-day free trial to check out these new features.
Also, if you wish to send us feedback or would like to submit any questions, please feel free to post them in the comments section of this blog post. You can also contact us through our support forums, feedback portal, or Direct-Trac support system.