Treeview ContainerFromItem 总是返回 null
Posted
技术标签:
【中文标题】Treeview ContainerFromItem 总是返回 null【英文标题】:Treeview ContainerFromItem always returns 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);
到目前为止,无论我尝试了什么,容器始终为空。有什么想法吗?
【问题讨论】:
【参考方案1】:该项目实际上是masterTreeView
的子项吗?
这实际上可能非常困难,因为 TreeViewItems
是 ItemsControls
和它们自己的 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();
【讨论】:
以上是关于Treeview ContainerFromItem 总是返回 null的主要内容,如果未能解决你的问题,请参考以下文章