You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF TreeListView条件自动展开优化:移除Task.Delay及相关改进

WPF树形控件自动展开优化问题

我在WPF视图中实现了带搜索过滤的树形控件(使用TreeListView,行为与TreeView一致),需求是当过滤条件或搜索字符串变更时,自动展开所有匹配节点。由于自定义ControlTemplate会忽略原TreeView的ItemsSource,我用了以下临时方案:

现有代码

ControlTemplate定义

<ControlTemplate x:Key="TreeControlTemplate">
    <Border>
        <treeList:TreeListView ItemsSource="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type treeList:TreeListView}}}"
        <!-- 其他模板内容 -->
    </Border>
</ControlTemplate>

TreeListView使用代码

<treeList:TreeListView Template="{StaticResource TreeControlTemplate}"
                       DataContext="{Binding RootItem.FilteredChildren}" />

后台展开逻辑

private ViewModel _viewModel;

public View()
{
    InitializeComponent();
    DataContextChanged += OnDataContextChanged;
    Tree.DataContextChanged += Tree_DataContextChanged;
}

private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (DataContext is ViewModel viewModel)
    {
        DataContextChanged -= OnDataContextChanged;
        _viewModel = viewModel;
    }
}

private async void Tree_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    await Task.Delay(500);
    if (_viewModel.ShouldExpandTreesAfterUpdate())
        ExpandTree(Tree);
}

private void ExpandTree(DependencyObject parent)
{
    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        var child = VisualTreeHelper.GetChild(parent, i);

        if (child is TreeListViewItem treeListViewItem)
            treeListViewItem.ExpandSubtree();
        else
            ExpandTree(child);
    }
}

当前实现仅在使用Task.Delay时生效,存在三个待解决问题:

  1. 必须依赖Task.Delay(否则UI未就绪),如何移除该依赖?
  2. 如何避免在View后台代码中访问ViewModel?
  3. 因绑定ItemsSource导致Items属性为空,需遍历整个视觉树查找节点,如何优化?

解决方案

1. 移除Task.Delay依赖

问题根源是DataContext变更后,UI容器尚未生成完成,直接调用展开逻辑会找不到节点。可以监听ItemContainerGenerator的StatusChanged事件,等待容器生成完毕后再执行展开:

private void Tree_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    if (_viewModel?.ShouldExpandTreesAfterUpdate() != true) return;
    
    void OnGeneratorStatusChanged(object s, EventArgs args)
    {
        if (Tree.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
        {
            Tree.ItemContainerGenerator.StatusChanged -= OnGeneratorStatusChanged;
            ExpandTree(Tree);
        }
    }

    Tree.ItemContainerGenerator.StatusChanged += OnGeneratorStatusChanged;
}

这样就不需要依赖延迟,而是在UI容器真正准备好后再执行展开操作。

2. 避免View后台访问ViewModel

可以用Blend行为封装展开逻辑,通过绑定ViewModel的属性触发操作,完全解耦View和ViewModel:

自定义行为类

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Xaml.Behaviors;

public class AutoExpandTreeBehavior : Behavior<TreeListView>
{
    public static readonly DependencyProperty ShouldExpandProperty =
        DependencyProperty.Register("ShouldExpand", typeof(bool), typeof(AutoExpandTreeBehavior), 
            new PropertyMetadata(false, OnShouldExpandChanged));

    public bool ShouldExpand
    {
        get => (bool)GetValue(ShouldExpandProperty);
        set => SetValue(ShouldExpandProperty, value);
    }

    private static void OnShouldExpandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behavior = (AutoExpandTreeBehavior)d;
        if ((bool)e.NewValue)
        {
            behavior.TriggerExpand();
        }
    }

    private void TriggerExpand()
    {
        void OnGeneratorReady(object s, EventArgs args)
        {
            if (AssociatedObject.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                AssociatedObject.ItemContainerGenerator.StatusChanged -= OnGeneratorReady;
                ExpandAllNodes(AssociatedObject);
            }
        }

        AssociatedObject.ItemContainerGenerator.StatusChanged += OnGeneratorReady;
    }

    private void ExpandAllNodes(DependencyObject parent)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            if (child is TreeListViewItem item)
            {
                item.ExpandSubtree();
            }
            else
            {
                ExpandAllNodes(child);
            }
        }
    }
}

XAML中引用行为

xmlns:i="http://schemas.microsoft.com/xaml/behaviors"

<treeList:TreeListView Template="{StaticResource TreeControlTemplate}"
                       DataContext="{Binding RootItem.FilteredChildren}">
    <i:Interaction.Behaviors>
        <local:AutoExpandTreeBehavior ShouldExpand="{Binding ShouldExpandTreesAfterUpdate}" />
    </i:Interaction.Behaviors>
</treeList:TreeListView>

之后View后台无需再引用ViewModel,完全通过绑定触发展开。

3. 优化视觉树遍历问题

由于ControlTemplate嵌套了一个内部TreeListView,外部控件的Items属性为空。可以直接定位到内部的TreeListView,利用它的ItemContainerGenerator操作节点,避免遍历整个视觉树:

private TreeListView GetInnerTree(TreeListView outerTree)
{
    // 根据ControlTemplate结构,直接获取内部TreeListView
    var border = VisualTreeHelper.GetChild(outerTree, 0) as Border;
    return border?.Child as TreeListView;
}

// 修改展开方法
private void ExpandTree(TreeListView outerTree)
{
    var innerTree = GetInnerTree(outerTree);
    if (innerTree == null) return;

    foreach (var item in innerTree.Items)
    {
        if (innerTree.ItemContainerGenerator.ContainerFromItem(item) is TreeListViewItem container)
        {
            container.ExpandSubtree();
            ExpandChildNodes(container);
        }
    }
}

private void ExpandChildNodes(TreeListViewItem parentItem)
{
    foreach (var childItem in parentItem.Items)
    {
        if (parentItem.ItemContainerGenerator.ContainerFromItem(childItem) is TreeListViewItem childContainer)
        {
            childContainer.ExpandSubtree();
            ExpandChildNodes(childContainer);
        }
    }
}

这样直接操作内部TreeListView的Items和容器生成器,遍历效率更高,逻辑也更清晰。


内容的提问来源于stack exchange,提问作者horotab

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 21:46:07