2009-12-15 73 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; } 
    } 
}