2009-12-15 48 views
1

我试图通过ID选择TreeViewItem,但有问题让它通过第一个(根)级别工作。我已经完成了很多关于此的阅读,并且正在使用下面的方法。WPF:Select 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不包含所有级别的项目。

我知道有很多的代码,但我正在烧几个小时试图让这个工作。谢谢你的帮助。 :)

回答

1

问题是嵌套的ItemContainerGenerators不是在开始时全部生成,而是按需生成。更应如此,他们在一个单独的线程中产生,所以你必须要听的发电机StatusChanged,以确保它已准备好=(

有人建议用Dispatcherlike in this Bea's post打)。我试过以实现分派器解决方案,但它没有工作出于某种原因...发电机仍然是空的=(

所以我结束了另一个,你特别要求树更新其布局,原因扩展节点的生成,下面是最后一个方法......你可能需要对它进行一点测试,以验证它是否适合你的需求,它可能会破坏在运行前扩展的一些节点

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; 
    } 
+0

谢谢,非常完美。 – Echilon 2009-12-18 14:27:34

+0

你能帮我实现与虚拟化树视图同样的事情吗?VirtualizingStackPanel.IsVirtualizing =“True” VirtualizingStackPanel.VirtualizationMode =“Recycling” – akjoshi 2010-08-10 13:37:20

0

我认为它不起作用,因为项目被折叠并且它的容器没有实例化。因此试图直接选择TreeViewItem绝对不是最好的选择。

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

在你的情况下,它会是这样的

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阅读约什·史密斯这美妙的文章:Simplifying the WPF TreeView by Using the ViewModel Pattern

希望这有助于。

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; } 
    } 
}