Treeview ContainerFromItem始终返回null

我已经阅读了关于这个主题的几个主题,但找不到任何可以做我想做的事情。 我有一个树视图绑定到一组分层的对象。 这些对象中的每一个都代表地图上的图标。 当用户单击地图上的其中一个图标时,我想在树状视图中选择该项目,将其聚焦,然后将其滚动到视图中。 map对象具有绑定到treeview的对象列表。 在示例中,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); } } } 

到目前为止,无论我尝试过什么,容器总是为空。 有任何想法吗?

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

这可能实际上非常困难,因为TreeViewItems是具有自己的ItemContainerGenerator ItemsControls ,这意味着您应该只能从直接父项的ItemContainerGenerator而不是从根获取容器。

一些递归函数,首先将层次结构上传到根,然后在ui级别反转此路由,总是获取项目的容器可能会有效,但是您的数据项需要引用其逻辑父数据对象。

问题是每个TreeViewItem本身就是一个ItemsControl,所以他们每个人都为自己的孩子管理自己的容器。

您有3个选择: – 禁用项目的虚拟化:,但这可能会影响性能 – 您可以管理每个项目的itemContainerGenerator状态(某些代码作为示例提供)。 很复杂。 – 将层次结构视图模型添加到层次结构中,并为每个节点级别实现“IsExpanded”属性。 最好的解决方案。

禁用虚拟化:

  

祝好运…

 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; } } } // ****************************************************************** ///  /// 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. ///  /// TreeView where an item has to be set visible /// 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) /// Optionnal public static void SetItemHierarchyVisible(this TreeView treeView, IEnumerable listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null) { ItemContainerGenerator icg = treeView.ItemContainerGenerator; if (icg == null) return; // Is tree loaded and initialized ??? SetItemHierarchyVisible(icg, new List(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; } } } // ****************************************************************** ///  /// 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) ///  /// TreeView where an item has to be set visible /// 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 /// Optionnal public static void ExpandItem(this TreeView treeView, IEnumerable listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null) { ItemContainerGenerator icg = treeView.ItemContainerGenerator; if (icg == null) return; // Is tree loaded and initialized ??? ExpandItem(icg, new List(listOfRootToNodePath), onTreeViewVisible); } // ****************************************************************** private static void ExpandSubWithContainersGenerated(ItemsControl ic, Action 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); } } // ****************************************************************** ///  /// Expand any ItemsControl (TreeView, TreeViewItem, ListBox, ComboBox, ...) and their childs if any (TreeView) ///  ///  ///  ///  public static void ExpandSubContainers(ItemsControl ic, Action 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); } } } } // ****************************************************************** ///  /// 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. ///  ///  ///  ///  public static void ExpandAll(this TreeView treeView, Action 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(); } } } } }