WinUI3 ComboBoxを扱いたい！

:::note info
WinUI3のComboBoxについて学んだ事をアウトプットしていきたいと思います。

何か追加情報があれば都度記事を更新していこうと思います。
:::

## ComboBoxのパターン
今回は以下のパターンについて学びました。

* 静的なアイテムの定義
* 動的なアイテムの定義
* 編集可能なアイテムの定義

まずはこれらのパターンを実装したコードサンプルを載せます。
その次のセクションで個々の詳細を記載していこうと思います。

```xml:MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="ComboBoxTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ComboBoxTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="ComboBoxTest">

    <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="30">
        <TextBlock Text="{x:Bind TextViewModel.Name, Mode=OneWay}" 
                   Foreground="{x:Bind TextViewModel.Color, Mode=OneWay}"
                   FontSize="{x:Bind TextViewModel.Size, Mode=OneWay}"
                   Margin="10" />
        <ComboBox Header="色" PlaceholderText="色を選択してください" 
                  SelectionChanged="ComboBox_SelectionChanged_Color"
                  Width="200"
                  Margin="5">
            <x:String>Red</x:String>
            <x:String>Green</x:String>
            <x:String>Blue</x:String>
        </ComboBox>
        <ComboBox Header="名前" PlaceholderText="名前を選択してください" 
                  SelectionChanged="ComboBox_SelectionChanged_Name"
                  ItemsSource="{x:Bind TextViewModel.Names}"
                  SelectedItem="{x:Bind SelectedName, Mode=TwoWay}"
                  Width="200"
                  Margin="5" />
        <!-- サイズは直接入力が可能です。 -->
        <ComboBox Header="サイズ" PlaceholderText="サイズを選択してください"
                  SelectionChanged="ComboBox_SelectionChanged_Size"
                  ItemsSource="{x:Bind TextViewModel.Sizes}"
                  SelectedIndex="5"
                  IsEditable="True"
                  Width="200"
                  Margin="5" />
    </StackPanel>
</Window>
```

```cs:MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
    private TextViewModel TextViewModel { get; }
    private string SelectedName { get; set; } = string.Empty;

    public MainWindow()
    {
        this.InitializeComponent();

        // ウィンドウのサイズを変更します。
        // SizeInt32(Width, Height)
        SizeInt32 size = new(300, 500);
        AppWindow.Resize(size);

        TextViewModel = new();
    }

    // 色一覧を選択した場合の処理です。
    private void ComboBox_SelectionChanged_Color(object sender, SelectionChangedEventArgs e)
    {
        // コンボボックスから選択した項目を取得します。
        string colorName = e.AddedItems[0].ToString() ?? string.Empty;
        TextViewModel.Color = colorName;
    }

    // 名前一覧を選択した場合の処理です。
    private void ComboBox_SelectionChanged_Name(object sender, SelectionChangedEventArgs e)
    {
        // コンボボックスから選択した項目を取得します。
        TextViewModel.Name = SelectedName;
    }

    // サイズ一覧を選択した場合の処理です。
    private void ComboBox_SelectionChanged_Size(object sender, SelectionChangedEventArgs e)
    {
        // コンボボックスから選択した項目を取得します。
        string size = e.AddedItems[0].ToString() ?? string.Empty;
        TextViewModel.Size = uint.Parse(size);
    }
}
```

```cs:TextViewModel.cs
internal class TextViewModel : INotifyPropertyChanged
{
    private TextModel _model = new();

    // コンボボックスに表示する名前一覧です。
    public string[] Names
    {
        get
        {
            return _model.Names;
        }
    }

    // テキストに表示する名前です。
    public string Name
    {
        get
        {
            return $"あなたの名前は{_model.Name}です。";
        }
        set
        {
            _model.Name = value;
            OnPropertyChanged();
        }
    }

    // テキストの色です。
    public string Color
    {
        get
        {
            return _model.Color;
        }
        set
        {
            _model.Color = value;
            OnPropertyChanged();
        }
    }

    // コンボボックスに表示するサイズ一覧です。
    public uint[] Sizes
    {
        get
        {
            return _model.Sizes;
        }
    }

    // テキストのサイズです。
    public uint Size
    {
        get
        {
            return _model.Size;
        }
        set
        {
            _model.Size = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged = delegate { };
    protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
```

```cs:TextModel.cs
internal class TextModel
{
    // コンボボックスに表示する名前の一覧です。
    public string[] Names { get; } =
    {
        "紫咲シオン",
        "宝鐘マリン",
        "雪花ラミィ",
        "戌神ころね",
        "大神ミオ",
        "さくらみこ",
        "姫森ルーナ",
        "博衣こより",
        "猫又おかゆ",
        "桃鈴ねね",
        "白上フブキ",
        "常闇トワ",
        "兎田ぺこら",
        "白銀ノエル",
        "ラプラス・ダークネス"
    };

    // コンボボックスに表示するサイズの一覧です。
    public uint[] Sizes { get; } =
    {
        8, 9, 10, 11, 12, 14, 16, 18, 20, 24, 28, 36, 48, 72,
    };

    // テキストに表示する名前です。
    public string Name { get; set; } = "＊＊＊＊＊＊＊";
    // テキストの色です。
    public string Color { get; set; } = "Black";
    // テキストのサイズです。
    public uint Size = 11;
}
```

![2025.02.03-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976537/9c81260b-fc85-77f5-b651-ad5210562e70.jpeg)

## 静的なアイテムの定義パターン
`ComboBox`の子要素として`x:string`をアイテムの数だけ定義します。
今回のサンプルでは色の定義として使用しています。

```xml:MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="ComboBoxTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ComboBoxTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="ComboBoxTest">

    <!-- 省略 -->
      
        <ComboBox Header="色" PlaceholderText="色を選択してください" 
                  SelectionChanged="ComboBox_SelectionChanged_Color"
                  Width="200"
                  Margin="5">
            <x:String>Red</x:String>
            <x:String>Green</x:String>
            <x:String>Blue</x:String>
        </ComboBox>

    <!-- 省略 -->
</Window>
```

アイテムを選択すると`SelectionChanged`イベントが呼び出されます。
これの実装として、`e.AddedItems[]`の0番目の文字列表現を取得することで選択したアイテムを取得、設定します。

```cs:MainWindow.xaml.cs
public sealed partial class MainWindow : Window
{
    // 省略

    private void ComboBox_SelectionChanged_Color(object sender, SelectionChangedEventArgs e)
    {
        // コンボボックスから選択した項目を取得します。
        string colorName = e.AddedItems[0].ToString() ?? string.Empty;
        TextViewModel.Color = colorName;
    }

    // 省略
}
```

![2025.02.03-4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976537/52be0dff-ab9e-b05d-23b5-adbfd1b2f69e.jpeg)


## 動的なアイテムの定義パターン
動的なアイテムには`ItemSource`属性にコレクションを設定します。

```xml:MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="ComboBoxTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ComboBoxTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="ComboBoxTest">
      <!-- 省略 -->
      
        <ComboBox Header="名前" PlaceholderText="名前を選択してください" 
                  SelectionChanged="ComboBox_SelectionChanged_Name"
                  ItemsSource="{x:Bind TextViewModel.Names}"
                  SelectedItem="{x:Bind SelectedName, Mode=TwoWay}"
                  Width="200"
                  Margin="5" />

        <!-- 省略 -->
</Window>
```

また、ここでは選択されたアイテムの取得方法に`SelectedItem`属性を設定することで実装しています。
この属性はコードビハインド側と双方向になるため、`Mode`には`TwoWay`を指定しています。

```cs:MainWIndow.xaml.cs
public sealed partial class MainWindow : Window
{
    private TextViewModel TextViewModel { get; }
    private string SelectedName { get; set; } = string.Empty;

    public MainWindow()
    {
         // 省略
    }

      // 省略

    private void ComboBox_SelectionChanged_Name(object sender, SelectionChangedEventArgs e)
    {
        // コンボボックスから選択した項目を取得します。
        TextViewModel.Name = SelectedName;
    }

      // 省略
}
```

`ComboBox`に定義するアイテムはモデル側で定義した値を使用しています。
今回は名前一覧を作成するため、`string型の配列`を定義します
```cs:TextModel.cs
internal class TextModel
{
    // コンボボックスに表示する名前の一覧です。
    public string[] Names { get; } =
    {
        "紫咲シオン",
        "宝鐘マリン",
        "雪花ラミィ",
        "戌神ころね",
        "大神ミオ",
        "さくらみこ",
        "姫森ルーナ",
        "博衣こより",
        "猫又おかゆ",
        "桃鈴ねね",
        "白上フブキ",
        "常闇トワ",
        "兎田ぺこら",
        "白銀ノエル",
        "ラプラス・ダークネス"
    };

    // 省略
}
```

また、ここでは大量のアイテムを定義していますが、表示されるアイテムの量が多い場合、一定の数からスクロールバーが表示されるようになります。

![2025.02.03-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976537/7b7d092a-03ba-0adf-785e-db43f734678d.jpeg)


## 編集可能なアイテムの定義パターン
`IsEditable`属性を`True`にすることで`ComboBox`内の値を直接編集することが可能です。

```xml:MainWindow.xaml
<?xml version="1.0" encoding="utf-8"?>
<Window
    x:Class="ComboBoxTest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:ComboBoxTest"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    Title="ComboBoxTest">
      <!-- 省略 -->
      
       <ComboBox Header="サイズ" PlaceholderText="サイズを選択してください"
                 SelectionChanged="ComboBox_SelectionChanged_Size"
                 ItemsSource="{x:Bind TextViewModel.Sizes}"
                 SelectedIndex="5"
                 IsEditable="True"
                 Width="200"
                 Margin="5" />

        <!-- 省略 -->
</Window>
```

また`SelectedIndex`属性に値を設定することでこの位置を選択した状態にすることが可能です。

![2025.02.03-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3976537/348b896c-ee7c-4af2-2dc7-38f0d400c873.jpeg)


## おわりに
`ComboBox`にはまだ定義できる属性があります。
ただ現段階で私が使いそうだな、と思ったのが今回載せたパターンです。

今後これも使いそうとなりましたら都度更新していこうと思います。