WPF ComboBox の ItemsSource バインドパターンと選択値の取得方法

ItemsSource に渡すデータ構造によって、DisplayMemberPath・SelectedItem・SelectedValue の設定方法が変わります。文字列リスト、オブジェクトリスト、Enum の各パターンを整理します。

概要

WPF の ComboBoxItemsSource に渡すデータ構造に応じて、表示内容の制御方法と選択値の取得方法が変わる。具体的には DisplayMemberPathItemTemplateSelectedItemSelectedValueSelectedValuePath の組み合わせが、バインドするコレクションの型によって異なる。本記事では代表的な実装パターンを整理し、それぞれの選択に関するプロパティの使い分けを示す。


前提・対象環境


問題

ComboBoxItemsSource に渡すコレクションの要素型が変わると、選択値の取得方法も変わる。たとえば文字列リストを渡す場合と、ID と名称を持つオブジェクトのリストを渡す場合では、ViewModel にバインドするプロパティの型と設定が異なる。この違いを把握していないと、選択しても値が反映されない、または初期値が表示されないといった問題が発生する。


原因・背景

ComboBox の選択関連プロパティは以下の 3 種類がある。

プロパティ 返す値 主な用途
SelectedItem ItemsSource の要素そのもの オブジェクト全体を ViewModel に渡す
SelectedValue SelectedValuePath で指定したプロパティ値 ID など特定フィールドだけを取得する
SelectedIndex 選択行のインデックス(0 始まり) 位置だけを管理する場合

文字列リストの場合、要素そのものが文字列であるため SelectedItemstring を返す。オブジェクトのリストを使い SelectedValuePath を指定すると、SelectedValue には指定プロパティの値が返る。どのプロパティをバインドするかはデータ構造に依存するため、構造ごとに設定を合わせる必要がある。


解決方法

実装に入る前に、ItemsSource の要素型に応じて選択関連プロパティを使い分ける。

この方針で選べば、ViewModel 側のプロパティ型と ComboBox の設定を対応させやすくなり、初期選択や選択変更の反映漏れを防ぎやすい。


実装例

パターン A:文字列リスト

ItemsSource が ObservableCollection<string> のとき、DisplayMemberPath は不要であり、SelectedItemstring 型のプロパティをバインドするだけで機能する。

<ComboBox ItemsSource="{Binding Regions}"
          SelectedItem="{Binding SelectedRegion}" />

対応する ViewModel 側の実装は次のとおりである。

public ObservableCollection<string> Regions { get; } = new()
{
    "東北", "関東", "中部", "近畿", "九州"
};

private string? _selectedRegion;
public string? SelectedRegion
{
    get => _selectedRegion;
    set { _selectedRegion = value; OnPropertyChanged(); }
}

SelectedItemstring 以外の型(例: int)をバインドすると型の不一致でバインドエラーとなる。


パターン B:オブジェクトリスト + DisplayMemberPath + SelectedItem

ItemsSource が ObservableCollection<T> で、表示する文字列と選択して取得するオブジェクトが同じ型の場合、DisplayMemberPath で表示プロパティを指定し、SelectedItem にオブジェクト全体をバインドする。

<ComboBox ItemsSource="{Binding Departments}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding SelectedDepartment}" />

上記の XAML では Department オブジェクトの Name を表示しつつ、選択結果として Department オブジェクト全体を SelectedDepartment にバインドしている。対応するモデルと ViewModel の定義例は次のとおり。

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 = "営業部" },
    new Department { Id = 2, Name = "開発部" },
    new Department { Id = 3, Name = "総務部" },
};

private Department? _selectedDepartment;
public Department? SelectedDepartment
{
    get => _selectedDepartment;
    set { _selectedDepartment = value; OnPropertyChanged(); }
}

選択後は SelectedDepartment.IdSelectedDepartment.Name で任意のフィールドにアクセスできる。ViewModel にオブジェクト全体を保持するため、後から複数フィールドを参照しやすい。


パターン C:オブジェクトリスト + DisplayMemberPath + SelectedValuePath

表示は Name で行い、選択値として Id(数値や文字列のキー)だけを取得したい場合に使う。SelectedValuePath に取得したいプロパティ名を指定し、SelectedValue にそのプロパティの型でバインドする。

<ComboBox ItemsSource="{Binding Departments}"
          DisplayMemberPath="Name"
          SelectedValuePath="Id"
          SelectedValue="{Binding SelectedDepartmentId}" />

対応する ViewModel 側では、選択された Id を保持するプロパティを次のように定義する。

private int _selectedDepartmentId;
public int SelectedDepartmentId
{
    get => _selectedDepartmentId;
    set { _selectedDepartmentId = value; OnPropertyChanged(); }
}

SelectedValue の型と SelectedValuePath で指定するプロパティの型が一致していないと、選択値が反映されない。また SelectedItemSelectedValue は同時に利用できるが、一方を変更するともう一方も自動で更新される。


パターン D:ItemTemplate を使ったカスタム表示

1 行の表示に複数フィールドを含めたい場合や、アイコン付きの選択肢を実装したい場合は ItemTemplate を使う。DisplayMemberPathItemTemplate は両方を設定できるが、表示には ItemTemplate が優先されて DisplayMemberPath は無視される。そのため、カスタム表示が必要なときは ItemTemplate を使用し、通常は 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>

この XAML で参照する Employee モデルの定義例は次のとおりである。

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

ItemTemplate を使う場合、ドロップダウンを閉じた状態(選択済み表示)と展開した一覧で異なるレイアウトを出したいときは ItemTemplate の代わりに ItemContainerStyleContentTemplate を組み合わせる。


パターン E:Enum リスト

選択肢が固定の列挙体の場合、ObjectDataProvider を使って XAML から Enum 値を列挙する方法と、ViewModel でコレクションを用意する方法がある。保守性を考えると ViewModel でコレクションを生成する方法が安全である。

ViewModel 側で Enum.GetValues を使って選択肢を生成する。

public enum Priority { , ,  }

public IEnumerable<Priority> Priorities { get; }
    = (Priority[])Enum.GetValues(typeof(Priority));

private Priority _selectedPriority = Priority.;
public Priority SelectedPriority
{
    get => _selectedPriority;
    set { _selectedPriority = value; OnPropertyChanged(); }
}

この ViewModel に対して、XAML 側では ItemsSourceSelectedItem を次のようにバインドする。

<ComboBox ItemsSource="{Binding Priorities}"
          SelectedItem="{Binding SelectedPriority}" />

SelectedItem の型は Priority(Enum 型)になる。SelectedValueSelectedValuePath を使って Enum の数値(基底値)だけを取得することも可能だが、明示的に (int)SelectedPriority でキャストした方が意図が明確である。


注意点


代替案・比較

パターン ItemsSource の型 選択値の取得 適するケース
A: 文字列リスト ObservableCollection<string> SelectedItemstring 選択肢が単純なラベルのみ
B: オブジェクト + SelectedItem ObservableCollection<T> SelectedItemT 選択後に複数フィールドを参照する
C: オブジェクト + SelectedValuePath ObservableCollection<T> SelectedValue(指定プロパティ型) ID など特定フィールドだけ必要
D: ItemTemplate ObservableCollection<T> SelectedItemT 複数フィールドを 1 行に表示する
E: Enum IEnumerable<TEnum> SelectedItemTEnum 固定の列挙値から選択する

まとめ

ComboBox の実装パターンは ItemsSource に渡す型によって決まる。

初期値が反映されない問題の多くは、参照比較の不一致か ItemsSource のセット順序に起因する。SelectedValuePath を使うか、同一インスタンスを参照するよう設計することで回避できる。