2009-12-14 9 views
13

मुझे सोमवार की मूर्खता का सामना करना पड़ सकता है, लेकिन मुझे कोड के पीछे कोड जोड़ने के बाद सभी वृक्षदृश्य नोड्स का विस्तार करने का एक अच्छा तरीका नहीं मिल रहा है (जैसे TreeView.ExpandAll())।कोड में एक डब्ल्यूपीएफ वृक्षदृश्य के सभी नोड्स का विस्तार कैसे करें?

कोई त्वरित सहायता?

उत्तर

28

XAML में इस प्रकार आप इसे कर सकता है:

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

WPF में ExpandAll विधि नहीं है। आपको प्रत्येक नोड पर संपत्ति को लूप और सेट करने की आवश्यकता होगी।

this question या this blog post देखें।

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(); 
       } 
      } 
     } 
    } 
} 
3

पूरी तरह से विस्तार हो रहा है और एक ट्री दृश्य गिर, अब तक सबसे तेजी से विधि द्वारा के लिए विभिन्न तरीकों के सभी के साथ चारों ओर खेलने के बाद इस प्रकार है। यह विधि बहुत बड़े पेड़ पर काम करती प्रतीत होती है।

सुनिश्चित करें कि आपका पेड़ वर्चुअलाइज्ड है, यदि यह वर्चुअलाइज्ड नहीं है तो जैसे ही पेड़ किसी भी प्रकार के आकार में आता है, यह आपके द्वारा किए गए दर्द से धीमे होने जा रहा है।

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 को रीबिंड करें।

क्योंकि हमारे पास वर्चुअलाइजेशन सक्षम है, 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); 
    } 
} 
:

यहाँ कुछ कोड है

संबंधित मुद्दे