方法: TreeView で TreeViewItem を検索する

TreeView コントロールは、階層データを表示するための便利な方法を提供します。 TreeView をデータ ソースにバインドすると、SelectedItem プロパティでは、選択したデータ オブジェクトをすばやく取得するための便利な方法を使用できるようになります。 通常、基になるデータ オブジェクトを使用することが最適な方法ですが、データが含まれている TreeViewItem をプログラムで操作することが必要になる場合もあります。 たとえば、TreeViewItem をプログラムで展開すること、または TreeView 内の別の項目をプログラムで選択することが必要になる場合があります。

特定のデータ オブジェクトを含む TreeViewItem を検索するには、TreeView の各レベルを走査する必要があります。 また、パフォーマンスを向上させるために、TreeView 内の項目を仮想化することもできます。 項目を仮想化する場合は、TreeViewItem を取得して、そこにデータ オブジェクトが含まれているかどうかを確認することも必要です。

使用例

説明

次の例では、TreeView 内で特定のオブジェクトを検索し、オブジェクトが含まれている TreeViewItem を返します。 この例では、子項目を検索できるように各 TreeViewItem をインスタンス化する必要があります。 この例は、TreeView に仮想化された項目がない場合も適用できます。

メモメモ

次の例は、基になるデータ モデルに関係なく、あらゆる TreeView に適用できます。オブジェクトが見つかるまで、それぞれの TreeViewItem を検索します。より優れたパフォーマンスを実現するもう 1 つの方法は、データ モデル内で特定のオブジェクトを検索し、データ階層内のその場所を追跡して、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 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
/// <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;
}

前のコードは、BringIntoView という名前のメソッドを公開するカスタム VirtualizingStackPanel に依存します。 次のコードは、カスタム VirtualizingStackPanel を定義します。

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
public class MyVirtualizingStackPanel : VirtualizingStackPanel
{
    /// <summary>
    /// Publically expose BringIndexIntoView.
    /// </summary>
    public void BringIntoView(int index)
    {

        this.BringIndexIntoView(index);
    }
}

次の XAML は、カスタム VirtualizingStackPanel を使用する TreeView を作成する方法を示します。

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

参照

処理手順

方法 : TreeView のパフォーマンスを改善する