How to get a ListBoxItem from a data bound ListBox

Data binding a list box to an enumeration of items could not be easier in WPF:
<Window.Resources>
<local:GreekGods x:Key="greekGods"/>
<DataTemplate x:Key="itemTemplate">
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</Window.Resources>
<ListBox ItemsSource="{StaticResource greekGods}" ItemTemplate="{StaticResource itemTemplate}" Name="listBox"/>
The ItemsSource property of ListBox takes an IEnumerable, which is the list of items you want to display. In this case, the GreekGods data source is of type ObservableCollection, which implements IEnumerable. The ItemTemplate property specifies the DataTemplate that will be used to control how the data is displayed. In this case, we will have a TextBlock for each item that will display the GreekGod’s name.
Some of you might find surprising, however, that doing listBox.Items[i] in code returns the data we’re binding to, and not the TextBlock or the ListBoxItem. In my opinion, it is actually pretty cool that retrieving the data in a particular position of the list box is so easy, because most of the time this is exactly what you want.
GreekGod greekGod = (GreekGod)(listBox.Items[0]);
But what about when you want to have access to the actual ListBoxItem generated? This is a bit tricky to discover but can be just as easily done with the following code:
ListBoxItem lbi1 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromIndex(0));
There is also a listBox.ItemContainerGenerator.ContainerFromItem(object item) that returns the ListBoxItem given the corresponding data item. This method is frequently used, for example, to retrieve the ListBoxItem for the current item:
ListBoxItem lbi2 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromItem(listBox.Items.CurrentItem));
I will talk about selection and current item in detail in some other post, but for this sample it is sufficient to know that to keep the selection and current item in sync, I set IsSynchronizedWithCurrentItem=”true” in the ListBox.
Here you can find the VS project with this sample code. This works with September CTP WPF bits. Put a breakpoint at the end of the ButtonClick handler in Window1.xaml.cs to inspect the values of the variables.
Update: You can download the WPF 3.5 (VS 2008) version of this post, or the Silverlight 3 version.
Anonymous
Given that I have a reference to the ListBoxItem, how would I get a reference to the TextBox within the ListBoxItem created via the DataTemplate
September 22, 2005 at 8:03 am
Bea
Today we don’t have an easy way to get to the ListBoxItem’s children in the scenario you described. Although we are trying to discourage people to do this, the only workaround I can think of is to walk the visual tree of the ListBoxItem by calling VisualOperations.GetChildren(…) recursively until you find the ContentPresenter with the Content set to the data item you want. Once you have the ContentPresenter, you can use the following code to get, for example, to a Grid inside the DataTemplate:
DataTemplate myDataTemplate = (DataTemplate)this.Resources["myDataTemplate"];
Grid myGrid = (Grid)myDataTemplate.FindName(“myGrid”, myContentPresenter);
September 23, 2005 at 10:17 pm
Paul
Hello Bea,
Could you link me to some more explanation as to why this is discouraged?
I need to do this in SILVERLIGHT and not WPF. To the previous commenter and those that will read this in future, note that the MSDN has a great example of accessing a textbox inside a WPF datatemplate here: http://msdn.microsoft.com/en-us/library/bb613579.aspx
Now, any clues on silverlight? I’m stuck without this nifty “FindVisualChild” bit when I’m using Silverlight! Can’t get the VisualTreeHelper to do anything either.
January 13, 2010 at 10:58 pm
Bea
Hi Paul,
Relying too much on the visuals is in general not a good practice. For example, if you decide to have a designer style your app which contains a ListBox, it could be that the a certain visual element you had initially is removed. If you’re relying on it for the logic of your program, the app will break. In WPF, this can also be an issue when the user changes the windows theme. If you don’t override the default styles, controls will have different visual trees in different themes, and it’s not a good idea to rely of visual elements of these styles. This really comes down to striving to achieve the best possible separation between UI and logic.
Having said that, there are scenarios where you can’t avoid having to walk the visual tree. For those scenarios, I use the “FindDescendents” method that I paste in my reply to Oli, further down in the comments to this blog post. This method works in Silverlight without changes.
Let me know if this helps.
Bea
January 21, 2010 at 11:25 am
jason d
Hey Beatriz. This is great stuff, but I have a bigger question. What if you’d like to specify things about the ListBoxItems before they are created? As an example, let’s say you want to be notified when the user mouses over a row in your ListBox. ListBoxItem (or ListViewItem) extends ContentControl, so it has MouseEnter and MouseLeave events. But as a WPF developer binding an ObservableCollection to a ListBox, we are not creating the ListBoxItems ourselves. They are created by some FrameworkElementFactory by the WPF framework I’d imagine, when the ListBox is having it’s visual tree built. So how can we get access to that FrameworkElementFactory, and register some methods to be invoked upon the MouseEnter/MouseLeave events being fired from the ListBoxItem?
Thanks! Great stuff here. This blog is doing more to explain the internals of WPF databinding than anything I’ve seen yet!
August 2, 2007 at 1:21 pm
Bea
Hi jason,
You can add MouseEnter and MouseLeave events to the ListBoxItem’s Style, with the help of an event setter. In general styles and templates allow you to make significant changes to the ListBoxItems at the time they’re created.
Bea
October 11, 2009 at 1:26 pm
Nate
I have to say this is the best resource in regard to binding in WPF.
I just started with WPF, and I got some simple binding (as in to a datatable or dataset) working, but I could not find the one magical keyword that I found in one of your samples… ObservableCollection. Now I can actually use a real class that can update as well!
Thanks a TON!
August 20, 2007 at 10:45 am
Bea
Thanks a lot Nate, I appreciate your feedback!
August 22, 2007 at 9:17 am
Andy
Hi Bea,
Just a cautionary note, the ItemContainerGenerator creates and destroys containers based on whether a container is currently visible. The default behavior of ListBox uses a VirtualizingStackPanel as an ItemsPanel, this means if you attempt to locate a ListBoxItem for an item which is currently not visible in the List the ItemContainerGenerator may return null.
If you need consistent access to all ListBoxItems bound to the list items you should consider using a standard ‘StackPanel’ as the ListBox ItemsPanel, which is non-virtualizing, which I neede to do when I coded a ‘MultiSelectComboBox’ (custom control) which required access to the ‘selected ListBoxItems’ when the ComboBox ‘popup (containing a ListBox of CheckBox’s)’ was not visible.
Cheers,
Andy
July 22, 2009 at 9:01 am
Bea
Hi Andy,
Right - if the ListBoxItem has been virtualized, you can’t get to it using this method. You can turn off virtualization by using a StackPanel as the ListBox panel, as you mention. That will work well if you’re only displaying a few items, but if you have more than several hundred items, you will see that the performance degrades significantly.
One option you may want to consider is to use UIAutomation to find the items you want and virtualize them if necessary. WPF 4.0 introduces two new automation patterns that allow you to do that - ItemContainerPattern and VirtualizedItemPattern. ItemContainerPattern allows you to find a container regardless of whether it has been virtualized, and VirtualizedItemPattern allows you to “de-virtualize” (or “realize”) a specific virtualized item. This is very useful in testing, and may be useful for your scenario too.
Bea
November 20, 2009 at 12:12 pm
Turner
When I tried using this method, not all of the listbox items were able to be retrieved-in fact, only about 22 out of 500 or so items were retrieved. I assume this is due to some sort of virtualization issue-is there another way to get the item, or a way to work around this issue?
August 13, 2009 at 7:30 am
Bea
Hi Turner,
Yes, as you guessed that’s because the ListBoxItems are virtualized. The only way around it is to turn off virtualization, which you can do by telling the ListBox to use a StackPanel instea of a VirtualizingStackPanel to host its items. Here’s the syntax:
<ListBox ItemsSource=”{Binding}”>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
You can read more about virtualization in the following post: https://www.zagstudio.com/blog/497.
Bea
October 11, 2009 at 1:22 pm
Oli
Hi,
Learning WPF I’ve come across a number of great articles from you, thanks!
I have a question similar to the one asked about getting reference to a particular control within the listboxitem. I need to do the same thing here as I am looking to change the itemsSource on a particualr comboBox contained within my listboxes datatemplate.
Would it be possible for you to ellaborate on this please - I haven’t been able to find a good example of using VisualOperations.GetChildren.
One last thing, you mention that this is something to be discouraged. Why is that?
Thanks very much.
September 11, 2009 at 3:41 am
Bea
Hi Oli,
I try as much as I can not to rely on visual tree walks because it’s brittle. Next time someone changes the DataTemplate, your code that relied on a particular element being there may fail. Having said that, there are certainly scenarios where walking the visual tree can be very useful. I use the following method to find descendents of a particular element:
public static T FindDescendent(DependencyObject element) where T : class stack = new Stack ();
{
Stack
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
stack.Push(VisualTreeHelper.GetChild(element, i));
}
while (stack.Count > 0)
{
DependencyObject poppedElement = stack.Pop();
T tPoppedElement = poppedElement as T;
if (tPoppedElement != null)
{
return tPoppedElement;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(poppedElement); i++)
{
stack.Push(VisualTreeHelper.GetChild(poppedElement, i));
}
}
return null;
}
Hope this helps.
Bea
November 19, 2009 at 7:56 pm
BlueSky
Typo?
ListBoxItem lbi2 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromIndex(listBox.Items.CurrentItem));
should be:
ListBoxItem lbi2 = (ListBoxItem)(listBox.ItemContainerGenerator.ContainerFromItem(listBox.Items.CurrentItem));
October 9, 2009 at 8:39 am
Bea
Yes, it should, thanks for letting me know. I’ve corrected that in the post.
Bea
October 11, 2009 at 11:19 am
Mike Pelton
Just as useful in 2010 as it was in 2005. Many thanks!
February 26, 2010 at 9:40 am