Controls
ListBox
ListBox is the selectable vertical items control for inspectors, pickers, navigation rails, and scrolling data lists. It layers the ItemsControl item-generation pipeline on top of Selector selection state, then hosts realized ListBoxItem containers inside a templated ScrollViewer so selection, styling, grouping, keyboard input, and scrolling stay on the same control surface.
Quick start
Use ListBox when you need a vertically stacked list with built-in selection, scrolling, and item-container styling. It works with explicit ListBoxItem markup, raw object collections, and grouped or collection-view-backed sources.
Minimal single-select list
<ListBox Width="260"
Height="180"
SelectedIndex="0">
<ListBoxItem>
<Label Content="Inbox" />
</ListBoxItem>
<ListBoxItem>
<Label Content="Drafts" />
</ListBoxItem>
<ListBoxItem>
<Label Content="Archive" />
</ListBoxItem>
</ListBox>
Bound object list with projected text
<ListBox Width="320"
Height="220"
ItemsSource="{Binding RecentProjects}"
DisplayMemberPath="Name"
SelectedItem="{Binding ActiveProject}"
SelectedValuePath="Id"
SelectedValue="{Binding ActiveProjectId}" />
Extended multi-select with scrolling
<ListBox Width="340"
Height="220"
SelectionMode="Extended"
VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding CandidateFiles}" />
Default recommendation: use bound data plus DisplayMemberPath for simple row text, then move to ItemTemplate, ItemContainerStyle, or explicit ListBoxItem markup when the row needs richer visuals or behavior.
Items and container model
ListBox inherits from Selector, which inherits from ItemsControl. The backing items can come from Items, ItemsSource, or a grouped ICollectionView, but the interactive row contract is always a realized ListBoxItem container.
Explicit containers
If you supply ListBoxItem elements directly, the control preserves them as-is. They are not replaced by ItemTemplate or by the generated-row pipeline.
Generated containers
If you add plain objects, ListBox creates ListBoxItem containers for them. That remains true even when ItemTemplate or ItemTemplateSelector is used.
ItemsandItemsSourcefollow the normalItemsControlrule: do not mix direct item mutation with an activeItemsSource.- Raw
UIElementitems become the content of a generatedListBoxItem, not standalone rows outside the container model. - When neither
ItemTemplatenorItemTemplateSelectoris present, generated text rows useDisplayMemberPathandItemStringFormatbefore falling back toToString(). - Generated label rows inherit the list box typography fast-path for
FontFamily,FontSize,FontWeight, andFontStyle.
Simple text rows from a bound object collection
<ListBox Width="340"
Height="220"
ItemsSource="{Binding Agents}"
DisplayMemberPath="DisplayName"
ItemStringFormat="[{0}]" />
Templated rows without losing ListBoxItem containers
<ListBox Width="360"
Height="220"
ItemsSource="{Binding Agents}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Width="10" Height="10" Background="#4FC7C1" Margin="0,0,10,0" />
<StackPanel Grid.Column="1">
<Label Content="{Binding Name}" />
<Label Content="{Binding Status}" Opacity="0.7" />
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Important precedence rule: ItemTemplate changes the content inside generated ListBoxItem containers. It does not replace explicit ListBoxItem elements and it does not remove the row container itself.
Selection model and data API
ListBox exposes the standard Selector properties, including SelectedIndex, SelectedItem, SelectedValue, SelectedValuePath, and the read-only SelectedItems projection. Selection changes still raise the bubbled SelectionChanged routed event.
Single
SelectionMode="Single" keeps exactly one primary selection. Setting SelectedIndex, SelectedItem, or SelectedValue replaces the current row.
Multiple and Extended
SelectionMode="Multiple" is direct toggle mode. SelectionMode="Extended" adds WPF-style range and modifier gestures while keeping SelectedItems and SelectedIndices synchronized.
SelectedIndex = -1,SelectedItem = null, orSelectedValue = nullclears selection.SelectedValueresolves throughSelectedValuePath; when no path is supplied, it mirrorsSelectedItem.SelectedItemsis a read-only live projection of the current selected backing items.SelectedIndicesremains the index-based inspection API for multi-selection scenarios.
Observe selection and selected values
listBox.SelectionChanged += (_, e) =>
{
Logger.Write($"Primary item: {listBox.SelectedItem}");
Logger.Write($"Selected value: {listBox.SelectedValue}");
foreach (var item in listBox.SelectedItems)
{
Logger.Write($"Selected item: {item}");
}
};
Select by projected value
listBox.SelectedValuePath = nameof(ServerEntry.Id);
listBox.SelectedValue = 42;
Pointer and keyboard behavior
Pointer selection happens against realized ListBoxItem containers, and focused list boxes participate in the shared input pipeline so keyboard navigation can change selection and viewport state without extra wiring.
Pointer gestures
Single-click selects. In Multiple mode, click toggles the clicked item. In Extended mode, plain click replaces selection, Ctrl+click toggles, Shift+click selects a range from the anchor, and Ctrl+Shift+click adds a range without clearing the existing one.
Keyboard gestures
Focused list boxes handle Up, Down, Home, End, PageUp, and PageDown. In Extended mode, Shift+Arrow extends the current range, Ctrl+A selects all, and Ctrl+Space toggles the focused row.
- Arrow and paging commands keep the new active item fully visible when selection moves.
Ctrl+Ais available for all non-single-select modes.ListBoxItemraises bubbledSelectedandUnselectedrouted events as its selection state changes.
Extended multi-select picker
<ListBox Width="360"
Height="240"
SelectionMode="Extended"
ItemsSource="{Binding SearchResults}" />
Programmatic selection and reveal
listBox.SelectedIndex = 27;
listBox.ScrollIntoView(listBox.SelectedItem);
Collection views, grouping, and current item sync
ListBox works with projected collection views, including grouped views. When the bound source exposes a grouped ICollectionView and you add one or more GroupStyle entries, the presenter projects GroupItem containers instead of a flat row list.
Grouped views
Use a grouped collection view plus GroupStyle when you want category headers or grouped item sections inside the same ListBox.
Current item synchronization
IsSynchronizedWithCurrentItem="True" couples the list box with the active ICollectionView.CurrentItem in single-select mode. Selection can pull from or push to the current item position.
- Current-item synchronization is meaningful only for single-select lists.
- Changing the view's current item can update
SelectedItemandSelectedIndex. - Changing the list box selection can move
CurrentItemandCurrentPositionon the bound view.
Grouped list backed by a collection view
<ListBox Width="360"
Height="260"
ItemsSource="{Binding AgentGroups}"
IsSynchronizedWithCurrentItem="True">
<ListBox.GroupStyle>
<GroupStyle />
</ListBox.GroupStyle>
</ListBox>
Presentation, items panel, and templates
The control now exposes the broader ItemsControl presentation surface. That means you can independently control text projection, item content templates, per-item container styles, the items host panel, and the outer control template.
Row presentation
DisplayMemberPath, ItemStringFormat, ItemTemplate, and ItemTemplateSelector control the content shown inside generated containers.
Container presentation
ItemContainerStyle and ItemContainerStyleSelector style the ListBoxItem containers themselves, which is where selection and hover visuals should usually live.
ItemsPanelsupplies the panel used as the items host. If you do not set it, the control uses a vertical host and switches toVirtualizingStackPanelwhenIsVirtualizingis enabled.- If you set
ItemsPanelyourself, that custom panel takes precedence over the built-in host selection. - The default control template expects a
ScrollViewernamedPART_ScrollViewer. That part becomes the row host for scrolling, selection reveal, and viewport management.
Per-item container styling by selector
public sealed class CategoryStyleSelector : StyleSelector
{
public override Style? SelectStyle(object? item, DependencyObject container)
{
if (item is AgentRow row && row.IsOnline)
{
return OnlineStyle;
}
return OfflineStyle;
}
}
listBox.ItemContainerStyleSelector = new CategoryStyleSelector();
Custom items panel
<ListBox Width="360"
Height="220"
ItemsSource="{Binding Logs}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Custom template that preserves the scroll host contract
<Style TargetType="{x:Type ListBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ScrollViewer x:Name="PART_ScrollViewer"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalScrollBarVisibility="{TemplateBinding HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding VerticalScrollBarVisibility}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Scrolling, layout, and virtualization
ListBox measures and arranges its active scroll host, then keeps selection changes and explicit reveal requests aligned with the viewport. The default host is vertical and optimized for row lists, but you can replace it through ItemsPanel or the outer control template.
Scroll settings
Horizontal scrolling defaults to Disabled. Vertical scrolling defaults to Auto. LineScrollAmount defaults to 24 device-independent units.
Viewport reveal
ScrollIntoView(object) brings a specific item into view, and selection-driven keyboard or pointer changes automatically reveal the active row when needed.
IsVirtualizing="True"switches the default host toVirtualizingStackPanel.- The clip rect matches the list box layout slot, so scrolled content remains clipped to the control bounds.
BorderThicknessis a non-negative singlefloatvalue on this control, not a four-sidedThickness.
Virtualized event stream
<ListBox Width="420"
Height="260"
IsVirtualizing="True"
VerticalScrollBarVisibility="Auto"
LineScrollAmount="18"
ItemsSource="{Binding EventLines}" />
Reveal a backing item directly
var target = viewModel.SearchResults[79];
listBox.ScrollIntoView(target);
listBox.SelectedItem = target;
Property reference
Selection API
SelectedIndex: the primary selected row index, or -1 when nothing is selected.
SelectedItem: the primary selected backing item.
SelectedItems: read-only projection of every selected backing item.
SelectedIndices: read-only projection of every selected index.
SelectedValue and SelectedValuePath: projected selection value support.
SelectionMode: Single, Multiple, or Extended.
IsSynchronizedWithCurrentItem: current-item coupling for single-select collection views.
Item pipeline
Items and ItemsSource: backing item collection sources.
DisplayMemberPath and ItemStringFormat: default generated-text projection.
ItemTemplate and ItemTemplateSelector: content templates inside generated ListBoxItem containers.
ItemContainerStyle and ItemContainerStyleSelector: row container styling.
ItemsPanel: explicit host panel template.
GroupStyle: grouped collection-view projection.
Scrolling and layout
IsVirtualizing: switches the default host to a VirtualizingStackPanel.
HorizontalScrollBarVisibility and VerticalScrollBarVisibility: forwarded to the active ScrollViewer host.
LineScrollAmount: positive scroll increment for line-based scrolling.
ScrollIntoView(object): reveals a specific backing item.
Chrome and template contract
Background and BorderBrush: Color values used by the default themed template.
BorderThickness: non-negative single-width border around the hosted scroller.
Template: custom control template support; preserve PART_ScrollViewer when replacing the default structure.
The companion row type is ListBoxItem, which exposes IsSelected, IsMouseOver, selection-aware chrome properties, and bubbled Selected / Unselected routed events.
Patterns and examples
Navigation rail
<ListBox Width="240"
Height="260"
SelectedIndex="0">
<ListBoxItem>
<Label Content="Overview" />
</ListBoxItem>
<ListBoxItem>
<Label Content="Build output" />
</ListBoxItem>
<ListBoxItem>
<Label Content="Diagnostics" />
</ListBoxItem>
</ListBox>
Object picker with selected value binding
<ListBox Width="320"
Height="220"
ItemsSource="{Binding Servers}"
DisplayMemberPath="DisplayName"
SelectedValuePath="Id"
SelectedValue="{Binding ActiveServerId}" />
Restyle rows while keeping selection on the container
<ListBox Width="340"
Height="220"
ItemsSource="{Binding Agents}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="#152230" />
<Setter Property="SelectedBackground" Value="#28445F" />
<Setter Property="BorderBrush" Value="#35516B" />
<Setter Property="Padding" Value="12,8" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Drive a details panel from the primary selection
private void OnServerListSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (ServerList.SelectedItem is ServerEntry selected)
{
DetailHost.Content = BuildServerPanel(selected);
}
}
Grouped operational view
<ListBox Width="380"
Height="260"
ItemsSource="{Binding GroupedIncidents}"
IsSynchronizedWithCurrentItem="True">
<ListBox.GroupStyle>
<GroupStyle />
</ListBox.GroupStyle>
</ListBox>
Notes and pitfalls
Keep PART_ScrollViewer in custom templates. The default template contract expects a scroll host with that name. If you replace the control template, preserve that part so scrolling, viewport metrics, and item hosting remain connected.
ItemsPanel wins over IsVirtualizing. If you explicitly supply an ItemsPanelTemplate, that panel becomes the host. Choose a VirtualizingStackPanel yourself if you still want virtualization on a custom panel path.
Row visuals belong on ListBoxItem. Styling only the content inside the row can leave hover and selection visuals disconnected from the clickable surface. Put row chrome on the container through ItemContainerStyle or explicit ListBoxItem markup.
Background and BorderBrush are colors, not brush objects. Keep examples and styles in terms of values that resolve to Color.
Current-item synchronization is single-select behavior. IsSynchronizedWithCurrentItem is designed for single-selection collection-view scenarios. Multi-select lists still expose SelectedItems, but they do not use current-item syncing as the primary model.