1

我正在尝试按 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
    ...
}

我认为它必须与数据模板有关(也许):

<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 确实包含所有级别的项目。

我知道有很多代码,但我花了几个小时试图让它工作。谢谢你的帮助。:)

4

3 回答 3

1

问题是嵌套ItemContainerGenerators不是一开始就生成的,它们是按需生成的。更重要的是,它们是在单独的线程中生成的,因此您必须StatusChanged在生成器上听 a 以确保它已准备好 =(

有些人建议玩Dispatcher就像在这个 Bea 的帖子中一样)。我试图实现 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;
    }
于 2009-12-18T09:54:57.053 回答
0

我认为它不起作用,因为项目已折叠且其容器未实例化。因此尝试直接选择 TreeViewItem 绝对不是最好的方法。

相反,我们使用 MVVM 方法。每个视图模型对象都应该具有 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的这篇精彩文章:使用 ViewModel 模式简化 WPF TreeView

希望这可以帮助。

于 2009-12-18T09:53:54.200 回答
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; }
    }
}
于 2012-10-16T18:53:18.893 回答