WPF Standard Control Demo App › ListBox
ListBox List
ListBox shows a scrollable list from which the user selects one item or, with SelectionMode, several.
What a click selects in each SelectionMode
ListBox derives from Selector, and ListView derives from ListBox. Items come from Items or ItemsSource, and each item is shown in a ListBoxItem container.
SelectionMode decides how clicks select items; the default is Single. Clicking Item 1, Item 2, and Item 1 without modifier keys gave these selections. With Single: Item 1, then Item 2, then Item 1. With Multiple: Item 1, then Items 1 and 2, then Item 2, because each click toggles an item. With Extended: the same as Single, because a plain click replaces the selection (selecting several items in Extended, for example files for a batch operation, takes Shift or Ctrl, which was not reproduced). SelectAll() selected all four items in Multiple and Extended, and threw NotSupportedException in Single, so check SelectionMode before calling it.
Selecting from code: matched by Equals
Setting SelectedIndex = 2 selected the third item, updated SelectedItem, and raised SelectionChanged once. An out-of-range index (10 with four items) raised no exception and left the selection unchanged; -1 cleared it.
SelectedItem is matched with Equals, not by reference. A new instance of a class that does not override Equals selected nothing. A new instance of a record with the same values selected the matching item. SelectedItem then held the new instance, not the one in the list. When the ViewModel restores a selection from saved data, give the item class a meaningful Equals, or select through SelectedValue and an ID.
SelectedValuePath names the property of the selected item that SelectedValue returns, for example an Id. The order does not matter: SelectedValue = 2 set before ItemsSource selected the item with Id 2 once the items arrived.
DisplayMemberPath is the property of each item to show as text. With DisplayMemberPath="Name", each item was shown by a TextBlock reading “Desktop”. A path that does not exist gave an empty TextBlock, with no exception. It cannot be combined with ItemTemplate: setting both threw InvalidOperationException.
| case | measured |
|---|---|
| SelectedIndex = 2 | SelectedIndex 2, SelectedItem Item 3, SelectionChanged 1 |
| then SelectedIndex = 10 (4 items) | no exception; SelectedIndex 2 |
| then SelectedIndex = -1 | SelectedIndex -1, SelectedItem null |
| class without Equals: SelectedItem = new instance with the same data | SelectedIndex -1, SelectedItem null |
| record (value equality): SelectedItem = new instance with the same data | SelectedIndex 1, same instance as the item: False |
| SelectedValue = 2 before ItemsSource, then ItemsSource set | SelectedValue 2 -> SelectedIndex 1, SelectedValue 2 |
| DisplayMemberPath="Name": element showing item 1 | TextBlock, Text "Desktop" |
| DisplayMemberPath="Nope": element showing item 1 | TextBlock, Text "" |
| DisplayMemberPath and ItemTemplate both set | InvalidOperationException when set; not shown |
SelectedIndex, SelectedItem, SelectedValue, and DisplayMemberPath. Measured on .NET 10 / Windows 11.
Virtualization: what exists, and what scrolls
The default style arranges the items in a VirtualizingStackPanel, so with ordinary data items set through ItemsSource, only the visible containers are created: with 1,000 strings in a list 100 high, six ListBoxItems existed. ListBoxItems added directly to the ListBox are containers themselves and, according to the documentation, are not virtualized.
That is why a selection made from code can be out of view. Setting SelectedIndex = 500 did not scroll the list, and the container for item 500 was not created; ScrollIntoView scrolled to it. Call ScrollIntoView after selecting from code.
It also affects multi-selection. IsSelected on a ListBoxItem binds two-way by default, but in a list created from ItemsSource, the containers are created only while visible, so a binding in ItemContainerStyle covers only those items. How to Prevent SelectedItems from Appearing Lost in a Virtualized WPF ListBox measures how such a selection gets out of sync when the list scrolls. Handle SelectionChanged and update the ViewModel from SelectedItems, or follow the article.
The scroll bars are set with the attached properties ScrollViewer.HorizontalScrollBarVisibility and ScrollViewer.VerticalScrollBarVisibility; the default style sets both to Auto. With 1,000 items, Auto showed the vertical bar. Disabled hid it but did not stop scrolling: the scrollable range was the same (996 items), and 20 presses of the down arrow scrolled the list as far as with Auto.
| case | measured |
|---|---|
| ListBoxItems created at start | 6 |
| SelectedIndex = 500 from code: VerticalOffset / container for item 500 | 0 / not created |
| then ScrollIntoView(SelectedItem): VerticalOffset / container for item 500 | 497 / created |
| VerticalScrollBarVisibility=Auto: scrollable / bar; after 20 Down keys | 996 / Visible; SelectedIndex 20, offset 17 |
| VerticalScrollBarVisibility=Disabled: scrollable / bar; after 20 Down keys | 996 / Collapsed; SelectedIndex 20, offset 17 |
Virtualization and scrolling with 1,000 items in a list 100 high. Offsets are in items. Measured on .NET 10 / Windows 11.
Trying it in the demo app

The ListBox page of the demo app has a section for each of SelectionMode, DisplayMemberPath, SelectedIndex and SelectedItem, SelectedValuePath and SelectedValue, IsSelected, and the scroll bars. The SelectionMode section shows SelectedItems.Count under the list. The DisplayMemberPath and SelectedValuePath sections hold a list of brushes and take the path from a text box: Name shows the brush names and any other text leaves the items blank, and as the value path, Value returns the brush and Name its name. The IsSelected section binds two ListBoxItems to check boxes, so checking a box selects the item and clicking the item changes the box. The scroll bar section has a long first item so you can also try the horizontal bar. The “Show Code” link under each section displays its XAML. The following XAML is the IsSelected(ListBoxItem) section (ListBoxUsageControl.xaml), with the styles and the surrounding GroupBoxes left out and the namespace declarations added:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CheckBox x:Name="IsSelectedItem1CheckBox" Content="Select Item 1" />
<CheckBox x:Name="IsSelectedItem2CheckBox" Content="Select Item 2" />
<ListBox x:Name="IsSelectedListBox" Height="100" SelectionMode="Extended">
<ListBoxItem Content="Item 1 (Bound to CheckBox)"
IsSelected="{Binding IsChecked, ElementName=IsSelectedItem1CheckBox}" />
<ListBoxItem Content="Item 2 (Bound to CheckBox)"
IsSelected="{Binding IsChecked, ElementName=IsSelectedItem2CheckBox}" />
<ListBoxItem Content="Item 3 (Independent)" />
</ListBox>
<TextBlock Text="{Binding SelectedItems.Count, ElementName=IsSelectedListBox}" />
</StackPanel>
Related controls and articles
- How to Prevent SelectedItems from Appearing Lost in a Virtualized WPF ListBox — multi-selection with virtualization.
- ListView — derives from ListBox and adds views such as
GridViewcolumns. - ComboBox — a
Selectorwhose list opens as a drop-down.
Source code and how it was measured
Every behavior on this page was measured by running it on .NET 10 / Windows 11, using ListBoxDemoScene in this site’s screenshot tool. Clicks were reproduced by raising the left-button event on the ListBoxItem, which is how a plain click reaches the ListBox. Clicks with Shift or Ctrl were not reproduced, because the ListBox reads the actual keyboard state.