2009-12-14 22 views

Trả lời

28

Trong XAML bạn có thể làm điều đó như sau:

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

WPF không có phương thức ExpandAll. Bạn sẽ cần phải lặp lại và thiết lập thuộc tính trên mỗi nút.

Xem this question hoặc this blog post.

0

Tôi đã làm một ExpandAll mà làm việc cũng nếu cây của bạn được đặt cho ảo hóa (các mặt hàng tái chế).

Đây là mã của tôi. Có lẽ bạn nên xem xét gói phân cấp của bạn thành một khung nhìn mô hình phân cấp?

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

Sau khi chơi đùa với tất cả các phương pháp khác nhau để mở rộng hoàn toàn và sụp đổ một cái nhìn cây, đến nay là phương pháp nhanh nhất là như sau. Phương pháp này dường như hoạt động trên những cây rất lớn.

Đảm bảo cây của bạn được ảo hóa, nếu nó không được ảo hóa thì ngay khi cây có kích thước bất kỳ, nó sẽ trở nên chậm chạp bất cứ điều gì bạn làm.

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

Giả sử rằng bạn có một mô hình xem sao cây của bạn, mỗi nút trên mô hình điểm cho rằng tương ứng với một HierarchicalDataTemplate cần một tài sản IsExpanded (nó không cần phải thực hiện bất động sản thay đổi). Giả sử các mô hình này xem thực hiện một giao diện như thế này:

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

Phong cách TreeViewItem cần phải được thiết lập như sau để ràng buộc IsExpanded tài sản trong mô hình nhằm xem:

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

Chúng tôi sẽ sử dụng thuộc tính này để thiết lập trạng thái mở rộng, nhưng cũng bởi vì cây được ảo hóa thuộc tính này là cần thiết để duy trì trạng thái xem chính xác khi cá nhân TreeViewItem s được tái chế. Nếu không có các nút liên kết này sẽ bị thu gọn khi chúng bị mất tầm nhìn khi người dùng duyệt cây.

Cách duy nhất để có được tốc độ chấp nhận được trên cây lớn là làm việc trong mã phía sau trong lớp chế độ xem. Kế hoạch về cơ bản là như sau:

  1. Nhận giữ liên kết hiện tại với TreeView.ItemsSource.
  2. Xóa ràng buộc đó.
  3. Đợi ràng buộc thực sự rõ ràng.
  4. Đặt trạng thái mở rộng trong mô hình chế độ xem (hiện không gắn kết).
  5. Rebind the TreeView.ItemsSource bằng cách sử dụng liên kết mà chúng tôi đã lưu trong bước 1.

Bởi vì chúng tôi đã bật ảo hóa, thực hiện liên kết trên TreeView.ItemsSource hóa ra rất nhanh, ngay cả với mô hình chế độ xem lớn. Tương tự như vậy, khi cập nhật không giới hạn trạng thái mở rộng của các nút sẽ rất nhanh. Điều này dẫn đến các cập nhật nhanh đáng ngạc nhiên.

Dưới đây là một số mã:

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); 
    } 
} 
Các vấn đề liên quan