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, with the control list on the left and the first section, IsExpanded(TreeViewItem)

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>

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.

View TreeView source code on GitHub →