WPF TreeView - 键盘导航性能缓慢

WPF TreeView - Slow Performance in Keyboard Navigation

我有一个运行时填充的 TreeView。它是这样定义的:

<TreeView ItemsSource="{Binding Path=RootItem.ChildItems}">

  <TreeView.ItemContainerStyle>
    <Style TargetType="{x:Type TreeViewItem}">
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
      <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    </Style>
  </TreeView.ItemContainerStyle>

  <TreeView.ItemTemplate>
  ...    
  </TreeView.ItemTemplate>
</TreeView>

我正在使用带有 Style / Setter 的部分,使我可以将两个属性 IsSelectedIsExpanded 绑定到同名属性(类型bool) 的基本项,因为我正在使用它们来确定何时以及如何异步加载子项。

树视图上显示的项目都继承自相同的基础class。我绑定的 属性 看起来像这样:

public bool IsExpanded
{
  get { return _IsExpanded; }
  set
  {
    if (HasAsynchChildItems)
      if (value)
        LoadChildItems();
      else if (!value)
        Collapse();
    _IsExpanded = value;
  }
}

HasAsynchChildItems 是一个属性,用于确定是否应异步加载子项。 LoadChildItems() 是加载它们的那个。它是异步的。

只要只有几个项目,一切都很好。

在树下的某个点,有大约 2500 多个项目,应用程序开始变得无响应,加载大约需要 20-30 秒。不仅如此,在加载它们之后,当我更改选择时,应用程序会再次挂起 10 秒以上。

所有导致应用变慢的项目都已 HasAsycnchChildItems 设置为 false。所以LoadChildItems()没有启动。

是我通过样式setter绑定的问题吗?

PS:我也用 Performance Explorer 监控过,Visual Studio 告诉我 90% 的处理时间在 PresentationFramework.ni.dll.

中减少了

有什么想法吗?

编辑

经过更多调查,我需要更新问题:

  1. 我发现加载真的很快。这些项目在从数据库中检索后几乎立即显示。

  2. 选择和扩展也几乎是即时的。

  3. 真正停滞的是应用程序挂起,是通过键盘(向上 + 向下箭头)在节点中导航。每个大约需要 15 秒。如果我通过鼠标单击更改选择,它会立即生效。

我怀疑所有项目都被调用了
尝试

public bool IsExpanded
{
  get { return _IsExpanded; }
  set
  {
      if (_IsExpanded == value) return;
      _IsExpanded = value;
      if (HasAsynchChildItems)
      {
          if (_IsExpanded)
            LoadChildItems();
          else 
            Collapse();
      }        
   }
} 

一旦您使用 LoadChildItems() 保存结果,您就不必再次加载它们

我找到了某种解决方法。

正如我在上面的评论中所述(因此我再次编辑了我的原始问题)我发现问题既不在 DataBinding 中,也不在任何其他 data-related 问题中。这只是 TreeView 控件本身及其处理键盘导航的方式的问题。

如果您通过 UpArrowDownArrow 在 TreeView 中导航, 会出现此问题。

PageDownPageUpMouse-Click 不会发生这种情况。

这就是我所做的。首先,我在我的 TreeView 上启用了虚拟化,仅此一项就已经改进了很多东西,但还不令人满意:

<TreeView ItemsSource="{Binding Path=RootItem.Items}" Grid.Row="1"
          VirtualizingStackPanel.IsVirtualizing="True"
          VirtualizingStackPanel.VirtualizationMode="Recycling"

我做的第二件事是编写自己的自定义事件处理程序来处理 ArrowUp/ArrowDown 在 TreeView 中的导航。我必须补充一点,我完全不喜欢那样写代码(这也是快速测试/实验的结果,所以也许真的有龙!)。此外:我的 TreeView 中的所有项目都继承自基础 class ManagementItem,它提供 IsSelectedIsExpandedIndex(指示项目在parent 的 child 项)。

private void TreeView_PreviewKeyDown(object sender, KeyEventArgs e)
{
  var tv = sender as TreeView;
  var item = tv.SelectedItem as ManagementItem;

  e.Handled = false;

  if (e.Key == Key.Down)
    try
    {
      if (item.IsExpanded)
        item.Items.First().IsSelected = true;
      else
        if (item.Index == item.Owner.Items.Count - 1)
          item.Owner.Owner.Items[item.Owner.Index + 1].IsSelected = true;
        else
          item.Owner.Items[item.Index + 1].IsSelected = true;
      e.Handled = true;
    } catch {}

  if (e.Key == Key.Up)
    try
    {
      if (item.Index == 0)
        item.Owner.IsSelected = true;
      else
        item.Owner.Items[item.Index - 1].IsSelected = true;
      e.Handled = true;
    } catch {}

 }

这大大提高了导航性能。

我确认了 Hemisphera 的解决方法。它实际上使树视图导航按预期工作。

我有一个具有 2 个初始级别的 TreeView:第一个 5K 节点,第二个级别 20K 个节点(每个父节点约 4 个子节点)。默认 WPF 导航从一个节点移动到另一个节点需要 3-4 秒。此实现在 ~100ms

内完成

实施时需要考虑的几件事:

  1. 为您的 TreeViewItems 模型创建基础 class(在 Hemisphera 的代码中,ManagementItem class)。包括所需的属性(如 Hemisphera 的代码所示,IsSelected、IsExpanded、Index 和 ParentOwner)。

  2. 不要忘记在您的基础中实施 INotifyPropertyChanged class。此外,您需要在设置 IsExpanded 和 IsSelected 时触发事件。这是 TwoWayBinding 所必需的:

    private bool _isExpanded;
    public bool IsExpanded
    {
        get { return _isExpanded; }
        set
        {
            _isExpanded = value;
            if (PropertyChanged != null)
                PropertyChanged(this, IsExpandedEventsArgs);
        }
    }
    
    private bool _isSelected;
    public bool IsSelected 
    { 
        get {return _isSelected;}
        set
        {
            _isSelected = value;
            if (PropertyChanged != null)
                PropertyChanged(this, IsSelectedEventArgs);
        }
    }
    
  3. TreeView_PreviewKeyDown 方法中有一些特殊情况需要考虑。即:更高级别的节点没有父节点(或所有者)。 key up 逻辑,从一个高层节点变为一个展开的低层节点时,还需要处理IsExpanded属性