初めての WinUI 3 (3) カスタムコントロール

# 前回
基礎練として、簡単なViewとViewModelの実装をしました。
最初に作った物をコピペしたので、XAMLが同じような内容(Grid周り)です。
記事を見直して、自分のことながら残念な気持ちに。

https://qiita.com/kashin777/items/4ac62cefaebfba175fcf

# 今回
そうならないために、カスタムコントロール／ユーザーコントロールを作って共通化するのが定石です。

## カスタムコントロール／ユーザーコントロールの違い
私「違いは何ですか？要約して教えてください。」

AI「カスタムコントロール は 汎用性が高く、再利用しやすい 独立した UI 部品。テンプレートを自由にカスタマイズでき、複数のプロジェクトで利用可能。
ユーザーコントロール は 特定用途向け に既存のコントロールを組み合わせて作る UI 部品。再利用性は限定的で、そのプロジェクト内で使うことが多い。
違いは「汎用性」と「設計の自由度」！ どちらを使うかは開発目的次第です。😃」

私「今回は、カスタムコントロールをやってみよう。」

## 先人の知恵に学ぶ
なんなら作りたくないし、あるもの利用するだけにしたいです。そんなもんです。
というわけで、こちら。

### Windows Community Toolkit の SettingsCard

https://learn.microsoft.com/ja-jp/dotnet/communitytoolkit/windows/settingscontrols/settingscard

> SettingsCard は、エクスペリエンスで設定を表示するために使用できるコントロールです。 これは、Windows 11 の既定のスタイルを使用し、使いやすく、すべてのアクセシビリティ標準を満たしており、設定ページの見栄えを良くします。 

利用します。

#### Nugetします

https://www.nuget.org/packages/CommunityToolkit.WinUI.Controls.SettingsControls/

#### XAMLだけ書き換えます

なんということでしょう！
あれだけ冗長だったXAMLがすっきり！！
Viva!!!

```xaml MainWindow.xaml
<Window
    ～略～
  
    xmlns:ui="using:CommunityToolkit.WinUI"
    xmlns:controls="using:CommunityToolkit.WinUI.Controls">

    ～略～
  
        <controls:SettingsCard 
            HeaderIcon="{ui:FontIcon Glyph='&#xF19E;'}"
            Header="ToggleSwitch &amp; Change Theme"
            Description="トグルスイッチでアプリケーションのテーマを編集します。">

            <!-- 本体 -->
            <ToggleSwitch x:Name="toggleSwitch" IsOn="{x:Bind ViewModel.ToggleSwitchValue, Mode=TwoWay}"  Toggled="toggleSwitch_Toggled" OffContent="ライトモード" OnContent="ダークモード" />

        </controls:SettingsCard>
```

#### 実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515556/3902109a-7bb2-48dd-bf93-4130d3adafc7.png)

まさにWin11の設定画面のそれですね。
ちなみに、クリックしたら下部領域が開く奴は SettingExpander です。

https://learn.microsoft.com/ja-jp/dotnet/communitytoolkit/windows/settingscontrols/settingsexpander

#### SettingsCard のソースを調査
SettingsCardは、どうやって実現されているのか？ソースコードはこちら

https://github.com/CommunityToolkit/Windows/tree/main/components/SettingsControls/src/SettingsCard

よし、見た。

## 簡易版を実装してみる

SettingsCard は、機能が充実していて、ソースコードももりもりです。
ここは、シンプルにすることで理解が進むはず。
やりたいことは、
・ヘッダーアイコンを指定できる
・ヘッダーを指定できる
・説明文を指定できる
・コンテンツは、自由にカスタマイズ可能とする
です。

### 実装 (C#)
・アイコン・ヘッダー、説明文、コンテンツのDependencyPropertyを宣言する。
・それぞれのget;set;を実装する。
・カスタムコントロールの子要素として指定するのは"Content"であることを宣言する。(ContentProperty)
```Csharp SettingsCard.cs
    [ContentProperty(Name = "Content")]
    public partial class SettingsCard : Control
    {
        public static DependencyProperty BuildDp<T, O>(string nameOf, T defaultValue, PropertyChangedCallback callback)
        {
            return DependencyProperty.Register(nameOf, typeof(T), typeof(O), new PropertyMetadata(defaultValue, callback));
        }

        public static DependencyProperty FontIconProperty =
            BuildDp<object, SettingsCard>(nameof(HeaderIcon), new FontIcon(), static (d, e) => { });

        public static DependencyProperty HeaderProperty =
            BuildDp<string, SettingsCard>(nameof(Header), string.Empty, static (d, e) => { });

        public static DependencyProperty DescriptionProperty =
            BuildDp<string, SettingsCard>(nameof(Description), string.Empty, static (d, e) => { });

        public static readonly DependencyProperty ChildrenProperty =
            BuildDp<object, SettingsCard>(nameof(Content), new(), static (d, e) => { });


        public SettingsCard() : base()
        {
            this.DefaultStyleKey = typeof(SettingsCard);
        }

        public object HeaderIcon
        {
            get => GetValue(FontIconProperty);
            set => SetValue(FontIconProperty, value);
        }

        public string Header
        {
            get => (string)GetValue(HeaderProperty);
            set => SetValue(HeaderProperty, value);
        }

        public string Description
        {
            get => (string)GetValue(DescriptionProperty);
            set => SetValue(DescriptionProperty, value);
        }

        public object Content
        {
            get => GetValue(ChildrenProperty);
            set => SetValue(ChildrenProperty, value);
        }
    }
```

### テンプレート(ResourceDictionary)

```xaml SettingsCard.xaml 
  <Style TargetType="local:SettingsCard">
    <Setter  Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:SettingsCard">
          <!-- テンプレート全体 -->
          <Grid x:Name="PART_Panel" 
                Padding="16" CornerRadius="4"
                Background="{ThemeResource ControlFillColorDefaultBrush}"
                HorizontalAlignment="Stretch">

            <Grid.ColumnDefinitions>
              <ColumnDefinition Width="Auto" />
              <ColumnDefinition Width="1*" />
              <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <!-- ヘッダーアイコン -->
            <ContentPresenter 
              Grid.Column="0" 
              x:Name="PART_HeaderIcon" Content="{TemplateBinding HeaderIcon}" 
              Margin="6 6 12 6" />

            <!-- ヘッダー・説明文 -->
            <StackPanel 
              Grid.Column="1" 
              x:Name="PART_Header" 
              Orientation="Vertical" Spacing="4" 
              VerticalAlignment="Center" HorizontalAlignment="Stretch">
              <TextBlock Text="{TemplateBinding Header}" 
                FontSize="14" TextWrapping="WrapWholeWords" />
              <TextBlock Text="{TemplateBinding Description}" 
                FontSize="12" TextWrapping="WrapWholeWords" />
            </StackPanel>

            <!-- コンテンツ -->
            <ContentPresenter Grid.Column="2" 
                      x:Name="PART_Content" Content="{TemplateBinding Content}" 
                      VerticalContentAlignment="Center" 
                      HorizontalContentAlignment="Right" />

          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
```

### 利用する(XAML)

```xaml MainWindow.xaml
        <local:SettingsCard 
            HeaderIcon="{ui:FontIcon Glyph='&#xE8B0;'}"
            Header="local:SettingsCard" 
            Description="SettingsCard風カスタムコントロール">
            <!-- コンテンツ -->
            <Grid>
                <Button Content="Button" />
            </Grid>
        </local:SettingsCard>
```

### 実行結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2515556/f7675f70-b660-4160-adf7-ecf5db29ce09.png)

簡易版のSettingsCardが完成して、基本は理解できたと思います。
本来は、状態によって部品の表示／非表示、デザイン変更とかが必要なはず。
DependencyProperty定義するの楽にならないかな。

また、次回があればどうぞ。