如何在代码后面展开WPF树视图的所有节点?

时间:2009-12-14 18:30:34

标签: c# wpf treeview expand

我可能会遇到星期一的愚蠢,但是在我将它们添加到后面的代码中后,我找不到扩展所有树视图节点的好方法(类似于treeView.ExpandAll())。

任何快速帮助?

5 个答案:

答案 0 :(得分:29)

在xaml中你可以这样做:

 <TreeView.ItemContainerStyle>
            <Style TargetType="TreeViewItem">
                <Setter Property="TreeViewItem.IsExpanded" Value="True"/>
            </Style>
 </TreeView.ItemContainerStyle>

答案 1 :(得分:4)

在使用完全展开和折叠树视图的所有各种方法之后,到目前为止最快的方法如下。这种方法似乎适用于非常大的树木。

确保您的树是虚拟化的,如果它没有被虚拟化,那么只要树达到任何类型的大小,无论您做什么,它都会变得非常缓慢。

VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"

假设您有一个支持树的视图模型,该视图模型上与HierarchicalDataTemplate对应的每个节点都需要一个IsExpanded属性(它不需要实现属性更改) 。假设这些视图模型实现了这样的接口:

interface IExpandableItem : IEnumerable
{
    bool IsExpanded { get; set; }
}

需要按如下方式设置TreeViewItem样式,以便将视图模型中的IsExpanded属性绑定到视图:

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

我们将使用此属性来设置扩展状态,但是,因为树是虚拟化的,所以此属性对于维持正确的视图状态是必要的,因为单个TreeViewItem被回收。如果没有这个绑定节点,当用户浏览树时,节点将会退出视图。

在大树上获得可接受速度的唯一方法是在视图层中使用代码。该计划基本如下:

  1. 抓住当前绑定到TreeView.ItemsSource
  2. 清除该绑定。
  3. 等待绑定实际清除。
  4. 在(现在未绑定)视图模型中设置扩展状态。
  5. 使用我们在步骤1中缓存的绑定重新绑定TreeView.ItemsSource
  6. 由于我们启用了虚拟化,因此即使使用大型视图模型,在TreeView.ItemsSource上执行绑定也会非常快。同样,当未绑定更新节点的扩展状态时应该非常快。这导致令人惊讶的快速更新。

    以下是一些代码:

    void SetExpandedStateInView(bool isExpanded)
    {
        var model = this.DataContext as TreeViewModel;
        if (model == null)
        {
            // View model is not bound so do nothing.
            return;
        }
    
        // Grab hold of the current ItemsSource binding.
        var bindingExpression = this.TreeView.GetBindingExpression(
            ItemsControl.ItemsSourceProperty);
        if (bindingExpression == null)
        {
            return;
        }
    
        // Clear that binding.
        var itemsSourceBinding = bindingExpression.ParentBinding;
        BindingOperations.ClearBinding(
        this.TreeView, ItemsControl.ItemsSourceProperty);
    
        // Wait for the binding to clear and then set the expanded state of the view model.
        this.Dispatcher.BeginInvoke(
            DispatcherPriority.DataBind, 
            new Action(() => SetExpandedStateInModel(model.Items, isExpanded)));
    
        // Now rebind the ItemsSource.
        this.Dispatcher.BeginInvoke(
            DispatcherPriority.DataBind,
            new Action(
                () => this.TreeView.SetBinding(
                    ItemsControl.ItemsSourceProperty, itemsSourceBinding)));
    }
    
    void SetExpandedStateInModel(IEnumerable modelItems, bool isExpanded)
    {
        if (modelItems == null)
        {
            return;
        }
    
        foreach (var modelItem in modelItems)
        {
            var expandable = modelItem as IExpandableItem;
            if (expandable == null)
            {
                continue;
            }
    
            expandable.IsExpanded = isExpanded;
            SetExpandedStateInModel(expandable, isExpanded);
        }
    }
    

答案 2 :(得分:2)

WPF没有ExpandAll方法。您需要循环并在每个节点上设置属性。

请参阅this questionthis blog post

答案 3 :(得分:0)

如果您的树设置为虚拟化(回收项目),我已经完成了一个ExpandAll。

这是我的代码。也许您应该考虑将层次结构包装到层次模型模型视图中?

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
using HQ.Util.General;

namespace HQ.Util.Wpf.WpfUtil
{
    public static class TreeViewExtensions
    {
        // ******************************************************************
        public delegate void OnTreeViewVisible(TreeViewItem tvi);
        public delegate void OnItemExpanded(TreeViewItem tvi, object item);
        public delegate void OnAllItemExpanded();

        // ******************************************************************
        private static void SetItemHierarchyVisible(ItemContainerGenerator icg, IList listOfRootToNodeItemPath, OnTreeViewVisible onTreeViewVisible = null)
        {
            Debug.Assert(icg != null);

            if (icg != null)
            {
                if (listOfRootToNodeItemPath.Count == 0) // nothing to do
                    return;

                TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
                if (tvi != null) // Due to threading, always better to verify
                {
                    listOfRootToNodeItemPath.RemoveAt(0);

                    if (listOfRootToNodeItemPath.Count == 0)
                    {
                        if (onTreeViewVisible != null)
                            onTreeViewVisible(tvi);
                    }
                    else
                    {
                        if (!tvi.IsExpanded)
                            tvi.IsExpanded = true;

                        SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodeItemPath, onTreeViewVisible);
                    }
                }
                else
                {
                    ActionHolder actionHolder = new ActionHolder();
                    EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
                        {
                            var icgSender = sender as ItemContainerGenerator;
                            tvi = icgSender.ContainerFromItem(listOfRootToNodeItemPath[0]) as TreeViewItem;
                            if (tvi != null) // Due to threading, it is always better to verify
                            {
                                SetItemHierarchyVisible(icg, listOfRootToNodeItemPath, onTreeViewVisible);

                                actionHolder.Execute();
                            }
                        };

                    actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
                    icg.StatusChanged += itemCreated;
                    return;
                }
            }
        }

        // ******************************************************************
        /// <summary>
        /// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem 
        /// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
        /// This method should work for Virtualized and non virtualized tree.
        /// The difference with ExpandItem is that this one open up the tree up to the target but will not expand the target itself,
        /// while ExpandItem expand the target itself.
        /// </summary>
        /// <param name="treeView">TreeView where  an item has to be set visible</param>
        /// <param name="listOfRootToNodePath">Any collectionic List. The collection should have every objet of the path to the targeted item from the root
        /// to the target. For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2 (index 3)</param>
        /// <param name="onTreeViewVisible">Optionnal</param>
        public static void SetItemHierarchyVisible(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
        {
            ItemContainerGenerator icg = treeView.ItemContainerGenerator;
            if (icg == null)
                return; // Is tree loaded and initialized ???

            SetItemHierarchyVisible(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
        }

        // ******************************************************************
        private static void ExpandItem(ItemContainerGenerator icg, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
        {
            Debug.Assert(icg != null);

            if (icg != null)
            {
                if (listOfRootToNodePath.Count == 0) // nothing to do
                    return;

                TreeViewItem tvi = icg.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
                if (tvi != null) // Due to threading, always better to verify
                {
                    listOfRootToNodePath.RemoveAt(0);

                    if (!tvi.IsExpanded)
                        tvi.IsExpanded = true;

                    if (listOfRootToNodePath.Count == 0)
                    {
                        if (onTreeViewVisible != null)
                            onTreeViewVisible(tvi);
                    }
                    else
                    {
                        SetItemHierarchyVisible(tvi.ItemContainerGenerator, listOfRootToNodePath, onTreeViewVisible);
                    }
                }
                else
                {
                    ActionHolder actionHolder = new ActionHolder();
                    EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
                        {
                            var icgSender = sender as ItemContainerGenerator;
                            tvi = icgSender.ContainerFromItem(listOfRootToNodePath[0]) as TreeViewItem;
                            if (tvi != null) // Due to threading, it is always better to verify
                            {
                                SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);

                                actionHolder.Execute();
                            }
                        };

                    actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
                    icg.StatusChanged += itemCreated;
                    return;
                }
            }
        }

        // ******************************************************************
        /// <summary>
        /// You cannot rely on this method to be synchronous. If you have any action that depend on the TreeViewItem 
        /// (last item of collectionOfRootToNodePath) to be visible, you should set it in the 'onTreeViewItemVisible' method.
        /// This method should work for Virtualized and non virtualized tree.
        /// The difference with SetItemHierarchyVisible is that this one open the target while SetItemHierarchyVisible does not try to expand the target.
        /// (SetItemHierarchyVisible just ensure the target will be visible)
        /// </summary>
        /// <param name="treeView">TreeView where  an item has to be set visible</param>
        /// <param name="listOfRootToNodePath">The collection should have every objet of the path, from the root to the targeted item.
        /// For example for an apple tree: AppleTree (index 0), Branch4, SubBranch3, Leaf2</param>
        /// <param name="onTreeViewVisible">Optionnal</param>
        public static void ExpandItem(this TreeView treeView, IEnumerable<object> listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
        {
            ItemContainerGenerator icg = treeView.ItemContainerGenerator;
            if (icg == null)
                return; // Is tree loaded and initialized ???

            ExpandItem(icg, new List<object>(listOfRootToNodePath), onTreeViewVisible);
        }

        // ******************************************************************
        private static void ExpandSubWithContainersGenerated(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
        {
            ItemContainerGenerator icg = ic.ItemContainerGenerator;
            foreach (object item in ic.Items)
            {
                var tvi = icg.ContainerFromItem(item) as TreeViewItem;
                actionItemExpanded(tvi, item);
                tvi.IsExpanded = true;
                ExpandSubContainers(tvi, actionItemExpanded, referenceCounterTracker);
            }
        }

        // ******************************************************************
        /// <summary>
        /// Expand any ItemsControl (TreeView, TreeViewItem, ListBox, ComboBox, ...) and their childs if any (TreeView)
        /// </summary>
        /// <param name="ic"></param>
        /// <param name="actionItemExpanded"></param>
        /// <param name="referenceCounterTracker"></param>
        public static void ExpandSubContainers(ItemsControl ic, Action<TreeViewItem, object> actionItemExpanded, ReferenceCounterTracker referenceCounterTracker)
        {
            ItemContainerGenerator icg = ic.ItemContainerGenerator;
            {
                if (icg.Status == GeneratorStatus.ContainersGenerated)
                {
                    ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
                }
                else if (icg.Status == GeneratorStatus.NotStarted)
                {
                    ActionHolder actionHolder = new ActionHolder();
                    EventHandler itemCreated = delegate(object sender, EventArgs eventArgs)
                        {
                            var icgSender = sender as ItemContainerGenerator;
                            if (icgSender.Status == GeneratorStatus.ContainersGenerated)
                            {
                                ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);

                                // Never use the following method in BeginInvoke due to ICG recycling. The same icg could be 
                                // used and will keep more than one subscribers which is far from being intended
                                //  ic.Dispatcher.BeginInvoke(actionHolder.Action, DispatcherPriority.Background);

                                // Very important to unsubscribe as soon we've done due to ICG recycling.
                                actionHolder.Execute();

                                referenceCounterTracker.ReleaseRef();
                            }
                        };

                    referenceCounterTracker.AddRef();
                    actionHolder.Action = new Action(() => icg.StatusChanged -= itemCreated);
                    icg.StatusChanged += itemCreated;

                    // Next block is only intended to protect against any race condition (I don't know if it is possible ? How Microsoft implemented it)
                    // I mean the status changed before I subscribe to StatusChanged but after I made the check about its state.
                    if (icg.Status == GeneratorStatus.ContainersGenerated)
                    {
                        ExpandSubWithContainersGenerated(ic, actionItemExpanded, referenceCounterTracker);
                    }
                }
            }
        }

        // ******************************************************************
        /// <summary>
        /// This method is asynchronous.
        /// Expand all items and subs recursively if any. Does support virtualization (item recycling).
        /// But honestly, make you a favor, make your life easier en create a model view around your hierarchy with
        /// a IsExpanded property for each node level and bind it to each TreeView node level.
        /// </summary>
        /// <param name="treeView"></param>
        /// <param name="actionItemExpanded"></param>
        /// <param name="actionAllItemExpanded"></param>
        public static void ExpandAll(this TreeView treeView, Action<TreeViewItem, object> actionItemExpanded = null, Action actionAllItemExpanded = null)
        {
            var referenceCounterTracker = new ReferenceCounterTracker(actionAllItemExpanded);
            referenceCounterTracker.AddRef();
            treeView.Dispatcher.BeginInvoke(new Action(() => ExpandSubContainers(treeView, actionItemExpanded, referenceCounterTracker)), DispatcherPriority.Background);
            referenceCounterTracker.ReleaseRef();
        }

        // ******************************************************************
    }
}

using System;
using System.Threading;

namespace HQ.Util.General
{
    public class ReferenceCounterTracker
    {
        private Action _actionOnCountReachZero = null;
        private int _count = 0;

        public ReferenceCounterTracker(Action actionOnCountReachZero)
        {
            _actionOnCountReachZero = actionOnCountReachZero;
        }

        public void AddRef()
        {
            Interlocked.Increment(ref _count);
        }

        public void ReleaseRef()
        {
            int count = Interlocked.Decrement(ref _count);
            if (count == 0)
            {
                if (_actionOnCountReachZero != null)
                {
                    _actionOnCountReachZero();
                }
            }
        }
    }
}

答案 4 :(得分:0)

您必须在项目中包括以下方法:

 private void ExpandAllNodes(TreeViewItem treeItem)
    {
        treeItem.IsExpanded = true;
        foreach (var childItem in treeItem.Items.OfType<TreeViewItem>())
        {
            ExpandAllNodes(childItem);
        }
    }

然后,您只需要这样称呼它即可:

treeView.Items.OfType<TreeViewItem>().ToList().ForEach(ExpandAllNodes);