Overview
In WPF’s ComboBox, the configuration of DisplayMemberPath, ItemTemplate, SelectedItem, SelectedValue, and SelectedValuePath varies depending on the element type of the collection bound to ItemsSource. This article organizes the representative binding patterns and explains how to select the appropriate properties for each case.
Prerequisites / Environment
- Framework / Language: .NET 8 / C# 12
- Target control: WPF ComboBox
- Architecture: MVVM (binding through DataContext)
- Prior knowledge: WPF binding basics,
INotifyPropertyChanged
Problem
When the element type of the collection bound to ItemsSource changes, the method for retrieving the selected value also changes. For example, binding a list of strings differs from binding a list of objects that each have an ID and a display name: the ViewModel property type and the required XAML properties are different in each case. Without understanding this distinction, the selected value may not be reflected, or the initial selection may fail to appear.
Cause / Background
The ComboBox exposes three selection-related properties:
| Property | Returns | Primary use |
|---|---|---|
SelectedItem |
The element itself from ItemsSource |
Pass the whole object to the ViewModel |
SelectedValue |
The value of the property named by SelectedValuePath |
Retrieve only a specific field such as an ID |
SelectedIndex |
The zero-based index of the selected row | Manage position only |
When ItemsSource holds strings, SelectedItem returns a string. When ItemsSource holds objects and SelectedValuePath is set, SelectedValue returns the value of the specified property. The appropriate binding target depends on the data structure, so the configuration must be aligned accordingly.
Solution
Before looking at implementations, choose the selection properties based on the element type of ItemsSource:
- If the element is a simple value such as a
stringor anenum, useSelectedItemto receive the element directly. - If the element is an object and the entire selected object is needed, use
SelectedItem. - If the element is an object and only a specific value such as an ID needs to be held in the ViewModel, use
SelectedValuetogether withSelectedValuePath. - To separate the display name from the value stored in the ViewModel, combine
DisplayMemberPathwithSelectedValuePath. - Reserve
SelectedIndexfor cases where the position in the list itself carries meaning; otherwise prefer working with values or objects directly.
This approach keeps the ViewModel property type aligned with the ComboBox configuration and reduces missed initial selections or update failures.
Implementation
Pattern A: String List
When ItemsSource is ObservableCollection<string>, DisplayMemberPath is unnecessary. Binding SelectedItem to a string property in the ViewModel is sufficient.
<ComboBox ItemsSource="{Binding Regions}"
SelectedItem="{Binding SelectedRegion}" />
The corresponding ViewModel implementation is as follows.
public ObservableCollection<string> Regions { get; } = new()
{
"Northeast", "Midwest", "South", "West"
};
private string? _selectedRegion;
public string? SelectedRegion
{
get => _selectedRegion;
set { _selectedRegion = value; OnPropertyChanged(); }
}
Binding SelectedItem to a type other than string (for example, int) results in a type mismatch and the binding will not reflect the selection.
Pattern B: Object List + DisplayMemberPath + SelectedItem
When ItemsSource is ObservableCollection<T> and the entire selected object is needed in the ViewModel, specify the display property with DisplayMemberPath and bind SelectedItem to a property of type T.
<ComboBox ItemsSource="{Binding Departments}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedDepartment}" />
The above XAML displays the Name property of each Department object while binding the entire selected Department to SelectedDepartment. The model and ViewModel definitions are as follows.
public class Department
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
public ObservableCollection<Department> Departments { get; } = new()
{
new Department { Id = 1, Name = "Sales" },
new Department { Id = 2, Name = "Engineering" },
new Department { Id = 3, Name = "Administration" },
};
private Department? _selectedDepartment;
public Department? SelectedDepartment
{
get => _selectedDepartment;
set { _selectedDepartment = value; OnPropertyChanged(); }
}
After selection, any field such as SelectedDepartment.Id or SelectedDepartment.Name is accessible. Holding the whole object in the ViewModel makes it straightforward to reference multiple fields later.
Pattern C: Object List + DisplayMemberPath + SelectedValuePath
This pattern applies when only a specific field — such as an ID — needs to be stored in the ViewModel. Specify the desired property name in SelectedValuePath and bind SelectedValue to a property of the matching type.
<ComboBox ItemsSource="{Binding Departments}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding SelectedDepartmentId}" />
The corresponding ViewModel property that holds the selected Id is defined as follows.
private int _selectedDepartmentId;
public int SelectedDepartmentId
{
get => _selectedDepartmentId;
set { _selectedDepartmentId = value; OnPropertyChanged(); }
}
If the type of SelectedValue does not match the type of the property named by SelectedValuePath, the selection will not be reflected. SelectedItem and SelectedValue can coexist; updating one automatically updates the other.
Pattern D: Custom Display with ItemTemplate
When multiple fields need to appear in a single row, or when an icon is included alongside text, use ItemTemplate. Both DisplayMemberPath and ItemTemplate can be set at the same time, but ItemTemplate takes precedence and DisplayMemberPath is ignored. For custom display, use ItemTemplate only and avoid combining it with DisplayMemberPath.
<ComboBox ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Id}" Width="40" Foreground="Gray"/>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The Employee model referenced by this XAML is defined as follows.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
When different layouts are needed for the collapsed selection display versus the expanded dropdown list, use ContentTemplate alongside ItemContainerStyle instead of relying on ItemTemplate alone.
Pattern E: Enum List
For a fixed set of choices defined by an enumeration, generating the collection in the ViewModel with Enum.GetValues is more maintainable than using ObjectDataProvider in XAML.
The ViewModel generates the enum values as follows.
public enum Priority { Low, Medium, High }
public IEnumerable<Priority> Priorities { get; }
= (Priority[])Enum.GetValues(typeof(Priority));
private Priority _selectedPriority = Priority.Medium;
public Priority SelectedPriority
{
get => _selectedPriority;
set { _selectedPriority = value; OnPropertyChanged(); }
}
The XAML binds ItemsSource and SelectedItem to these ViewModel properties as follows.
<ComboBox ItemsSource="{Binding Priorities}"
SelectedItem="{Binding SelectedPriority}" />
SelectedItem is of type Priority. Retrieving the underlying integer value via SelectedValue and SelectedValuePath is possible, but an explicit cast such as (int)SelectedPriority expresses the intent more clearly.
Notes
-
Behavior when both
DisplayMemberPathandItemTemplateare set
When both are set,ItemTemplatetakes precedence andDisplayMemberPathis ignored. To prevent unintended behavior, useItemTemplatewhen custom display is needed and do not combine it withDisplayMemberPath. -
Setting the initial value for
SelectedValuecorrectly
When usingSelectedValuePath, if the ViewModel’s initial value does not exist inItemsSource, the selection state will be empty. SettingSelectedValuebeforeItemsSourceis assigned can also cause the binding to have no effect. Always assignItemsSourcebefore setting the selected value. -
SelectedItemmatching considersEquals
SelectedItemdoes not strictly perform reference comparison; it involvesEquals-based matching. With the default implementation, two separate instances with identical contents will not be considered equal, so setting a different instance as an initial value will not result in a visible selection. To achieve value-based equality, overrideEqualsandGetHashCodeon the target type and implementIEquatable<T>if necessary. When selection should be managed by an identifier or code value, useSelectedValuePathwithSelectedValue. -
Handling
null
To include a “no selection” option in the list, bind anullentry inItemsSource; theComboBoxdisplays it as a blank entry. Use a nullable type (for example,string?orint?) for the ViewModel property.
Alternatives / Comparison
| Pattern | ItemsSource type | Selected value retrieval | Best suited for |
|---|---|---|---|
| A: String list | ObservableCollection<string> |
SelectedItem (string) |
Choices are plain labels only |
| B: Object + SelectedItem | ObservableCollection<T> |
SelectedItem (T) |
Multiple fields needed after selection |
| C: Object + SelectedValuePath | ObservableCollection<T> |
SelectedValue (specified property type) |
Only a specific field such as an ID is needed |
| D: ItemTemplate | ObservableCollection<T> |
SelectedItem (T) |
Multiple fields displayed in a single row |
| E: Enum | IEnumerable<TEnum> |
SelectedItem (TEnum) |
Selecting from a fixed enumeration |
Summary
The appropriate ComboBox implementation pattern is determined by the type bound to ItemsSource.
- A string-only list requires nothing more than binding
SelectedItemto astringproperty. - An object list where the whole object is needed in the ViewModel uses
DisplayMemberPathtogether withSelectedItem. - An object list where only a specific field such as an ID is needed uses
SelectedValuePathwithSelectedValue. - Custom row layouts require
ItemTemplate; combining it withDisplayMemberPathshould be avoided. - Enum values are exposed as a collection in the ViewModel and bound via
SelectedItemtyped to the enum.
Most initial-value failures trace back to either Equals-based mismatch or incorrect assignment order relative to ItemsSource. Using SelectedValuePath, or ensuring the exact same instance is referenced, resolves the majority of these cases.