Answered by:
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,
Martin1 /// <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> 6 public 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> 17 private 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/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/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/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/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