【问题标题】:How can I improve the performance of checking all items in a WPF TreeView?如何提高检查 WPF TreeView 中所有项目的性能?
【发布时间】:2011-08-01 19:15:27
【问题描述】:

我有一个 WPF TreeView,其中每个 TreeViewItem 都有一个复选框。我的实现基于 Josh Smith 的优秀文章中提供的示例:http://www.codeproject.com/KB/WPF/TreeViewWithCheckBoxes.aspx。与文章中一样,我使用的行为是,当一个项目被选中或取消选中时,它的所有子项目都应该分别被选中或取消选中。

我的问题是我的 TreeView 可以包含数以万计的项目。因此,如果最初未选中所有项目,然后我检查根项目,则可能需要 10-20 秒才能更新所有后代。这对我的用户来说太慢了,我想知道是否有办法加快 UI 更新。我相当肯定 PropertyChanged 事件是这里的罪魁祸首,因为当我删除它们时,检查根项目几乎是即时的,尽管子复选框没有得到更新。是否有更好的方法来同时处理更新多个 UI 元素?请注意,我尝试使用容器回收,但这似乎没有帮助。

如果你想尝试自己重现这个,你可以下载前面文章中的项目,然后在 FooViewModel.cs 中,将 CreateFoos() 函数更改为使用此代码:

var root = new FooViewModel("Weapons");
root.IsInitiallySelected = true;

for (int i = 0; i < 400; i++)
{
    var table = new FooViewModel("Table " + i);
    for (int j = 65; j < 91; j++)
    {
        var charItem = new FooViewModel(Convert.ToChar(j).ToString());
        for (int k = 0; k < 3; k++)
        {
            var deepItem = new FooViewModel("Item " + k);
            charItem.Children.Add(deepItem);
        }

        table.Children.Add(charItem);
    }

    root.Children.Add(table);
}

root.Initialize();
return new List<FooViewModel> { root };

感谢您的任何想法,

-克雷格

【问题讨论】:

  • 你是在 Windows Vista/7 上运行这个吗?
  • 正如 dlev 指出的那样,虚拟化可能是您最好的选择。但是对于 Aero 主题,复选框会为其状态转换设置动画。这意味着对于每个 CheckBox,您正在启动一个动画。解决此问题的唯一方法是使用带有不使用 BulletChrome 元素的 ControlTemplate 的自定义 CheckBox 样式。
  • @CodeNaked:实际上,我们使用的是自定义主题,但我会仔细检查这里没有使用动画,因为我同意这也会减慢速度。谢谢。

标签: wpf treeview


【解决方案1】:

呈现包含 10,000 个项目的树视图的 UI 可能是需要重新设计的 UI。 (虽然不一定 - 如果您的文件系统足够多,浏览文件夹对话框本质上就是这样做的。)

假设您不尝试虚拟化,您可以尝试两件事:

  1. TreeViewItem.IsExpanded 绑定到节点视图模型中的布尔属性。如果其父节点的 IsExpanded 属性为 true,则节点应仅引发 PropertyChanged 事件。这将防止不可见的叶节点引发可视树必须忽略的事件。 (您可能还会发现,当父级的 IsExpanded 属性变为 true 时,您需要在其 IsChecked 属性上增加一个节点 PropertyChanged。)当树完全展开时,这仍然会出现问题,但除非您一开始就以这种状态向用户呈现树,用户将不得不努力使控件进入开始遇到性能问题的状态。

  2. 永远不要在父节点更新其后代时引发 PropertyChanged 事件。相反,让拥有的视图模型在TreeViewItemsSource 绑定到的属性上提升PropertyChanged。这将迫使整个控件重新渲染,但这很可能比处理来自所有节点的单个更新更快。 (当然,您需要为节点实现某种方式来通知其父节点它刚刚更新了它的后代 - 例如引发一个事件 - 并以某种方式告诉一个节点如果它正在更新它不应该引发这个事件后代,因为它的父母告诉它。)

【讨论】:

  • 您的第一个建议效果很好。对于显示这么多项目的树视图,场景是让用户使用模式-表-列层次结构浏览和选择 SQL DB 中的项目。对于大型数据库,我们可以得到很多项目,尽管大多数不会扩展。
【解决方案2】:

不幸的是,WPF 在处理看似复杂的可视化树时几乎总是会遇到性能问题。此外,如果您的视图模型仅实现 INotifyPropertyChanged(而不是从 DependencyObject 继承),那么更新 10000 多个绑定通常也会很慢。

作为参考,容器回收只有在您的 ItemsPanel 被虚拟化时才有用(我怀疑它正在考虑您的性能问题。)

说到虚拟化,这可能是解决这个问题的最佳方式。不幸的是,这可能意味着编写一个自定义的 VirtualizingPanel。您可以通过关注链here 开始。

如果您不想采取这一步(这是可以理解的),那么您应该考虑如何将一些数据从树中卸载,也许是一些辅助控件,这样您就可以限制有多少项目是一键更新。

【讨论】:

  • 我的理解是WPF TreeViews已经使用了VirtualizingStackPanels;见bea.stollnitz.com/blog/?p=338。我确实尝试设置 VirtualizingStackPanel.IsVirtualizing="True" 和 VirtualizingStackPanel.VirtualizationMode="Recycling" 但这并没有提高性能。
猜你喜欢
  • 2021-03-21
  • 2012-09-25
  • 1970-01-01
  • 1970-01-01
  • 2023-03-24
  • 1970-01-01
  • 2012-06-29
  • 1970-01-01
  • 2013-02-17
相关资源
最近更新 更多