【问题标题】:Treeview ContainerFromItem always returns nullTreeview ContainerFromItem 总是返回 null
【发布时间】:2011-10-06 10:34:15
【问题描述】:

我已经阅读了一些关于这个主题的主题,但找不到任何可以做我想做的事情。我有一个绑定到一组分层对象的树视图。这些对象中的每一个都代表地图上的一个图标。当用户单击地图上的一个图标时,我想在树视图中选择该项目,关注它,然后将其滚动到视图中。地图对象具有绑定到树视图的对象列表。在示例中,Thing 是绑定到树的对象类型。

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

到目前为止,无论我尝试了什么,容器始终为空。有什么想法吗?

【问题讨论】:

    标签: c# wpf treeview itemcontainergenerator


    【解决方案1】:

    该项目实际上是masterTreeView 的子项吗?

    这实际上可能非常困难,因为 TreeViewItemsItemsControls 和它们自己的 ItemContainerGenerator,这意味着您应该只能从直接父级的 ItemContainerGenerator 而不是从根中获取容器。

    一些递归函数首先将层次结构提升到根,然后在 ui 级别反转此路线,始终获取项目的容器可能会起作用,但您的数据项需要对其逻辑父数据对象的引用。

    【讨论】:

    • 不,只有其中一些是树视图的子级。树视图有很多层次。每个事物都有对它的父事物的引用,而不是对它的 TreeViewItem 容器的引用。这就是我想要弄清楚的。我可以轻松地从绑定对象中上下树,我只是不确定如何获取特定对象的 TreeViewItem 容器。
    • 将每个对象推入堆栈直到到达根项目,您知道根对应的ItemContainerGenerator,因此您可以使用它来获取堆栈中下一个对象的容器,然后您可以使用这个 TreeViewItem 的 ItemContainerGenerator 来获取堆栈中下一个对象的容器,依此类推,直到堆栈为空并且您拥有初始事物的容器。
    • 有没有关于如何获取实际容器的sn-p?
    • @JobaDiniz:没有。
    • 如果有人想要一个如何实现的示例,this answer 非常好。
    【解决方案2】:

    问题在于每个 TreeViewItem 本身就是一个 ItemsControl,因此它们各自为自己的子级管理自己的容器。

    【讨论】:

      【解决方案3】:

      您有 3 个选择:

      • 您禁用项目的虚拟化:<TreeView VirtualizingStackPanel.IsVirtualizing="False">,但这可能会影响性能

      • 您管理每个项目的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();
                      }
                  }
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2014-03-04
        • 2016-11-02
        • 2014-05-06
        • 2012-06-05
        • 2014-05-30
        • 2014-02-23
        • 2017-10-12
        相关资源
        最近更新 更多