我正在尝试按ID选择TreeViewItem,但是在让它超过第一个(根)级别时遇到问题。我已经做了很多阅读,并使用下面的方法。
private static bool SetSelected(ItemsControl parent, INestable itemToSelect) {
if(parent == null || itemToSelect == null) {
return false;
}
foreach(INestable item in parent.Items) {
if(item.ID == itemToSelect.ID) { // just comparing instances failed
TreeViewItem container = parent.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if(container != null) {
container.IsSelected = true;
container.Focus();
return true;
}
}
ItemsControl childControl = parent.ItemContainerGenerator.ContainerFromItem(item) as ItemsControl;
if(SetSelected(childControl, itemToSelect))
return true;
}
return false;
}
INestable是基础级接口,由IGroup和IAccount实现:
public interface INestable {
string ID { get; set; }
...
}
public interface IAccount : INestable {
...
}
public interface IGroup : INestable {
public IList<INestable> Children
...
}
我认为它必须与datatemplates(可能)有关:
<HierarchicalDataTemplate DataType="{x:Type loc:IGroup}" ItemsSource="{Binding Children}" x:Key="TreeViewGroupTemplate">
<HierarchicalDataTemplate DataType="{x:Type loc:IAccount}" x:Key="TreeViewAccountTemplate">
The Template selector for the treeview returns thr group template for IGroups and the account template for IAccounts:
<conv:TreeTemplateSelector x:Key="TreeTemplateSelector" AccountTemplate="{StaticResource TreeViewAccountTemplate}" GroupTemplate="{StaticResource TreeViewGroupTemplate}"/>
<TreeView ItemTemplateSelector="{StaticResource TreeTemplateSelector}">
它适用于所有顶级项目,只是在此之下,调试确认parent.ItemContainerGenerator确实包含所有级别的项目。
我知道有很多代码,但是我正在努力工作以实现这个目标。谢谢你的帮助。 :)
答案 0 :(得分:1)
问题是嵌套的ItemContainerGenerators
一开始并不是全部生成的,它们是按需生成的。更重要的是,它们是在一个单独的线程中生成的,所以你必须在生成器上听StatusChanged
以确保它已准备就绪=(
有人建议使用Dispatcher
(like in this Bea's post)。我试图实现Dispatcher解决方案,但由于某种原因它没有工作......生成器仍然是空的=(
所以我最后得到了另一个,你特意要求树更新它的布局,这会导致生成扩展节点。这是最后的方法......您可能需要稍微测试一下以验证它是否满足您的需求。它可能会折叠在运行之前展开的某些节点。
private static bool SetSelected(TreeView treeView, ItemsControl parentControl, INestable itemToSelect)
{
if (parentControl == null || itemToSelect == null)
{
return false;
}
foreach (INestable item in parentControl.Items)
{
TreeViewItem container = parentControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
if (item.ID == itemToSelect.ID)
{ // just comparing instances failed
container.IsSelected = true;
container.Focus();
return true;
}
container.IsExpanded = true;
treeView.UpdateLayout();
WaitForPriority(DispatcherPriority.Background);
if (SetSelected(treeView, container, itemToSelect))
return true;
else
container.IsExpanded = false;
}
return false;
}
答案 1 :(得分:0)
我认为它不起作用,因为项目已折叠且其容器未实例化。因此,尝试直接选择TreeViewItem绝对不是最好的方法。
相反,我们使用MVVM方法。每个viewmodel对象都应具有IsSelected属性。然后将TreeViewItem.IsSelected绑定到它。
在你的情况下它会像这样
<强> CS:强>
public interface INestable : INotifyPropertyChanged
{
string ID { get; set; }
// Make sure you invoke PropertyChanged in setter
bool IsSelected { get; set; }
event PropertyChangedEventHandler PropertyChanged;
...
}
<强> XAML:强>
<TreeView ...>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
现在您可以浏览模型并在那里设置IsSelected
属性。
您可能还想以同样的方式跟踪IsExpanded
财产...
要获得有关TreeView的更多信息,请阅读Josh Smith撰写的这篇精彩文章:Simplifying the WPF TreeView by Using the ViewModel Pattern
希望这有帮助。
答案 2 :(得分:0)
虽然接受的答案大部分时间都有效。它可能无法正常工作,因为该对象是在调度程序根本不受控制的另一个线程上创建的。
如前所述,问题在于TreeViewItem是在调度程序的另一个线程中创建的。
我个人认为正确的解决方案更复杂。我知道这听起来很糟糕,但我真的认为是这样。我的代码应该在虚拟化时使用。此外,您可以删除任何不需要的引用(我没有验证)。
我的解决方案基于一个数据模型,其中每个节点都从相同的根继承:MultiSimBase对象,但这不是必需的。
一切都从SetSelectedTreeViewItem()开始,它激活(+设置焦点并进入视图)新添加的项目。
希望它可以帮助或启发一些......快乐的编码!!!
表格代码:
// ******************************************************************
private List<MultiSimBase> SetPathListFromRootToNode(MultiSimBase multiSimBase, List<MultiSimBase> listTopToNode = null)
{
if (listTopToNode == null)
{
listTopToNode = new List<MultiSimBase>();
}
listTopToNode.Insert(0, multiSimBase);
if (multiSimBase.Parent != null)
{
SetPathListFromRootToNode(multiSimBase.Parent, listTopToNode);
}
return listTopToNode;
}
// ******************************************************************
private void SetSelectedTreeViewItem(MultiSimBase multiSimBase)
{
List<MultiSimBase> listOfMultiSimBasePathFromRootToNode = SetPathListFromRootToNode(multiSimBase);
TreeViewStudy.SetItemHierarchyVisible(listOfMultiSimBasePathFromRootToNode, (tvi) =>
{
tvi.IsSelected = true;
tvi.Focus();
tvi.BringIntoView();
});
}
现在是通用代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Threading;
namespace HQ.Util.Wpf.WpfUtil
{
public static class TreeViewExtensions
{
public delegate void OnTreeViewVisible(TreeViewItem tvi);
private static void SetItemHierarchyVisible(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 (listOfRootToNodePath.Count == 0)
{
if (onTreeViewVisible != null)
onTreeViewVisible(tvi);
}
else
{
if (!tvi.IsExpanded)
tvi.IsExpanded = true;
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.
/// </summary>
/// <param name="treeView">TreeView where an item has to be set visible</param>
/// <param name="collectionOfRootToNodePath">Any of collection that implement ICollection like a generic List.
/// The collection should have every objet of the path to the targeted item from the top 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, IList listOfRootToNodePath, OnTreeViewVisible onTreeViewVisible = null)
{
ItemContainerGenerator icg = treeView.ItemContainerGenerator;
if (icg == null)
return; // Is tree loaded and initialized ???
SetItemHierarchyVisible(icg, listOfRootToNodePath, onTreeViewVisible);
}
和
using System;
namespace HQ.Util.Wpf.WpfUtil
{
// Requested to unsubscribe into an anonymous method that is a delegate used for a one time execution
// http://social.msdn.microsoft.com/Forums/en-US/csharplanguage/thread/df2773eb-0cc1-4f3a-a674-e32f2ef2c3f1/
public class ActionHolder
{
public void Execute()
{
if (Action != null)
{
Action();
}
}
public Action Action { get; set; }
}
}