none
TreeView, TreeViewItem and IsSelected

    Question

  • Hello all,

    I have a simple TreeView with two levels. The Virtualization is turned on as well as the recycling. I am trying to select a TreeViewItem freshly created. I am doing something similar to the following :

    SuperObject so = new SuperObject("Bla bla bla");
    myCollection.Add(so);
    myTreeView.SelectItem(so);

    If the parent of the TreeViewItem is collapsed, the TreeViewItem is found and selected. However, if the parent of the TreeViewItem that I would like to see selected is expanded, the item is found but not the TreeViewItem. As a result, the TreeViewItem is not selected.

    I use the following method below to select a TreeViewItem based on its Item. It works in most of the cases, but fails in the one explained earlier. Does anybody have an idea what I am not able to the TreeViewItem?

    More details... My code reaches the line 25 because it finds the object SuperObject in the TreeView, but in the call just after, ContainerFromItem returns me null only if the parent is expanded. I don't understand why. Any ideas?

    Thanks,
    Martin


    1/// <summary> 
    2/// Searches a TreeView for the provided object and selects it if found 
    3/// </summary> 
    4/// <param name="treeView">The TreeView containing the item</param> 
    5/// <param name="item">The item to search and select</param> 
    6public static void SelectItem(this TreeView treeView, object item) 
    7
    8    ExpandAndSelectItem(treeView, item); 
    9
    10 
    11/// <summary> 
    12/// Finds the provided object in an ItemsControl's children and selects it 
    13/// </summary> 
    14/// <param name="parentContainer">The parent container whose children will be searched for the selected item</param> 
    15/// <param name="itemToSelect">The item to select</param> 
    16/// <returns>True if the item is found and selected, false otherwise</returns> 
    17private static bool ExpandAndSelectItem(ItemsControl container, object itemToSelect) 
    18
    19    // Check all items at the current level 
    20    foreach (Object currentItem in container.Items) 
    21    { 
    22        // If the data item matches the item we want to select, set the corresponding IsSelected to true 
    23        if (currentItem == itemToSelect) 
    24        { 
    25            TreeViewItem currentContainer = container.ItemContainerGenerator.ContainerFromItem(currentItem) as TreeViewItem; 
    26            if (currentContainer != null
    27            { 
    28                currentContainer.IsSelected = true
    29                currentContainer.BringIntoView(); 
    30                currentContainer.Focus(); 
    31 
    32                // The itemToSelect was found and has been selected 
    33                return true
    34            } 
    35        } 
    36    } 
    37 
    38    // If we get to this point, the SelectedItem was not found at the 
    39    // current level, so we must check the children 
    40    foreach (Object item in container.Items) 
    41    { 
    42        TreeViewItem currentContainer = container.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; 
    43 
    44        // If the currentContainer has children 
    45        if (currentContainer != null && currentContainer.Items.Count > 0) 
    46        { 
    47            // Keep track of if the TreeViewItem was expanded or not 
    48            bool wasExpanded = currentContainer.IsExpanded; 
    49 
    50            // Expand the current TreeViewItem so we can check its child TreeViewItems 
    51            currentContainer.IsExpanded = true
    52 
    53            // If the TreeViewItem child containers have not been generated, we must listen to 
    54            // the StatusChanged event until they are 
    55            if (currentContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
    56            { 
    57                //store the event handler in a variable so we can remove it (in the handler itself) 
    58                EventHandler eh = null
    59                eh = new EventHandler(delegate 
    60                { 
    61                    if (currentContainer.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
    62                    { 
    63                        if (ExpandAndSelectItem(currentContainer, itemToSelect) == false
    64                        { 
    65                            //The assumption is that code executing in this EventHandler is the result of the parent not 
    66                            //being expanded since the containers were not generated. 
    67                            //since the itemToSelect was not found in the children, collapse the parent since it was previously collapsed 
    68                            currentContainer.IsExpanded = false
    69                        } 
    70 
    71                        //remove the StatusChanged event handler since we just handled it (we only needed it once) 
    72                        currentContainer.ItemContainerGenerator.StatusChanged -= eh; 
    73                    } 
    74                }); 
    75                currentContainer.ItemContainerGenerator.StatusChanged += eh; 
    76            } 
    77            else //otherwise the containers have been generated, so look for item to select in the children 
    78            { 
    79                if (ExpandAndSelectItem(currentContainer, itemToSelect) == false
    80                { 
    81                    //restore the current TreeViewItem's expanded state 
    82                    currentContainer.IsExpanded = wasExpanded; 
    83                } 
    84                else //otherwise the node was found and selected, so return true 
    85                { 
    86                    return true
    87                } 
    88            } 
    89        } 
    90    } 
    91 
    92    //no item was found 
    93    return false
    94

    Tuesday, August 26, 2008 2:44 PM

Answers

  • If you set up your data object correctly, you will never need to walk the visual tree to select or expand nodes within the tree.  It can all be done within the view model.

    The trick is to expose an IsExpanded and an IsSelected property on your data object.  Then use the item container style to bind these properties to the IsExpanded and IsSelected properties on TreeViewItem:

     
        <Setter Property="IsSelected" Value="{Binding Path=IsSelected}" /> 
        <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" /> 
     

    I typically structure my hierarchical data such that a child data item maintains a pointer to its parent (but this is simply to make it easier to work with in code).  If you do this, you can select an item and make sure that it is actually visible by doing something like the following:

     
    void SelectItem(MyObject someItem)  
    {  
        someItem.IsSelected = true;  
        while (someItem.Parent != null)  
        {  
            someItem = someItem.Parent;  
            someItem.IsExpanded = true;  
        }  
    }  
     

    Dr. WPF - Online Office at http://drwpf.com/blog/
    • Proposed as answer by Dr. WPF Wednesday, August 27, 2008 1:00 AM
    • Marked as answer by mgagne_98 Wednesday, August 27, 2008 10:57 AM
    Wednesday, August 27, 2008 1:00 AM
  • If you are using a collection view with a grouping property to achieve the hierarchy, then the approach I described above won't work.  In your case, you have flat data.  That means you don't have a way to get to the parent data item (because it really only exists in the collection view... not in the data).

    To expand the parent TreeViewItem (TVI), you will need to locate the group that contains the target data item.  You can do this by enumerating the items in the TreeView.  They will be of type CollectionViewGroup.  One of the groups will contain your item.  When you find that group, you will need to get its corresponding TVI.  This can be done using the ItemContainerGenerator.ContainerFromItem() method.  Once you have the TVI, you can set its IsExanded property to true.

    The tricky part is that you can't get the container until it has been generated.  (For a good explanation of item container generation, see 'G' is for Generator in my ItemsControl series.)  You can use the StatusChanged event of the TreeView's ItemContainerGenerator to know when the containers have been generated.  Then it is safe to locate and expand the desired group.

    I grabbed this old sample that Bea Costa posted a while back and made the changes described above.  The modified sample is available here.
    Dr. WPF - Online Office at http://drwpf.com/blog/
    • Proposed as answer by Dr. WPF Wednesday, August 27, 2008 7:16 AM
    • Marked as answer by mgagne_98 Wednesday, August 27, 2008 10:56 AM
    Wednesday, August 27, 2008 7:13 AM

All replies

  • UP...
    Tuesday, August 26, 2008 5:53 PM
  • The easiest thing to do here would be to bind your list of SuperObjects to the tree and style them using a DataTemplate. Then all you have to do is set treeView.SelectedItem equal to your SuperObject.

    When bound, TreeView.Items returns a collection of your bound objects instead of TreeViewItems, so I would guess that since you have recycling and virtualization on that when a TreeViewItem is collapsed you would not be able to find child TreeViewItems since the containers don't exist yet.
    Coffee.
    Tuesday, August 26, 2008 7:00 PM
  • SelectedItem is readonly in WPF. This is not a Windows Form tree... :(
    Tuesday, August 26, 2008 8:29 PM
  • Ah, sorry I had hierarchical ListBoxes on the mind... I was thinking that Bea Costa's blog had some information on this but I don't think that's the case. Not sure how to do it without that method, but what I think is strange about your scenario is that it doesn't find the TreeViewItem when the parent is expanded. Possibly you could collapse then re-expand the parent as a temporary fix until you find the correct method.
    Coffee.
    Tuesday, August 26, 2008 8:56 PM
  • If you set up your data object correctly, you will never need to walk the visual tree to select or expand nodes within the tree.  It can all be done within the view model.

    The trick is to expose an IsExpanded and an IsSelected property on your data object.  Then use the item container style to bind these properties to the IsExpanded and IsSelected properties on TreeViewItem:

     
        <Setter Property="IsSelected" Value="{Binding Path=IsSelected}" /> 
        <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded}" /> 
     

    I typically structure my hierarchical data such that a child data item maintains a pointer to its parent (but this is simply to make it easier to work with in code).  If you do this, you can select an item and make sure that it is actually visible by doing something like the following:

     
    void SelectItem(MyObject someItem)  
    {  
        someItem.IsSelected = true;  
        while (someItem.Parent != null)  
        {  
            someItem = someItem.Parent;  
            someItem.IsExpanded = true;  
        }  
    }  
     

    Dr. WPF - Online Office at http://drwpf.com/blog/
    • Proposed as answer by Dr. WPF Wednesday, August 27, 2008 1:00 AM
    • Marked as answer by mgagne_98 Wednesday, August 27, 2008 10:57 AM
    Wednesday, August 27, 2008 1:00 AM
  • Hello!

    I love your solution! It works and it's elegant. On the other hand, I'm not sure to like a property IsSelected in my BOL, but who cares, it works!

    My only problem is that I don't know the parent of my TreeViewItem. My TreeView is binded to this...


    1<CollectionViewSource x:Key="collectionViewSource" Source="{Binding Path=sList}"
    2  <CollectionViewSource.GroupDescriptions> 
    3    <PropertyGroupDescription PropertyName="Category" /> 
    4  </CollectionViewSource.GroupDescriptions> 
    5</CollectionViewSource> 
    6 

    So the grouping is done by WPF... How do I expand the parent node if I don't know it?

    Thanks!
    Wednesday, August 27, 2008 4:11 AM
  • If you are using a collection view with a grouping property to achieve the hierarchy, then the approach I described above won't work.  In your case, you have flat data.  That means you don't have a way to get to the parent data item (because it really only exists in the collection view... not in the data).

    To expand the parent TreeViewItem (TVI), you will need to locate the group that contains the target data item.  You can do this by enumerating the items in the TreeView.  They will be of type CollectionViewGroup.  One of the groups will contain your item.  When you find that group, you will need to get its corresponding TVI.  This can be done using the ItemContainerGenerator.ContainerFromItem() method.  Once you have the TVI, you can set its IsExanded property to true.

    The tricky part is that you can't get the container until it has been generated.  (For a good explanation of item container generation, see 'G' is for Generator in my ItemsControl series.)  You can use the StatusChanged event of the TreeView's ItemContainerGenerator to know when the containers have been generated.  Then it is safe to locate and expand the desired group.

    I grabbed this old sample that Bea Costa posted a while back and made the changes described above.  The modified sample is available here.
    Dr. WPF - Online Office at http://drwpf.com/blog/
    • Proposed as answer by Dr. WPF Wednesday, August 27, 2008 7:16 AM
    • Marked as answer by mgagne_98 Wednesday, August 27, 2008 10:56 AM
    Wednesday, August 27, 2008 7:13 AM
  • Thank you very much for the help Dr. WPF! My issue is fixed now... :) Thank you again.
    Wednesday, August 27, 2008 10:56 AM
  • Thanks so much, I try to solve for 3 days and your solution does really do that, what I Need:)
    Wednesday, September 7, 2016 11:38 AM