2011-07-15 71 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所以他们各自管理自己的他们的孩子的容器。