代码之家  ›  专栏  ›  技术社区  ›  Padu Merloti

如何在代码隐藏中扩展WPF树视图的所有节点?

  •  13
  • Padu Merloti  · 技术社区  · 16 年前

    我可能正在忍受周一的沉默,但在我将所有treeview节点添加到代码隐藏(类似于treeview.ExpandAll())中之后,我找不到一种扩展所有treeview节点的好方法。

    有什么快速的帮助吗?

    5 回复  |  直到 15 年前
        1
  •  31
  •   OrPaz    15 年前

    在xaml中,可以按如下方式执行:

     <TreeView.ItemContainerStyle>
                <Style TargetType="TreeViewItem">
                    <Setter Property="TreeViewItem.IsExpanded" Value="True"/>
                </Style>
     </TreeView.ItemContainerStyle>
    
        2
  •  5
  •   satnhak    10 年前

    确保你的树是虚拟化的,如果它没有虚拟化,那么一旦树达到任何大小,无论你做什么,它都会变得非常缓慢。

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

    假设有一个视图模型支持树,该视图模型上的每个节点都对应一个 HierarchicalDataTemplate 需要一个 IsExpanded

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

    这个 TreeViewItem 需要按如下方式设置样式以绑定 扩展

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

    我们将使用此属性来设置扩展状态,但是,由于树是虚拟化的,因此此属性对于作为个体维护正确的视图状态是必需的

    1. 获取当前绑定到 TreeView.ItemsSource
    2. 清除该绑定。
    3. 等待绑定实际清除。
    4. 在(现在未绑定)视图模型中设置展开状态。
    5. 重新绑定 TreeView.itemsource

    因为我们启用了虚拟化,在 TreeView.itemsource

    下面是一些代码:

    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);
        }
    }
    
        3
  •  2
  •   Community Mohan Dere    9 年前
        4
  •  0
  •   Eric Ouellet    13 年前

    这是我的密码。也许你应该考虑把你的层次结构包装成一个分层模型模型视图?

    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();
                    }
                }
            }
        }
    }
    
        5
  •  0
  •   Ricardo Almeida    7 年前

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

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