2009-12-14 31 views

Respuesta

28

en XAML que podría hacerlo de la siguiente manera:

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

he hecho un ExpandAll que funciona también si el árbol está listo para la virtualización (artículos de reciclaje).

Este es mi código. Tal vez debería considerar envolver su jerarquía en una vista de modelo de modelo jerárquico.

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

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

Y

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

Después de jugar un poco con todos los diversos métodos para expandir y contraer totalmente una vista de árbol, con mucho, el método más rápido es el siguiente. Este método parece funcionar en árboles muy grandes.

Asegúrate de que tu árbol está virtualizado, si no está virtualizado, tan pronto como el árbol alcance cualquier tamaño, se volverá extremadamente lento, hagas lo que hagas.

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

Suponga que tiene un modelo de vista copia de su árbol, cada nodo en ese modelo de vista que corresponde a un HierarchicalDataTemplate necesita una propiedad IsExpanded (no es necesario para poner en práctica la propiedad cambiado). Asumen estos modelos de vista implementar una interfaz de esta manera:

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

El estilo TreeViewItem necesita configurarse de la siguiente manera para enlazar la propiedad IsExpanded en la vista del modelo a la vista:

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

Vamos a use esta propiedad para establecer el estado de expansión, pero también, debido a que el árbol está virtualizado, esta propiedad es necesaria para mantener el estado de vista correcto a medida que el individuo TreeViewItem s se recicla. Sin este enlace, los nodos se colapsarán a medida que se pierden de vista a medida que el usuario navega por el árbol.

La única manera de obtener una velocidad aceptable en árboles grandes es trabajar en código detrás de la capa de vista. El plan es básicamente el siguiente:

  1. Obtenga la vinculación actual en el TreeView.ItemsSource.
  2. Borrar ese enlace.
  3. Espere a que el enlace sea realmente claro.
  4. Establezca el estado de expansión en el modelo de vista (ahora sin consolidar).
  5. Vuelva a conectar el TreeView.ItemsSource utilizando el enlace que almacenamos en caché en el paso 1.

Porque tenemos la virtualización habilitada, realizar un enlace en TreeView.ItemsSource resulta ser muy rápido, incluso con un modelo de vista grande. Del mismo modo, cuando se libera la actualización, el estado de expansión de los nodos debe ser muy rápido. Esto da como resultado actualizaciones sorprendentemente rápidas.

Aquí hay un código:

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); 
    } 
} 
Cuestiones relacionadas