Search code examples
wpftreeviewwpf-controls

WPF cannot get ItemContainerGenerator.ContainerFromItem to work


I have looked here and here and many other places, but I just can't seem to get the ItemContainerGenerator.ContainerFromItem method to work on a WPF TreeView! I have tried to pass in the actual item I want to see, but not getting anywhere with that, I just tried to get the first item in my TreeView. Here's my sample code:

private static bool ExpandAndSelectItem(ItemsControl parentContainer, object itemToSelect)
{
    // This doesn't work.
    parentContainer.BringIntoView();
    // May be virtualized, bring into view and try again.
    parentContainer.UpdateLayout();
    parentContainer.ApplyTemplate();

    TreeViewItem topItem = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(parentContainer.Items[0]);

    // Can't find child container unless the parent node is Expanded once
    if ((topItem != null) && !topItem.IsExpanded) 
    {
        topItem.IsExpanded = true;
        parentContainer.UpdateLayout();
    }
}

As you can see, I have tried to call many "updating" methods to try to get the TreeView to be "visible" and "accessible". The Catch-22 seems to be that you can't use ContainerFromItem() unless the first TreeViewItem is expanded, but I can't grab the TreeViewItem to Expand it until ContainerFromItem() works!

Another funny thing that is happening is this: When I open this window (it is a UserControl), ContainerFromItem() returns nulls, but if I close the window and open it back up, ContainerFromItem() starts returning non-nulls. Is there any event I should be looking for or forcing to fire?


Solution

  • Turns out the event I was looking for was "Loaded". I just attached an event handler onto my treeview in the XAML, and called my logic in that event handler.

    <TreeView x:Name="MyTreeView"
              Margin="0,5,0,5"
              HorizontalAlignment="Left"
              BorderThickness="0"
              FontSize="18"
              FontFamily="Segoe WP"
              MaxWidth="900"
              Focusable="True"
              Loaded="MyTreeView_Load">
        ...
    </TreeView>
    

    The event handler:

    private void MyTreeView_Load(object sender, RoutedEventArgs e)
    {
        ShowSelectedThing(MyTreeView, ThingToFind);
    }
    // Gotta call the TreeView an ItemsControl to cast it between TreeView and TreeViewItem
    // as you recurse
    private static bool ShowSelectedThing(ItemsControl parentContainer, object ThingToFind)
    {
        // check current level of tree
        foreach (object item in parentContainer.Items)
        {
            TreeViewItem currentContainer = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
            if ((currentContainer != null) && (item == ThingToFind)
            {
                currentContainer.IsSelected = true;
                currentContainer.BringIntoView();
                return true;
            }
        }
        // item is not found at current level, check the kids
        foreach (object item in parentContainer.Items)
        {
            TreeViewItem currentContainer = (TreeViewItem)parentContainer.ItemContainerGenerator.ContainerFromItem(item);
            if ((currentContainer != null) && (currentContainer.Items.Count > 0))
            {
                // Have to expand the currentContainer or you can't look at the children
                currentContainer.IsExpanded = true;
                currentContainer.UpdateLayout();
                if (!ShowSelectedThing(currentContainer, ThingToFind))
                {
                    // Haven't found the thing, so collapse it back
                    currentContainer.IsExpanded = false;
                }
                else
                {
                    // We found the thing
                    return true;
                }
            }
        }
        // default
        return false;
    }
    

    Hope this helps someone. Sometimes in the real world, with demanding customers, weird requirements and short deadlines, ya gotta hack!