如何:在 TreeView 中尋找 TreeViewItem

TreeView 控制項提供顯示階層式資料的簡便方式。 如果您的 TreeView 繫結到資料來源,SelectedItem 屬性會為您提供一個快速擷取選取資料物件的簡便方式。 一般推薦使用基礎資料物件,但有時您可能需要以程式設計方式操作資料所包含的 TreeViewItem。 例如,您可能需要以程式設計方式展開 TreeViewItem,或在 TreeView 中選取一個不同項目。

若要尋找包含特定資料物件的 TreeViewItem,您必須周遊 TreeView 每一個層級。 TreeView 中的項目可視覺化,以提升效能。 在項目可能虛擬化的情況下,您也必須了解 TreeViewItem,以檢查其是否包含資料物件。

範例

描述

下列範例會為特定物件尋找 TreeView,並傳回包含 TreeViewItem 的物件。 此範例可確保每個 TreeViewItem 都會具現化,以便搜尋其子項目。 如果 TreeView 不使用虛擬化專案,此範例依然會運作。

注意

無論基礎資料模型為何,下列範例適用於任何 TreeView,並會搜尋每個 TreeViewItem,直到找到物件為止。 另一個效能較佳的技術是搜尋指定對象的資料模型、追蹤其在資料階層中的位置,然後在 TreeView 中尋找對應的 TreeViewItem。 不過,具有較佳效能的技術需要了解資料模型,而且無法針對任何指定的 TreeView 進行一般化。

代碼

/// <summary>
/// Recursively search for an item in this subtree.
/// </summary>
/// <param name="container">
/// The parent ItemsControl. This can be a TreeView or a TreeViewItem.
/// </param>
/// <param name="item">
/// The item to search for.
/// </param>
/// <returns>
/// The TreeViewItem that contains the specified item.
/// </returns>
private TreeViewItem GetTreeViewItem(ItemsControl container, object item)
{
    if (container != null)
    {
        if (container.DataContext == item)
        {
            return container as TreeViewItem;
        }

        // Expand the current container
        if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
        {
            container.SetValue(TreeViewItem.IsExpandedProperty, true);
        }

        // Try to generate the ItemsPresenter and the ItemsPanel.
        // by calling ApplyTemplate.  Note that in the
        // virtualizing case even if the item is marked
        // expanded we still need to do this step in order to
        // regenerate the visuals because they may have been virtualized away.

        container.ApplyTemplate();
        ItemsPresenter itemsPresenter =
            (ItemsPresenter)container.Template.FindName("ItemsHost", container);
        if (itemsPresenter != null)
        {
            itemsPresenter.ApplyTemplate();
        }
        else
        {
            // The Tree template has not named the ItemsPresenter,
            // so walk the descendents and find the child.
            itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            if (itemsPresenter == null)
            {
                container.UpdateLayout();

                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
            }
        }

        Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);

        // Ensure that the generator for this panel has been created.
        UIElementCollection children = itemsHostPanel.Children;

        MyVirtualizingStackPanel virtualizingPanel =
            itemsHostPanel as MyVirtualizingStackPanel;

        for (int i = 0, count = container.Items.Count; i < count; i++)
        {
            TreeViewItem subContainer;
            if (virtualizingPanel != null)
            {
                // Bring the item into view so
                // that the container will be generated.
                virtualizingPanel.BringIntoView(i);

                subContainer =
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);
            }
            else
            {
                subContainer =
                    (TreeViewItem)container.ItemContainerGenerator.
                    ContainerFromIndex(i);

                // Bring the item into view to maintain the
                // same behavior as with a virtualizing panel.
                subContainer.BringIntoView();
            }

            if (subContainer != null)
            {
                // Search the next level for the object.
                TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                if (resultContainer != null)
                {
                    return resultContainer;
                }
                else
                {
                    // The object is not under this TreeViewItem
                    // so collapse it.
                    subContainer.IsExpanded = false;
                }
            }
        }
    }

    return null;
}

/// <summary>
/// Search for an element of a certain type in the visual tree.
/// </summary>
/// <typeparam name="T">The type of element to find.</typeparam>
/// <param name="visual">The parent element.</param>
/// <returns></returns>
private T FindVisualChild<T>(Visual visual) where T : Visual
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
    {
        Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
        if (child != null)
        {
            T correctlyTyped = child as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            T descendent = FindVisualChild<T>(child);
            if (descendent != null)
            {
                return descendent;
            }
        }
    }

    return null;
}
''' <summary> 
''' Recursively search for an item in this subtree. 
''' </summary> 
''' <param name="container"> 
''' The parent ItemsControl.  This can be a TreeView or a TreeViewItem.
''' </param> 
''' <param name="item"> 
''' The item to search for. 
''' </param> 
''' <returns> 
''' The TreeViewItem that contains the specified item. 
''' </returns> 
Private Function GetTreeViewItem(ByVal container As ItemsControl,
                                 ByVal item As Object) As TreeViewItem

    If container IsNot Nothing Then
        If container.DataContext Is item Then
            Return TryCast(container, TreeViewItem)
        End If

        ' Expand the current container 
        If TypeOf container Is TreeViewItem AndAlso
           Not DirectCast(container, TreeViewItem).IsExpanded Then

            container.SetValue(TreeViewItem.IsExpandedProperty, True)
        End If

        ' Try to generate the ItemsPresenter and the ItemsPanel. 
        ' by calling ApplyTemplate. Note that in the 
        ' virtualizing case, even if IsExpanded = true, 
        ' we still need to do this step in order to 
        ' regenerate the visuals because they may have been virtualized away. 
        container.ApplyTemplate()

        Dim itemsPresenter As ItemsPresenter =
            DirectCast(container.Template.FindName("ItemsHost", container), ItemsPresenter)

        If itemsPresenter IsNot Nothing Then
            itemsPresenter.ApplyTemplate()
        Else
            ' The Tree template has not named the ItemsPresenter, 
            ' so walk the descendents and find the child. 
            itemsPresenter = FindVisualChild(Of ItemsPresenter)(container)

            If itemsPresenter Is Nothing Then
                container.UpdateLayout()

                itemsPresenter = FindVisualChild(Of ItemsPresenter)(container)
            End If
        End If

        Dim itemsHostPanel As Panel =
            DirectCast(VisualTreeHelper.GetChild(itemsPresenter, 0), Panel)


        ' Do this to ensure that the generator for this panel has been created. 
        Dim children As UIElementCollection = itemsHostPanel.Children

        Dim virtualizingPanel As MyVirtualizingStackPanel =
            TryCast(itemsHostPanel, MyVirtualizingStackPanel)


        For index As Integer = 0 To container.Items.Count - 1

            Dim subContainer As TreeViewItem

            If virtualizingPanel IsNot Nothing Then

                ' Bring the item into view so 
                ' that the container will be generated. 
                virtualizingPanel.BringIntoView(index)

                subContainer =
                    DirectCast(container.ItemContainerGenerator.ContainerFromIndex(index), 
                        TreeViewItem)
            Else
                subContainer =
                    DirectCast(container.ItemContainerGenerator.ContainerFromIndex(index), 
                        TreeViewItem)

                ' Bring the item into view to maintain the 
                ' same behavior as with a virtualizing panel. 
                subContainer.BringIntoView()
            End If

            If subContainer IsNot Nothing Then

                ' Search the next level for the object.
                Dim resultContainer As TreeViewItem =
                    GetTreeViewItem(subContainer, item)

                If resultContainer IsNot Nothing Then
                    Return resultContainer
                Else
                    ' The object is not under this TreeViewItem
                    ' so collapse it.
                    subContainer.IsExpanded = False
                End If
            End If
        Next
    End If

    Return Nothing
End Function

''' <summary> 
''' Search for an element of a certain type in the visual tree. 
''' </summary> 
''' <typeparam name="T">The type of element to find.</typeparam> 
''' <param name="visual">The parent element.</param> 
''' <returns></returns> 
Private Function FindVisualChild(Of T As Visual)(ByVal visual As Visual) As T

    For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(visual) - 1

        Dim child As Visual = DirectCast(VisualTreeHelper.GetChild(visual, i), Visual)

        If child IsNot Nothing Then

            Dim correctlyTyped As T = TryCast(child, T)
            If correctlyTyped IsNot Nothing Then
                Return correctlyTyped
            End If

            Dim descendent As T = FindVisualChild(Of T)(child)
            If descendent IsNot Nothing Then
                Return descendent
            End If
        End If
    Next

    Return Nothing
End Function

上一個程式碼倚靠自訂 VirtualizingStackPanel 來公開名為 BringIntoView 的方法。 下列程式碼會定義自訂 VirtualizingStackPanel

public class MyVirtualizingStackPanel : VirtualizingStackPanel
{
    /// <summary>
    /// Publically expose BringIndexIntoView.
    /// </summary>
    public void BringIntoView(int index)
    {

        this.BringIndexIntoView(index);
    }
}
Public Class MyVirtualizingStackPanel
    Inherits VirtualizingStackPanel
    ''' <summary> 
    ''' Publically expose BringIndexIntoView. 
    ''' </summary> 
    Public Overloads Sub BringIntoView(ByVal index As Integer)

        Me.BringIndexIntoView(index)
    End Sub
End Class

下列 XAML 示範如何建立使用自訂 VirtualizingStackPanelTreeView

<TreeView VirtualizingStackPanel.IsVirtualizing="True">

  <!--Use the custom class MyVirtualizingStackPanel
      as the ItemsPanel for the TreeView and
      TreeViewItem object.-->
  <TreeView.ItemsPanel>
    <ItemsPanelTemplate>
      <src:MyVirtualizingStackPanel/>
    </ItemsPanelTemplate>
  </TreeView.ItemsPanel>
  <TreeView.ItemContainerStyle>
    <Style TargetType="TreeViewItem">
      <Setter Property="ItemsPanel">
        <Setter.Value>
          <ItemsPanelTemplate>
            <src:MyVirtualizingStackPanel/>
          </ItemsPanelTemplate>
        </Setter.Value>
      </Setter>
    </Style>
  </TreeView.ItemContainerStyle>
</TreeView>

另請參閱