WPF Standard Control Demo App › TreeView
TreeView List
TreeView shows hierarchical data as nodes that the user expands and collapses. Nodes come from nested TreeViewItems or from data with a HierarchicalDataTemplate.
Not virtualized by default
TreeView derives from ItemsControl, and each node is a TreeViewItem, which derives from HeaderedItemsControl and holds its own child nodes. Unlike ListBox, a TreeView is not virtualized by default. With 1,000 root nodes in a TreeView 100 high, VirtualizingPanel.IsVirtualizing was False, the items were arranged in a StackPanel, and all 1,000 TreeViewItems were created. With VirtualizingPanel.IsVirtualizing="True", the panel became a VirtualizingStackPanel and 12 were created. Turn it on for large trees.
An IsExpanded binding is removed unless it is TwoWay
IsExpanded on a TreeViewItem tells whether a node shows its children. Unlike IsSelected, it does not bind two-way by default. With a check box bound to Node 1’s IsExpanded without a Mode, collapsing Node 1 with its own expander button removed that binding. The node collapsed but the check box stayed checked, and unchecking and checking the box afterward no longer expanded the node. Write Mode=TwoWay for such a binding.
Bound two-way through ItemContainerStyle, the state is kept in the data: a child whose source had IsExpanded = true under a collapsed parent had no container yet, and was expanded as soon as the parent was expanded. Keep expansion in the data.
For a folder tree that loads children when a node is expanded, a placeholder child that the Expanded handler replaced worked: a real click on the expander button showed the two real children.
Selection goes through IsSelected, not SelectedItem
One node at a time is selected. SelectedItem and SelectedValue are read-only: setting a binding on SelectedItem threw ArgumentException. Selection is changed through each node’s IsSelected instead; Selecting and Expanding a WPF TreeView Node from Code, and Why SelectedItem Is Read-Only covers that in detail. SelectedItem returned the TreeViewItem for nodes written in XAML, and the data object for nodes created from ItemsSource.
IsSelected binds two-way by default. Selecting a node deselects the previous one and writes False back to its source. With IsSelected bound through ItemContainerStyle, setting Desktop and then Mobile to True in the data left Mobile selected and Desktop’s source False.
IsSelectionActive (the attached Selector.IsSelectionActive) is read-only and tells whether the selection has keyboard focus. Selected from code without focus, Node 2 had IsSelectionActive False. Once focused it was True. After focus moved to a TextBox, it was False again while IsSelected stayed True.
SelectedValuePath names the property of the selected node that SelectedValue returns. With Header, selecting “Gaming PC” gave a SelectedValue of “Gaming PC”.
HierarchicalDataTemplate, and what a node’s data reaches
A HierarchicalDataTemplate describes a node and, through its ItemsSource, where its children come from. Set as the TreeView’s ItemTemplate, it was applied at every level: a second-level node showed “Workstation PC” through the template. As an implicit template in resources, it applies only to items of its DataType. With a mismatched DataType, the nodes did not become empty: they showed the item’s ToString() and could not be expanded.
A ContextMenu set in ItemContainerStyle had no DataContext before it opened; while open, its DataContext was the node’s data object.
Keyboard
With node A selected and focused (A has children, the first of which has its own child), the down arrow selected the next node. The right arrow expanded A, and the left arrow collapsed it again. The numpad * expanded A and everything under it. Space and Enter changed neither the selection nor the expansion.
| case | measured |
|---|---|
| demo IsExpanded section, node collapsed by its expander: IsExpanded / CheckBox / binding | False / True / removed |
| then the CheckBox unchecked and checked again: IsExpanded | False |
| child IsExpanded=true (source) under a collapsed parent: container before / after expanding | not created / created, IsExpanded True, leaf created |
| explicit HierarchicalDataTemplate: text of a second-level node | "Workstation PC" |
| implicit HierarchicalDataTemplate, DataType=Node (items are Node) | first node shows "Desktop", expandable True |
| implicit HierarchicalDataTemplate, DataType=String (items are Node) | first node shows "ToString:Desktop", expandable False |
| 1,000 root nodes, IsVirtualizing not set: value, panel, TreeViewItems | False, StackPanel, 1000 |
| 1,000 root nodes, IsVirtualizing True: value, panel, TreeViewItems | True, VirtualizingStackPanel, 12 |
| key Down on A (selected, focused) | selected B; A closed, A1 closed |
| key Right on A (selected, focused) | selected A; A open, A1 closed |
| key Left (after Right) on A (selected, focused) | selected A; A closed, A1 closed |
| key Space on A (selected, focused) | selected A; A closed, A1 closed |
| key Enter on A (selected, focused) | selected A; A closed, A1 closed |
| key numpad * on A (selected, focused) | selected A; A open, A1 open |
| ContextMenu from ItemContainerStyle (Mobile): DataContext before / while open | null / ToString:Mobile |
| ScrollViewer.HorizontalScrollBarVisibility / VerticalScrollBarVisibility (value source) | Auto (DefaultStyle) / Auto (DefaultStyle) |
| placeholder replaced in Expanded: before; after real click on expander (loads) | (loading); Child 1, Child 2 (1) |
Expansion, templates, virtualization, keys, and context menus. Measured on .NET 10 / Windows 11.
Trying it in the demo app

The TreeView page of the demo app has sections for IsExpanded, IsSelected and IsSelectionActive, SelectedValuePath and SelectedValue, ItemTemplate with a HierarchicalDataTemplate, and the scroll bars. The IsExpanded section binds a check box to Node 1’s IsExpanded without a Mode, so the binding is removed as described above. The SelectedValuePath section takes the path from a text box, over nodes written in XAML. The scroll bar section has a long first node with five children so both bars can be tried; the default style sets both ScrollViewer.HorizontalScrollBarVisibility and ScrollViewer.VerticalScrollBarVisibility to Auto. The “Show Code” link under each section displays its XAML. The following XAML is the ItemTemplate / HierarchicalDataTemplate section (TreeViewUsageControl.xaml), with the styles and the surrounding GroupBoxes left out and the namespace declarations added. The template, which the demo app keeps in the GroupBox’s resources, is in the StackPanel’s resources here. DeviceTree is a collection of items with Name and Children:
<StackPanel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel.Resources>
<HierarchicalDataTemplate x:Key="MyTreeViewTemplate" x:Name="MyTreeViewTemplate"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Path Margin="0,0,5,0" VerticalAlignment="Center"
Data="M0,0 L8,4 L0,8 Z" Fill="Orange" />
<TextBlock VerticalAlignment="Center" Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</StackPanel.Resources>
<TreeView x:Name="ItemTemplateTreeView" Height="150"
ItemTemplate="{StaticResource MyTreeViewTemplate}"
ItemsSource="{Binding DeviceTree}" />
</StackPanel>
Related controls and articles
- Selecting and Expanding a WPF TreeView Node from Code, and Why SelectedItem Is Read-Only — selecting nodes from code and when their containers exist.
- ListBox — a flat list, virtualized by default when its items come from
ItemsSource(not whenListBoxItems are added directly). - Expander — a single collapsible section.
Source code and how it was measured
Every behavior on this page was measured by running it on .NET 10 / Windows 11, using TreeViewDemoScene in this site’s screenshot tool. Keys were reproduced by sending key events to a displayed window, and the expander button by toggling it through UI Automation, which runs the same toggle handling as a click. Only the expander of the node with a placeholder child was clicked with the real mouse.