WPF:选择超过根级别的TreeViewItem

时间:2009-12-15 08:38:38

标签: wpf treeview selecteditem

我正在尝试按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确实包含所有级别的项目。

我知道有很多代码,但是我正在努力工作以实现这个目标。谢谢你的帮助。 :)

3 个答案:

答案 0 :(得分:1)

问题是嵌套的ItemContainerGenerators一开始并不是全部生成的,它们是按需生成的。更重要的是,它们是在一个单独的线程中生成的,所以你必须在生成器上听StatusChanged以确保它已准备就绪=(

有人建议使用Dispatcherlike 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; }
    }
}