65.9K
CodeProject 正在变化。 阅读更多。
Home

LazyLoad WPF Treeview 处理大量两级宽数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (6投票s)

2009年10月19日

CPOL

3分钟阅读

viewsIcon

63988

downloadIcon

1255

提高 WPF Treeview 控件在加载大量两级宽数据时的性能

引言

本文将介绍作者及其团队成员用来解决包含两级广域数据的 WPF Treeview 控件性能慢的实践经验。

背景

目前,我正在通过一个作业页面来增强性能。 此页面具有两级深度的 TreeView 控件。 顶层显示作业列表,第二层显示与作业关联的项目的数量。

这是作业的初始页面。当您展开作业时,可以看到与作业相关的项目。

jobs.png

在当前应用程序中,作业数量每天增加 10 个,每个作业平均有大约 150 个作业项目。

随着越来越多的作业被创建,此页面的速度会越来越慢,直到达到最大可接受阈值。

我们的开发团队查看了该页面,我们发现我们一次性加载了所有作业和相关的作业项目,并将它们传递给客户端,客户端使用 TreeViewHierarchicalDataTemplate 来显示作业和作业项目。 因此,添加的作业和作业项目越多,速度肯定会越慢。

我做了一些调查/谷歌搜索/实验,看看我们是否可以在没有任何额外更改的情况下进行懒加载树。 我发现当前的 WPF TreeView 控件会急切地加载二级节点,即,如果展开一个节点,它将加载所有子节点和孙节点,但不会加载孙节点以下的任何节点。 这对我们没有帮助,因为我们只有两层节点。

在与团队成员讨论后,我们提出了两种可能的方法

方法 1:由于我们只有两级数据,因此无需使用 WPF TreeView 控件。 我们可以使用 ListView 控件来显示作业集合,用户点击作业后,可以从服务器检索关联的作业项目并显示。

方法 2:我们仍然使用 TreeView 控件; 但是,我们将覆盖 ExpandEvent。 这将允许我们进行懒加载作业项目。 我在 Microsoft 网站上找到了一个修复方法,这里

选择了方法 2,因为我们希望对当前应用程序进行最小的更改。

Using the Code

在示例中,您可以在我的原始实现和性能改进的实现之间切换。 您可以打开 App.xaml,并更改 StartupUri 值。 如果 StartupUrl="Job.xaml",那么它是原始实现,性能很差,如果 StartupUrl="LazyJob.xaml",那么性能就非常快。

这是方法 2 的示例代码

Job.xaml

Job UI xaml 页面挂钩了两个事件,LoadedUnloaded 事件。 它还定义了两个模板,JobItemTemplateJobTemplate。 每个模板包含一个 checkbox 控件和一个 TextBlock 控件。 Xaml 页面还有一个空的 TreeView,稍后将动态更新。

<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>

这是 Job.xmls.cs 后台代码文件。

当页面加载时,它使用自定义的 eventhandler (OnTreeItemExpanded) 挂钩 TreeViewItem 展开事件,它还加载了第一级节点 (LoadRootNodes)。 一旦使用点击并在树上展开,OnTreeItemExpanded 被调用,它会加载关联的 JobItemCollection

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 类有两种方法,一种只加载 Job 对象集合,另一种方法将为展开的作业加载 JobItemCollection

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); } 
    } 
}

就是这样。 在方法 2 中,我们仅在需要时才加载作业项目。 在实现更改后,显示此页面的速度提高了大约 150 倍。

参考

© . All rights reserved.