How to get TreeViewItem from HierarchicalDataTemplate item?
Solution 1
From Bea Stollnitz's blog entry about this, try
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
Solution 2
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition));
DOES NOT WORK (for me) as mainTreeList.Items.CurrentPosition in a treeview using a HierarchicalDataTemplate will always be -1.
NEITHER DOES below as as mainTreeList.Items.CurrentItem in a treeview using a HierarchicalDataTemplate will always be null.
TreeViewItem item = (TreeViewItem)mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.Items.CurrentItem);
INSTEAD I had to set a the last selected TreeViewItem in the routed TreeViewItem.Selected event which bubbles up to the tree view (the TreeViewItem's themselves do not exist at design time as we are using a HierarchicalDataTemplate).
The event can be captured in XAML as so:
<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../>
Then the last TreeViewItem selected can be set in the event as so:
private void TreeViewItemSelected(object sender, RoutedEventArgs e)
{
TreeViewItem tvi = e.OriginalSource as TreeViewItem;
// set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null)
this.lastSelectedTreeViewItem = tvi;
...
}
Solution 3
I ran into this same problem. I needed to get to the TreeViewItem so that I could have it be selected. I then realized that I could just add a property IsSelected to my ViewModel, which I then bound to the TreeViewItems IsSelectedProperty. This can be achieved with the ItemContainerStyle:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Now if I want to have an item in the treeview selected, I just call IsSelected on my ViewModel class directly.
Hope it helps someone.
Solution 4
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0.
How about
TreeViewItem item = (TreeViewItem)(mainTreeList
.ItemContainerGenerator
.ContainerFromItem(mainTreeList.SelectedItem)));
This works better for me.
Solution 5
Inspired by Fëanor’s answer, I’ve attempted to make the TreeViewItem
easily accessible for every data item that a TreeViewItem
has been created for.
The idea is to add a TreeViewItem
-typed field to the view model, also exposed via an interface, and have the TreeView
automatically populate it whenever the TreeViewItem
container is created.
This is done by subclassing TreeView
and attaching an event to the ItemContainerGenerator
, which records the TreeViewItem
whenever it’s created. Gotchas include the fact that TreeViewItem
s are created lazily, so there might genuinely not be one available at certain times.
Since posting this answer, I've developed this further and used it for a long time in one project. No issues so far, other than the fact that this violates MVVM (but also saves you a ton of boilerplate for the simple cases). Source here.
Usage
Select the parent of the selected item and collapse it, ensuring that it’s in the view:
...
var selected = myTreeView.SelectedItem as MyItem;
selected.Parent.TreeViewItem.IsSelected = true;
selected.Parent.TreeViewItem.IsExpanded = false;
selected.Parent.TreeViewItem.BringIntoView();
...
Declarations:
<Window ...
xmlns:tvi="clr-namespace:TreeViewItems"
...>
...
<tvi:TreeViewWithItem x:Name="myTreeView">
<HierarchicalDataTemplate DataType = "{x:Type src:MyItem}"
ItemsSource = "{Binding Children}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
</tvi:TreeViewWithItem>
...
</Window>
class MyItem : IHasTreeViewItem
{
public string Name { get; set; }
public ObservableCollection<MyItem> Children { get; set; }
public MyItem Parent;
public TreeViewItem TreeViewItem { get; set; }
...
}
Code
public class TreeViewWithItem : TreeView
{
public TreeViewWithItem()
{
ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
}
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
{
var generator = sender as ItemContainerGenerator;
if (generator.Status == GeneratorStatus.ContainersGenerated)
{
int i = 0;
while (true)
{
var container = generator.ContainerFromIndex(i);
if (container == null)
break;
var tvi = container as TreeViewItem;
if (tvi != null)
tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
var item = generator.ItemFromContainer(container) as IHasTreeViewItem;
if (item != null)
item.TreeViewItem = tvi;
i++;
}
}
}
}
interface IHasTreeViewItem
{
TreeViewItem TreeViewItem { get; set; }
}
Razzie
I graduated at the University of Amsterdam in 2006, in Software Engineering. I primarily develop in C# (since 2003), while I learned to program in Java. I got employed by a Dutch website company in 2006, located in The Hague, where I'm currently a software engineer. In my spare time I like to mess around with WPF a little.
Updated on January 12, 2020Comments
-
Razzie over 4 years
I have a
TreeView
which uses aHierarchicalDataTemplate
to bind its data.It looks like this:
<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}> <TreeView.Resources> <HierarchicalDataTemplate DataType="{x:Type local:MyTreeViewItemViewModel}" ItemsSource="{Binding Children}"> <!-- code code code --> </HierarchicalDataTemplate> </TreeView.Resources> </TreeView>
Now, from the code-behind of say the main window, I want to get the current selected
TreeViewItem
. However, if I use:this.mainTreeList.SelectedItem;
The selectedItem is of type
MyTreeViewItemViewModel
. But I want to get the 'parent' or 'bound'TreeViewItem
. I do not pass that to myTreeViewItemModel
object (wouldn't even know how).How can I do this?
-
Razzie about 15 yearsGood enough for me, thanks. (it's a bit different though, CurrentItem returns an object and ContainerFromIndex takes an integer, so I had to use CurrentIndex or something, but oh well)
-
Cameron MacFarland about 15 yearsAh, I just copied-n-pasted from Bea's blog and then changed a few things. I didn't notice that error :) Still, I'll update my answer.
-
Cameron MacFarland almost 14 yearsThe only way for the CurrentItem to equal null (or CurrentIndex equaling -1) is if there are no items in the tree. Make sure there are items before trying to get the items container.
-
Cameron MacFarland almost 14 yearsThe CurrentItem is not the same as the SelectedItem. The ContainerFromItem returns null if it cannot find the container, and null can be cast to a TreeViewItem just fine without needing to use as cast. Did you even run the code?
-
markmnl almost 14 yearsThe problem I think is you are using mainTreeList.Items which will only work for items in the first level of the treeview - each tree view node (TreeViewItem) has its own Items collection - unlike Bea's example which you quote which is using a ListBox and only has one level of items. It follows that you need to obtain reference currently selected TreeViewItem for which the only way I found to do so when using a HierarchicalDataTemplate is capturing the TreeViewItem.Selected event as in my answer.
-
markmnl almost 14 yearsThe reason CurrentItem returns null is only once a TreeViewItem is expanded are the next level of nodes instantiated. Admittedly this is not clear from the MSDN documentation.
-
markmnl about 13 yearsIncidentally Bea does have a solution for a TreeView too - but that is much messier than mine above as involves waiting for the UI thread to finish doing all its tasks then assuming the TreeViewItems have then been created: bea.stollnitz.com/blog/?p=59
-
Roman Starkov about 12 years
CurrentPosition
is always 0 for me, regardless of what’s selected. The blog entry you link to is about lists, not trees - perhaps that’s why. There’s noHierarchicalDataTemplate
involved in the linked blog post. -
Jason Ridge about 12 yearsIn my opinion this is the best way to use treeviews. The items need to be viewmodels so you can work with them property. Otherwise you end up having to do all sorts of shenanigans in order to get what should be simple behaviour to work correctly.
-
Roman Starkov about 12 yearsHow to add the event in code:
ctLayersTree.AddHandler(TreeViewItem.SelectedEvent, <your handler>)
-
René almost 11 yearsYou can actually simplify the last example. Just use
...ItemGenerator.ContainerFromItem(mainTreeList.SelectedItem)
. This also works if you're using one or moreHierarchicalDataTemplate
's, whereas the first example won't. I allowed myself to edit your answer. I hope that's alright with you. -
markmnl over 10 yearsI agree, however the question was how to get the TreeViewItem not the object it wraps.
-
zionpi about 10 yearsIt has been worked for a period of time,but now CurrentPosition always be 0.
-
L.Trabacchin over 9 yearsInspecting this road has been very intresting, and formative on how wpf works (aka black magic), but the answer with the bounty led me on how to do it correctly. This is not a good way of doing it (with respect) because the items get regenerated, and u keep adding handers, also you are never going to remove those handlers, because there isn't an generator status "removed". We have routed events and we are supposed to learn and use them.
-
Roman Starkov over 9 yearsYeah, this is certainly the way you're supposed to do it. But having a ViewModel for evvverything is quite a PITA, especially for small basic UIs.
-
Roman Starkov over 9 years@L.Trabacchin I would certainly agree that my approach is hacky. TBH, for complex UIs the best bet is almost certainly to abandon the idea of accessing TreeViewItems altogether; that's just not how WPF is supposed to be used. We're supposed to be accessing our ViewModel instances instead. I don't remember why the bountied answer didn't cut it for me, but it does look less hacky than mine.
-
L.Trabacchin over 9 yearsSure, another good way of doing this is using viewmodels, but it depends on what we are trying to achieve, for me the routed events worked better because i was trying to extend the treeview to achieve a multiselect treeview, but i wanted it to be less code as possible, because that could lead to maintenance and bugs, and wanted it to work independently on what model is used, i mostly did it, i have some flaws right now, exposing the selected items to be accessed from others controls ... i wish i know more :) thanks anyway!
-
j riv almost 9 yearsThat settles it to be honest. WPF is a horrific monstrous behemoth of unitunitive mess. So many smart people around here that can't easily figure out a simple thing like getting the name of an item on a big fat visual control, and if they do find an answer, it's an event.
-
markmnl almost 9 years@LelaDax it takes some getting used to, but I am glad I did, and like XAML in general now :) Note: this scenario is unusual - why are you trying to the Visual? Bindings in XAML should obviate the need.
-
Ejrr1085 almost 5 yearsNot work, mainTreeList.SelectedItemis null is redundant, mainTreeList.SelectedItem and TreeViewItem item is the same.