2011-07-15 22 views
4

我已閱讀了關於此主題的一些主題,但找不到任何要執行的操作。我有一個綁定到一組分層對象的樹視圖。這些對象中的每一個都代表地圖上的圖標。當用戶點擊地圖上的其中一個圖標時,我想在樹視圖中選擇該項目,然後將其關注並滾動到視圖中。地圖對象具有綁定到樹視圖的對象的列表。在這個例子中,Thing是綁定到樹上的對象的類型。Treeview ContainerFromItem總是返回null

public void ScrollIntoView(Thing t) 
{ 
    if (t != null) 
    { 
    t.IsSelected = true; 
    t.IsExpanded = true; 

    TreeViewItem container = (TreeViewItem)(masterTreeView 
     .ItemContainerGenerator.ContainerFromItem(t)); 
    if (container != null) 
    { 
     container.Focus(); 
     LogicalTreeHelper.BringIntoView(container); 
    } 
    } 
} 

到目前爲止,不管是什麼我已經試過,容器始終爲空。有任何想法嗎?

回答

6

該物品實際上是masterTreeView的小孩嗎?

這實際上是相當困難的,因爲TreeViewItemsItemsControls自己ItemContainerGenerator這意味着你應該只能夠得到來自直屬母公司的ItemContainerGenerator而不是從根容器。

一些遞歸函數,它首先上升的層次結構的根,然後反轉在UI層面始終得到物品的容器可能工作了,但你的數據項需要把他們的邏輯父數據對象的引用這條路線。

+0

不是,只有其中一些是treeview的孩子。樹視圖層次很深。每件事都有一個對它的父項的引用,但不包括它的TreeViewItem容器。這就是我想要弄明白的。我可以輕鬆地從綁定的對象內上下樹,我只是不知道如何獲取特定對象的TreeViewItem容器。 – ConditionRacer

+1

將堆棧中的每個對象推送到根項目,您知道根對應的ItemContainerGenerator,因此您可以使用它來獲取堆棧中下一個對象的容器,然後可以使用此TreeViewItem的ItemContainerGenerator來獲取堆棧中的下一個對象的容器等等,直到你的堆棧爲空,並且你擁有容器爲你的初始事物。 –

+0

我想這就好。感謝您的想法! – ConditionRacer

1

您有3個選擇: - 您禁用項目虛擬化:,但這可能會影響性能 - 您管理每個項目的itemContainerGenerator狀態(某些代碼作爲示例提供)。相當複雜。 - 爲層次結構添加層次結構視圖模型,併爲每個節點層次實施「IsExpanded」屬性。最好的解決方案。

禁用虛擬化:

<TreeView VirtualizingStackPanel.IsVirtualizing="False"> 

好運...

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 delegate void CountToZeroAction(); 

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

的問題是,每個樹型視圖本身就是一個ItemsControl所以他們各自管理自己的他們的孩子的容器。