How to Add a TreeView Inside a WPF ComboBox: Display Hierarchical Lists and Set Selected Value

The WPF ComboBox is a versatile control for displaying flat lists of items, but when working with hierarchical data (e.g., categories with subcategories, file directories, or nested menus), it falls short. The TreeView control, on the other hand, excels at visualizing hierarchical relationships but lacks the compact dropdown behavior of a ComboBox.

Combining a TreeView inside a ComboBox creates a powerful hybrid control: a dropdown that lets users select from nested lists while maintaining a clean UI. This guide will walk you through building this control step-by-step, from setting up the project to handling selection and styling.

Table of Contents#

  1. Prerequisites
  2. Step 1: Set Up the WPF Project
  3. Step 2: Define the Hierarchical Data Model
  4. Step 3: Create the ComboBox with TreeView
  5. Step 4: Style the ComboBox and TreeView
  6. Step 5: Handle Selection and Set Selected Value
  7. Step 6: Test the Application
  8. Troubleshooting Common Issues
  9. Conclusion
  10. References

Prerequisites#

  • Visual Studio (2019 or later; 2022 recommended).
  • Basic knowledge of WPF, XAML, and C#.
  • .NET 6 or later (we’ll use .NET 7 in this example, but .NET Framework 4.8+ works too).

Step 1: Set Up the WPF Project#

  1. Open Visual Studio → Create a new project → Select WPF App (.NET) → Name it TreeViewComboBoxDemo → Click Create.
  2. The project will generate a default MainWindow.xaml (UI) and MainWindow.xaml.cs (code-behind). We’ll work primarily in these files.

Step 2: Define the Hierarchical Data Model#

To display hierarchical data, we need a model class that supports parent-child relationships. Let’s create a Category class with:

  • Id: Unique identifier for the item.
  • Name: Display text.
  • Children: A collection of child Category items (for nesting).

Create the Category Class#

Add a new class file (Category.cs) to your project:

using System.Collections.ObjectModel;
 
namespace TreeViewComboBoxDemo
{
    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
        public ObservableCollection<Category> Children { get; set; } = new ObservableCollection<Category>();
 
        // Optional: Override ToString() to display "Name" in the ComboBox by default
        public override string ToString() => Name;
    }
}
  • ObservableCollection<Category> ensures the UI updates if children are added/removed dynamically.
  • Overriding ToString() lets the ComboBox display the Name property by default (we’ll refine this later with DisplayMemberPath).

Step 3: Create the ComboBox with TreeView#

The default ComboBox uses an ItemsPresenter to display flat items. To host a TreeView, we’ll override the ComboBox’s ControlTemplate to replace the default dropdown with a TreeView inside a Popup.

Key Concepts:#

  • ControlTemplate: Defines the visual structure of the ComboBox, including its dropdown (Popup).
  • TreeView in Popup: The Popup will contain a TreeView to render hierarchical data.
  • HierarchicalDataTemplate: Tells the TreeView how to display parent and child items.

Update MainWindow.xaml#

Replace the default Grid in MainWindow.xaml with the following XAML. We’ll break down the code afterward:

<Window x:Class="TreeViewComboBoxDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewComboBoxDemo"
        Title="TreeView in ComboBox Demo" Height="450" Width="800">
    <Grid Margin="20">
        <ComboBox 
            x:Name="cmbTreeView" 
            MinWidth="250" 
            DisplayMemberPath="Name"  <!-- Shows the "Name" property in the ComboBox -->
            SelectedValuePath="Id">   <!-- Uses "Id" as the SelectedValue -->
 
            <!-- Override the ComboBox template to include a TreeView -->
            <ComboBox.Template>
                <ControlTemplate TargetType="ComboBox">
                    <Grid>
                        <!-- ToggleButton: Opens/closes the dropdown -->
                        <ToggleButton 
                            x:Name="ToggleButton" 
                            IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
                            ClickMode="Press">
                            <ToggleButton.Template>
                                <ControlTemplate TargetType="ToggleButton">
                                    <Border 
                                        x:Name="Border" 
                                        Background="White" 
                                        BorderBrush="Gray" 
                                        BorderThickness="1" 
                                        CornerRadius="4">
                                        <Grid>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition Width="*"/>
                                                <ColumnDefinition Width="25"/> <!-- Space for the dropdown arrow -->
                                            </Grid.ColumnDefinitions>
                                            
                                            <!-- Displays the selected item in the ComboBox -->
                                            <ContentPresenter 
                                                Margin="8,0,0,0" 
                                                VerticalAlignment="Center" 
                                                Content="{TemplateBinding SelectionBoxItem}"
                                                ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"/>
                                            
                                            <!-- Dropdown arrow -->
                                            <Path 
                                                Grid.Column="1" 
                                                HorizontalAlignment="Center" 
                                                VerticalAlignment="Center" 
                                                Data="M 0 0 L 4 4 L 8 0 Z" 
                                                Fill="Gray" 
                                                Width="8" 
                                                Height="4"/>
                                        </Grid>
                                    </Border>
                                </ControlTemplate>
                            </ToggleButton.Template>
                        </ToggleButton>
 
                        <!-- Popup: Contains the TreeView -->
                        <Popup 
                            x:Name="Popup" 
                            IsOpen="{TemplateBinding IsDropDownOpen}" 
                            Placement="Bottom" 
                            AllowsTransparency="True"
                            PopupAnimation="Slide">
                            <Border 
                                MinWidth="{TemplateBinding ActualWidth}"  <!-- Match ComboBox width -->
                                MaxHeight="300"  <!-- Limit dropdown height -->
                                Background="White" 
                                BorderBrush="Gray" 
                                BorderThickness="1" 
                                CornerRadius="4"
                                Padding="2">
                                
                                <!-- TreeView for hierarchical data -->
                                <TreeView 
                                    x:Name="PART_TreeView"  <!-- Name for code-behind access -->
                                    MinWidth="{TemplateBinding ActualWidth}"
                                    MaxHeight="290">
                                    
                                    <!-- Define how to display parent/child items -->
                                    <TreeView.ItemTemplate>
                                        <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                                            <TextBlock 
                                                Text="{Binding Name}" 
                                                Padding="2" 
                                                FontSize="14"/>
                                        </HierarchicalDataTemplate>
                                    </TreeView.ItemTemplate>
                                </TreeView>
                            </Border>
                        </Popup>
                    </Grid>
                </ControlTemplate>
            </ComboBox.Template>
        </ComboBox>
    </Grid>
</Window>

Explanation:#

  • ToggleButton: Replaces the default ComboBox button. It controls the IsDropDownOpen state (to show/hide the popup) and displays the selected item via ContentPresenter.
  • Popup: Contains the TreeView and appears when the ToggleButton is clicked. Its MinWidth matches the ComboBox’s width for consistency.
  • TreeView: Renders hierarchical data using HierarchicalDataTemplate, which binds Children to nested items and displays Name for each item.

Step 4: Style the ComboBox and TreeView#

To improve usability, style the TreeView and ComboBox for better appearance:

Add Styles to MainWindow.xaml#

Add these styles inside the Window.Resources section (above the Grid):

<Window.Resources>
    <!-- Style for TreeView items -->
    <Style TargetType="TreeViewItem">
        <Setter Property="IsExpanded" Value="True"/>  <!-- Auto-expand all nodes -->
        <Setter Property="Padding" Value="4,2"/>      <!-- Add spacing between items -->
        <Setter Property="Foreground" Value="#333"/>  <!-- Dark gray text -->
        <Setter Property="FontSize" Value="14"/>
        <Style.Triggers>
            <!-- Highlight selected items -->
            <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="#0078D7"/>  <!-- Blue background -->
                <Setter Property="Foreground" Value="White"/>     <!-- White text -->
            </Trigger>
        </Style.Triggers>
    </Style>
 
    <!-- Style for ComboBox ToggleButton (hover/active states) -->
    <Style TargetType="ToggleButton" x:Key="ComboBoxToggleButtonStyle">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToggleButton">
                    <Border 
                        x:Name="Border" 
                        Background="White" 
                        BorderBrush="Gray" 
                        BorderThickness="1" 
                        CornerRadius="4">
                        <!-- ... (reuse the earlier ToggleButton template here) ... -->
                    </Border>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="Border" Property="BorderBrush" Value="#0078D7"/>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="True">
                            <Setter TargetName="Border" Property="BorderBrush" Value="#0078D7"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
  • TreeViewItem Style: Auto-expands nodes, adds padding, and highlights selected items with a blue background.
  • ToggleButton Style: Improves interactivity with hover/active states (border color changes).

Step 5: Handle Selection and Set Selected Value#

The ComboBox’s SelectedItem/SelectedValue should reflect the TreeView’s selected item. We’ll sync them using code-behind.

1. Populate Sample Data#

In MainWindow.xaml.cs, add sample hierarchical data to the Category model:

using System.Collections.ObjectModel;
using System.Windows;
 
namespace TreeViewComboBoxDemo
{
    public partial class MainWindow : Window
    {
        // Sample data
        public ObservableCollection<Category> Categories { get; set; } = new ObservableCollection<Category>();
 
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;  // Bind UI to this window's properties
 
            // Populate sample categories
            Categories.Add(new Category
            {
                Id = 1,
                Name = "Electronics",
                Children =
                {
                    new Category { Id = 101, Name = "Smartphones" },
                    new Category { Id = 102, Name = "Laptops" },
                    new Category 
                    { 
                        Id = 103, 
                        Name = "Accessories",
                        Children = 
                        {
                            new Category { Id = 1031, Name = "Chargers" },
                            new Category { Id = 1032, Name = "Cases" }
                        }
                    }
                }
            });
 
            Categories.Add(new Category
            {
                Id = 2,
                Name = "Books",
                Children =
                {
                    new Category { Id = 201, Name = "Fiction" },
                    new Category { Id = 202, Name = "Non-Fiction" }
                }
            });
 
            // Bind the TreeView to the sample data
            Loaded += (s, e) => 
            {
                if (cmbTreeView.Template.FindName("PART_TreeView", cmbTreeView) is TreeView treeView)
                {
                    treeView.ItemsSource = Categories;
                    treeView.SelectedItemChanged += TreeView_SelectedItemChanged;  // Handle selection
                }
            };
        }
 
        // Update ComboBox.SelectedItem when TreeView selection changes
        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            if (e.NewValue is Category selectedCategory)
            {
                cmbTreeView.SelectedItem = selectedCategory;  // Sync ComboBox.SelectedItem
                cmbTreeView.IsDropDownOpen = false;  // Close the dropdown after selection
            }
        }
    }
}

2. Key Points:#

  • Data Binding: The TreeView’s ItemsSource is set to Categories (sample data) when the window loads.
  • Selection Sync: The TreeView_SelectedItemChanged event updates cmbTreeView.SelectedItem with the selected Category, and closes the dropdown.
  • SelectedValue: Since we set SelectedValuePath="Id" on the ComboBox, cmbTreeView.SelectedValue will return the Id of the selected Category.

Step 6: Test the Application#

  1. Press F5 to run the app.
  2. Click the ComboBox to open the dropdown—you’ll see a hierarchical list of categories.
  3. Select an item (e.g., "Chargers" under "Accessories")—the ComboBox will display "Chargers", and cmbTreeView.SelectedValue will be 1031.

Troubleshooting Common Issues#

IssueSolution
TreeView doesn’t display dataEnsure TreeView.ItemsSource is bound to your data (check Loaded event in code-behind).
Selected item doesn’t show in ComboBoxVerify DisplayMemberPath="Name" is set on the ComboBox, and SelectionBoxItem is bound in the ContentPresenter.
Dropdown is too small/largeAdjust Popup.MaxHeight and Border.MinWidth in the ControlTemplate.
TreeView items aren’t expandingEnsure HierarchicalDataTemplate.ItemsSource is bound to Children (the child collection in your model).

Conclusion#

By overriding the ComboBox’s template and embedding a TreeView, you can display hierarchical data in a dropdown. This control is ideal for scenarios like nested category selection, file directory navigation, or multi-level menu systems. With proper styling and selection handling, it integrates seamlessly into WPF applications.

References#