代码之家  ›  专栏  ›  技术社区  ›  Echilon

WPF:选择树视图项已断开根级别

  •  1
  • Echilon  · 技术社区  · 14 年前

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

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

    3 回复  |  直到 14 年前
        1
  •  1
  •   Massimiliano    14 年前

    问题是嵌套的 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;
        }
    
        2
  •  0
  •   Anvaka    14 年前

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

    相反,我们使用MVVM方法。每个viewmodel对象都应该具有IsSelected属性。然后将TreeViewItem.IsSelected属性绑定到它。

    反恐精英:

    public interface INestable : INotifyPropertyChanged
    {
      string ID { get; set; }
    
      // Make sure you invoke PropertyChanged in setter
      bool IsSelected { get; set; } 
    
      event PropertyChangedEventHandler PropertyChanged;
      ...
    }
    

    <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

    希望这有帮助。

        3
  •  0
  •   Eric Ouellet    12 年前

    尽管被接受的答案在大多数情况下都有效。它可能恰好不起作用,因为对象是在另一个线程上创建的,而该线程根本不受调度程序控制。

    我个人认为正确的解决方案更复杂。我知道这很糟糕,但我真的认为是这样。无论是否虚拟化,我的代码都应该可以工作。此外,您可以删除任何不需要的引用(我没有验证)。

    一切都从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; }
        }
    }