【WPF】MVVMでコンボボックスの使用方法

【WPF】MVVMでコンボボックスの使用方法

はじめに

今回の記事では、MVVM でコンボボックスの使用方法についてまとめていきます。

最終的な成果物のイメージです。

combobox-03.gif

MVVM に関しては下記の記事で紹介しています。

開発環境

  • .NET6
  • Visual Studio 2022(v17.5.4)

画面の実装

MainWindow.xamlを以下のように記述します。

"MainWindow.xaml"
<Window
    x:Class="ComboBox_Sample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ComboBox_Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="ComboBox-Sample"
    Width="450"
    Height="450"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <!-- MainWindow.xaml.csで指定する事もできる -->
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="225" />
            <RowDefinition Height="225" />
        </Grid.RowDefinitions>
        <StackPanel
            Grid.Row="0"
            Height="60"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Orientation="Horizontal">
            <ComboBox
                Width="200"
                Cursor="Hand"
                FontSize="24"
                ItemsSource="{Binding ComboBoxSource}"
                SelectedItem="{Binding SelectedComboBoxItem}"
                />
        </StackPanel>
        <StackPanel Grid.Row="1">
            <StackPanel
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Orientation="Horizontal">
                <Label Content="選択された値↓" FontSize="24" />
            </StackPanel>
            <StackPanel>
                <TextBox
                    Width="150"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    FontSize="24"
                    Text="{Binding SelectedComboBoxItemText}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

Xaml 側でWindow.DataContextを指定している箇所がありますが、MainWindow.xaml.cs(コードビハインド)で指定するのと同じことをしています。

"MainWindow.xaml.cs"
using System.Windows;

namespace ComboBox_Sample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            // Xaml側で<Window.DataContext>を指定しているのと同じ
            DataContext = new MainWindowViewModel();
        }
    }
}

次に ViewModel 側の実装をしていきます。手順としては、

  1. BindableBase.cs を作成する。
  2. MainWindowViewModel.cs を作成する
  3. DelegateCommand.cs を作成して Command を実行出来るようにする。
  4. データ用に ConboBoxEntity.cs を作成する

大体いつものパターンです。MVVM フレームワークであるPrismを使用すれば上記のファイルは最初から用意されていますが、今回は使用せずに進めていきます。

DelegateCommand.csBindableBase.csの内容は割愛します。詳細はリポジトリを参照してください。

コンボボックスに表示させるためのConboBoxEntityクラスを作成します。

"ConboBoxEntity.cs"
namespace ComboBox_Sample
{
    public sealed class ConboBoxEntity
    {
        /// <summary>
        /// ID
        /// </summary>
        public int Id { get; }

        /// <summary>
        /// 名前
        /// </summary>
        public string Name { get; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="id">ID</param>
        /// <param name="name">名前</param>
        public ConboBoxEntity(int id, string name)
        {
            Id = id;
            Name = name;
        }
    }
}

プロパティは読み取り専用でインスタンス生成後は変更できません。

コンストラクタで初期化するときのみ値を格納できるようにしています。

次にMainWindowViewModel.csを作成します。

"MainWindowViewModel.cs"
using System.Collections.Generic;

namespace ComboBox_Sample
{
    /// <summary>
    /// MainWindowのViewModel。ウィンドウのコンポーネントのコマンドとインタラクションを処理します。
    /// </summary>
    public sealed class MainWindowViewModel : BindableBase
    {
        /// <summary>
        /// ComboBoxに表示させるリスト
        /// </summary>
        public List<ConboBoxEntity> ComboBoxSource
        {
            get => _comboBoxSource;
            set => SetProperty(ref _comboBoxSource, value);
        }
        private List<ConboBoxEntity> _comboBoxSource = new List<ConboBoxEntity>();

        /// <summary>
        /// コンボボックスに選択されたデータを表示する
        /// </summary>
        public string SelectedComboBoxItemText
        {
            get => _selectedComboBoxItemText;
            set => SetProperty(ref _selectedComboBoxItemText, value);
        }
        private string _selectedComboBoxItemText = "テスト";

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel()
        {

            ComboBoxSource = new List<ConboBoxEntity>
                                {
                                    new ConboBoxEntity(1, "山根 京子"),
                                    new ConboBoxEntity(2, "鵜飼 亮介"),
                                    new ConboBoxEntity(3, "住吉 知香"),
                                    new ConboBoxEntity(4, "岡本 徹"),
                                    new ConboBoxEntity(5, "岩澤 博志"),
                                    new ConboBoxEntity(6, "長谷部 美里"),
                                };

        }

    }

}

人物名などは個人情報テストデータジェネレーターというサービスを利用して作成しました。

一旦作成したファイルを見てみます。

current-files

一度ビルドして動作確認します。

combobox-01

コンボボックスに表示されている文字列がおかしいですね。これは ConboBoxEntity オブジェクトそのものが表示されてしまっています。

ConboBoxEntity オブジェクト内のプロパティを表示させるように修正します。

DisplayMemberPath

DisplayMemberPathではコンボボックスで表示する各項目のテキストを決定するプロパティのパスを指定します。

ConboBoxEntity オブジェクトでは Name プロパティを持っているので、DisplayMemberPath="Name"と指定するとコンボボックスには各項目の Name プロパティの値が表示されます。

"MainWindow.xaml"
<Window
    x:Class="ComboBox_Sample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ComboBox_Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="ComboBox-Sample"
    Width="450"
    Height="450"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="225" />
            <RowDefinition Height="225" />
        </Grid.RowDefinitions>
        <StackPanel
-           Height="60"
            Grid.Row="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Orientation="Horizontal">
            <ComboBox
                Width="200"
+               VerticalAlignment="Center"
                Cursor="Hand"
+               DisplayMemberPath="Name"
                FontSize="24"
                ItemsSource="{Binding ComboBoxSource}"
                SelectedItem="{Binding SelectedComboBoxItem}"
                />
        </StackPanel>
        <StackPanel Grid.Row="1">
            <StackPanel
                HorizontalAlignment="Center"
                VerticalAlignment="Top"
                Orientation="Horizontal">
                <Label Content="選択された値↓" FontSize="24" />
            </StackPanel>
            <StackPanel>
                <TextBox
                    Width="150"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Top"
                    FontSize="24"
                    Text="{Binding SelectedComboBoxItemText}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Window>

一旦ビルドして表示を確認します。レイアウトも少し修正しました。

combobox-02.gif

適切に表示されていることが分かります。

コンボボックス関連で使用する主要なプロパティを表にまとめました。

プロパティ名内容
SelectedItem現在選択されているオブジェクトを取得または設定します。これは一般的にバインディングを通じて ViewModel と同期されます。選択が変わると、このプロパティは新しく選択された項目を参照します。
SelectedValueSelectedValuePathプロパティに指定したパスで、現在選択されているオブジェクトのプロパティの値を取得または設定します。
SelectedValuePathSelectedValueプロパティが取得する値を決定するプロパティのパスを指定します。つまり、SelectedValueは選択されたアイテムオブジェクトのパスにより参照されるプロパティの値を返します。
DisplayMemberPathコンボボックスで表示する各項目のテキストを決定するプロパティのパスを指定します。

初期値に選択されている要素を指定する

現在のコンボボックスは起動時は何も要素が選択されていないので空白ですが、ここに初期値を設定します。

 public sealed class MainWindowViewModel : BindableBase
    {
        /// <summary>
        /// ComboBoxに表示させるリスト
        /// </summary>
        public List<ConboBoxEntity> ComboBoxSource
        {
            get => _comboBoxSource;
            set => SetProperty(ref _comboBoxSource, value);
        }
        private List<ConboBoxEntity> _comboBoxSource = new List<ConboBoxEntity>();

        /// <summary>
        /// コンボボックスで選択された要素
        /// </summary>
+       public ConboBoxEntity SelectedComboBoxItem
+       {
+           get => _selectedComboBoxItem;
+           set => SetProperty(ref _selectedComboBoxItem, value);
+       }
+       private ConboBoxEntity _selectedComboBoxItem;

// ~ 省略 ~

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel()
        {

            ComboBoxSource = new List<ConboBoxEntity>
                                {
                                    new ConboBoxEntity(1, "山根 京子"),
                                    new ConboBoxEntity(2, "鵜飼 亮介"),
                                    new ConboBoxEntity(3, "住吉 知香"),
                                    new ConboBoxEntity(4, "岡本 徹"),
                                    new ConboBoxEntity(5, "岩澤 博志"),
                                    new ConboBoxEntity(6, "長谷部 美里"),
                                };

            // 最初に選択されている要素を指定する
+           SelectedComboBoxItem = ComboBoxSource[0];

        }

// ~ 省略 ~

起動時に最初の要素の名前が選択されていることが分かります。

current-item

選択された要素の値を取得する

最後に、コンボボックスを選択するたびに要素の値を取得する方法を解説します。

MVVM で XAML のイベントに Command を指定するにはMicrosoft.Xaml.Behaviors.Wpfというパッケージが必要になります。このパッケージは、WPF アプリケーションで使用できるビヘイビア、トリガー、アクションなどのクラスを提供する NuGet パッケージです。MVVM パターンを適用する際に非常に役立ちます。

また、Microsoft.Xaml.Behaviors.Wpf パッケージの最も重要な機能の一つは、イベントをコマンドにバインドする機能です。これにより、MVVM パターンを適用する際に、ViewModel から View のイベントを処理することができます。

Microsoft.Xaml.Behaviors.Wpf は Nuget からインストールできます。

xaml-behaviors

XAML 側の処理

MainWindow.xamlを修正します。ComboBoxは単一のタグで使用していましたが、bhのタグを囲むように修正します。

"MainWindow.xaml"
<Window
    x:Class="ComboBox_Sample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+   xmlns:bh="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:ComboBox_Sample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="ComboBox-Sample"
    Width="450"
    Height="450"
    WindowStartupLocation="CenterScreen"
    mc:Ignorable="d">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="225" />
            <RowDefinition Height="225" />
        </Grid.RowDefinitions>
        <StackPanel
            Grid.Row="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Orientation="Horizontal">
            <ComboBox
                Width="200"
                VerticalAlignment="Center"
                Cursor="Hand"
                DisplayMemberPath="Name"
                FontSize="24"
                ItemsSource="{Binding ComboBSelectedComboBoxItemoxSource}"
                SelectedItem="{Binding SelectedComboBoxItem}">
+               <bh:Interaction.Triggers>
+                   <bh:EventTrigger EventName="SelectionChanged">
+                       <bh:InvokeCommandAction Command="{Binding ExecuteSelectedItem}" />
+                   </bh:EventTrigger>
+               </bh:Interaction.Triggers>
+           </ComboBox>
        </StackPanel>

<!-- 省略 -->

次にMainWindowViewModel.csを修正します。

"MainWindowViewModel.cs"
using System.Collections.Generic;

namespace ComboBox_Sample
{
    /// <summary>
    /// MainWindowのViewModel。ウィンドウのコンポーネントのコマンドとインタラクションを処理します。
    /// </summary>
    public sealed class MainWindowViewModel : BindableBase
    {
        /// <summary>
        /// ComboBoxに表示させるリスト
        /// </summary>
        public List<ConboBoxEntity> ComboBoxSource
        {
            get => _comboBoxSource;
            set => SetProperty(ref _comboBoxSource, value);
        }
        private List<ConboBoxEntity> _comboBoxSource = new List<ConboBoxEntity>();

        /// <summary>
        /// コンボボックスで選択された要素
        /// </summary>
        public ConboBoxEntity SelectedComboBoxItem
        {
            get => _selectedComboBoxItem;
            set => SetProperty(ref _selectedComboBoxItem, value);
        }
        private ConboBoxEntity _selectedComboBoxItem;

        /// <summary>
        /// コンボボックスに選択されたデータを表示する
        /// </summary>
        public string SelectedComboBoxItemText
        {
            get => _selectedComboBoxItemText;
            set => SetProperty(ref _selectedComboBoxItemText, value);
        }
        private string _selectedComboBoxItemText = "テスト";

        /// <summary>
        /// SelectedChangedイベントが実行されたときのコマンド
        /// </summary>
+       public DelegateCommand ExecuteSelectedItem { get; }

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindowViewModel()
        {

            ComboBoxSource = new List<ConboBoxEntity>
                                {
                                    new ConboBoxEntity(1, "山根 京子"),
                                    new ConboBoxEntity(2, "鵜飼 亮介"),
                                    new ConboBoxEntity(3, "住吉 知香"),
                                    new ConboBoxEntity(4, "岡本 徹"),
                                    new ConboBoxEntity(5, "岩澤 博志"),
                                    new ConboBoxEntity(6, "長谷部 美里"),
                                };

            SelectedComboBoxItem = ComboBoxSource[0];

+           ExecuteSelectedItem = new DelegateCommand(SelectedItem);

        }

        /// <summary>
        /// 選択された要素をテキストボックスに格納する
        /// </summary>
+       private void SelectedItem()
+       {
+           SelectedComboBoxItemText = SelectedComboBoxItem.Name;
+       }

    }

}

ここで一旦ビルドして動作確認します。

combobox-03.gif

コンボボックスを選択するたびに文字列が変化していることが分かります。

最後に

今回は MVVM でコンボボックスを使用する方法についてまとめました。

今回作成したアプリケーションのリポジトリはこちらになります。