Finding an Object TreeViewItem

 

The question often comes up, "how do I get to a certain TreeViewItem?"  The question arises because a TreeView is bound to a data source, TreeViewItems are implicitly created and wrap the data object.  However, the TreeView.SelectedItem property returns the data object, not the TreeViewItem.   This is good when you need to access the data, but what if you need to manipulate the TreeViewItem?  It has been suggested that if you design your data model correctly, you can avoid needing to manipulate TreeViewItems directly.  While that is good general advice, there might be cases where manipulating the TreeViewItem is unavoidable.

 

Finding the TreeViewItem in a TreeView is more involved than finding item containers in other ItemControls (such as a ListBoxItem in a ListBox) because TreeViewItems are, naturally, nested.   For example, to return the ListBoxItem of an object in a ListBox, you can call ListBox.ItemContainerGenerator.GetContainerFromItem. But if you call GetContainerFromItem on a TreeView, the ItemContainerGenerator searches only the direct child objects of the TreeView.  So you need to recursively traverse the TreeView and child TreeViewItem objects.  A further complication is that if the TreeView virtualizes its items (you enable virtualization by setting the VirtualizingStackPanel.IsVirtualizing property to true), the child items need to be created before you can check its data object.

 

Given the complexity of the problem, I (with help from the development team) created a method that traverses the TreeView and realizes any virtualized items.

 

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

}

 

Note that this code looks for a Panel called, MyVirtualizingStackPanel.  This new type exposes a method that allows you to bring an item into view by passing in the item's index.

 

public class MyVirtualizingStackPanel : VirtualizingStackPanel

{

    /// <summary>

    /// Publically expose BringIndexIntoView.

    /// </summary>

    public void BringIntoView(int index)

    {

 

        this.BringIndexIntoView(index);

    }

}

 

The final step is to use the custom VirtualizingStackPanel as the TreeView's ItemsPanel:

 

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

Now that you have the code, you can search for any object in any TreeView, regardless of its depth in the tree.

 

I attached a sample that uses this technique to find any item in the TreeView. The sample asks for a number, finds the corresponding data object in the data model, and then selects the TreeViewItem that contains the object.  Note that the way I find the data object in the model is specific to the organization of my data.  I could have kept track of the data object's location within the data hierarchy, and then find the corresponding TreeViewItem in the TreeView. 

 

For example, the item that corresponds to 41 is, starting from the root of the TreeView, under the second item, then under the first item, then under the second item, and finally the first item.  I could have kept track of its location with a list of indices, 2,1,2,1, and then used those to navigate the TreeView.  However, this approach is dependent on the organization of the underlying TreeView.  The implementation I show above works on any TreeView and it doesn't require knowledge of the data model.

 

The attached sample has C# and Visual Basic versions.

FindTreeViewItem.zip