Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

LazyLoad WPF Treeview with Large Amount of Two Level Broad Data

4.17/5 (6 votes)
29 Feb 2012CPOL3 min read 63.5K   1.2K  
Enhance performance of WPF Treeview control while loading large amount of two level broad data

Introduction

This article will introduce the hand-on experience the author and his team members used to solve the slow performance of WPF Treeview Control which contains two levels of broad data.

Background

Currently, I am working on enhancing performance with a Job page. This page has a two levels depth TreeView control. The top level displays the list of jobs, and the second level displays the number of job associated items.

Here is the initial page of jobs. You can see the job associated items when you expand the job.

jobs.png

In the current application, the numbers of jobs get increased by 10 everyday, and each job has around 150 Job items on average.

This page gets slower and slower as more and more jobs gets created until it reaches the maximum acceptance threshold.

Our development team looked at that page, and we discovered that we load all the jobs and associated job items at once, and passed it to the client, and client uses TreeView and HierarchicalDataTemplate to display the job and job items. As a consequence, the more job and job items get added, the slower is will be for sure.

I did some investigation/Googling/experiments to see if we can lazy load tree without any additional change. I found that the current WPF TreeView control will eager load two level node, i.e., If you expand a node, it will load all the child and grand child nodes, but will not load any node which is below the grand child nodes. It doesn’t help us because we have only two levels of nodes.

After a discussion with team members, we comes up with two possible approaches:

Approach 1: There is no need to use WPF TreeView control since we have only two levels of data. We could use ListView control to display job collection, after user clicks on the job, the associated job item can be retrieved from server, and be displayed.

Approach 2: We still use TreeView control; however, we will overwrite the ExpandEvent. It will allows us to lazyload Job Items. I found a fix from the Microsoft website here.

Approach 2 was chosen since we would like to make a minimum change to the current application.

Using the Code

In the example, you can switch between my original implementation and the performance improved implementation. You can open the App.xaml, and change the StartupUri value. If StartupUrl="Job.xaml", then it is the original implementation, and the performance is poor, if StartupUrl="LazyJob.xaml", then the performance is very fast.

Here is the sample code for approach 2:

Job.xaml

Job UI xaml page hooks up two events, Loaded and Unloaded event. It also defines two templates, JobItemTemplate and JobTemplate. Each template contains a checkbox control and a TextBlock control. Xaml page also has a empty TreeView which will be updated later dynamically.

XML
<Window x:Class="JobLazyLoad.LazyJob"
    Loaded="OnLoaded" Unloaded="OnUnloaded"> 
    <Window.Resources>
        <DataTemplate x:Key = "JobItemTemplate" >
           <CheckBox IsChecked="{Binding Path=IsSelected}">
                <TextBlock Text="{Binding Path=Name}"/>
            </CheckBox>
        </DataTemplate>
        <DataTemplate x:Key = "JobTemplate">

            <CheckBox IsChecked="{Binding Path=IsSelected}">
                 <TextBlock Text="{Binding Path=Name}"/>
            </CheckBox> 
        </DataTemplate> 
    </Window.Resources> 
    <Grid> 
        <TreeView x:Name="JobTree"/> 
    </Grid> 
</Window>

Here is Job.xmls.cs code behind file.

While the page gets loaded, it hooks up TreeViewItem expand event with customized eventhandler (OnTreeItemExpanded), it also loads the first level of node (LoadRootNodes). Once use clicks and expands on the tree, the OnTreeItemExpanded gets invoked, and it loads the associated JobItemCollection.

C#
public partial class LazyJob : Window
{ 
     private object _dummyNode = null;
     private JobViewModel _viewModel;
     public LazyJob()
     { 
         InitializeComponent();
         InitializeViewModel();
     } 

     private void InitializeViewModel()
     { 
          _viewModel = new JobViewModel();
          this.DataContext = _viewModel;
     } 

     private void OnLoaded(object sender, RoutedEventArgs e)
     {
          JobTree.AddHandler(TreeViewItem.ExpandedEvent,
              new RoutedEventHandler(OnTreeItemExpanded)); 

          LoadRootNodes();
     }

     private void OnUnloaded(object sender, RoutedEventArgs e)
     {
         JobTree.RemoveHandler(TreeViewItem.ExpandedEvent,
              new RoutedEventHandler(OnTreeItemExpanded)); 
     }

     private void OnTreeItemExpanded(object sender, RoutedEventArgs e)
     {
         TreeViewItem item = e.OriginalSource as TreeViewItem;
         if (item != null && item.Items.Count == 1 && item.Items[0] == _dummyNode) 
         {
             item.Items.Clear();
             Domain.Job job = item.Header as Domain.Job;
             foreach (var acquisitionItem in _viewModel.JobItemCollection)
             {
                  TreeViewItem subItem = new TreeViewItem();
                  subItem.Header = acquisitionItem;
                  subItem.HeaderTemplate = FindResource(
                      "JobItemTemplate") as DataTemplate;
                  item.Items.Add(subItem);
             }
         }
     }  

     private void LoadRootNodes()
     {
         JobTree.Items.Clear();
         foreach (Domain.Job job in _viewModel.LazyJobCollection)
         {
             TreeViewItem item = new TreeViewItem();
             item.Header = job;
             item.HeaderTemplate = FindResource("JobTemplate") as DataTemplate; 
             item.Items.Add(_dummyNode);
             JobTree.Items.Add(item);
        } 
    }
}

JobViewModel class has two methods, one of loading only the Job object collection, and another method will load JobItemCollection for expanded job.

C#
public class JobViewModel
{
    public int CurrentJobId { get; set; }

    public IList<Domain.Job> LazyJobCollection
    {
        get { return new JobService().GetLazyJobCollection(); }
    }  

    public IList<JobItem> JobItemCollection
    { 
        get { return new JobService().GetJobItemCollection(CurrentJobId); } 
    } 
}

That is it. In approach 2, we load job items only when we need it. After implementing the change, it becomes about 150 times faster to get this page displayed.

Reference

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)