【问题标题】:WPF: Select TreeViewItem broken past the root levelWPF:选择超出根级别的 TreeViewItem
【发布时间】:2010-12-26 17:36:22
【问题描述】:

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

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

【问题讨论】:

    标签: wpf treeview selecteditem


    【解决方案1】:

    我认为它不起作用,因为项目已折叠且其容器未实例化。因此尝试直接选择 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 的这篇精彩文章:Simplifying the WPF TreeView by Using the ViewModel Pattern

    希望这会有所帮助。

    【讨论】:

      【解决方案2】:

      问题是嵌套的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;
          }
      

      【讨论】:

      • 你能帮我实现与虚拟化树视图相同的事情吗,有 VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling"
      【解决方案3】:

      虽然接受的答案在大多数情况下都有效。它可能碰巧不起作用,因为该对象是在另一个完全不受调度程序控制的线程上创建的。

      如前所述,问题在于 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; }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-08-23
        • 2014-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多