WinUI3でテーマカラー(ダーク/ライト/ハイコントラスト)対応する

WInUI3関連記事

https://tera1707.com/entry/2022/02/06/144447#WinUI3

やりたいこと

WinUI3で作成するアプリを、Windowsの設定で選べるテーマカラー(ダーク/ライト/ハイコントラスト)ごとに、 ダークは暗めの色、ライトは明るめの色、ハイコントラストは色が見えにくい方にコントロールが判別しやすい色、になるように作りたい。

Windowsのテーマカラーの設定はここ。

※今回は、アクセントカラーの設定に合わせて画面の色も変わるようにはせず、テーマごとに固定の色で表示するようにする。

前提

  • VisualStudio2022 Community 17.1.0
  • WinUI3
  • Windows App SDK 1.0

※色々調べて試した結果、こうやったらできたというやり方のメモなので、こうすべき、とかがあれば指摘頂けると大変ありがたいです。

実験の目標

下記のような画面を作ることを目標に実験する。

実験で調べることは下記の感じ。

  1. 今回は、ボタン(Button)で実験する。
  2. ボタンが配置された画面で、ダーク/ライト/ハイコントラスト時に違和感のないUIにするため、各テーマでボタンの色を指定する方法を調べる。
  3. ボタンの見た目がアプリ全体で統一されてればいいが、そうでないとき(特定のボタンだけ違う色にしたい、とか)にも対応できるように、
  4. アプリ全体のボタンの色を指定する方法を調べる
  5. 特定のボタンの色だけを指定する方法も調べる

ということを調べる。

スタート地点

まずは、このウインドウをスタート地点にする。

<Window
    x:Class="CheckboxThemeColorJikken.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CheckboxThemeColorJikken"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="2*"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal" Background="#55FF0000">
            <TextBlock Text="独自の色をセット" VerticalAlignment="Center"/>
            <Button Content="独自の色をセット" VerticalAlignment="Center"/>
        </StackPanel>

        <StackPanel Grid.Row="1" Orientation="Horizontal" Background="#5500FF00">
            <TextBlock Text="標準の色を使用" VerticalAlignment="Center"/>
            <Button Content="標準の色を使用" VerticalAlignment="Center"/>
        </StackPanel>
    </Grid>
</Window>

見た目

標準の色を使用、のボタンは、アプリ全体のボタン統一の色を使うイメージ。 独自の色をセット、のボタンは、この画面のこのボタンだけの色を指定して使うイメージ。

テーマごとのリソースディクショナリをつくる

ライト/ダーク/ハイコントラスト時の色をどうするか、を書くためのリソースディクショナリ(.xaml)を作る。

プロジェクトを右クリック > 追加 > 新しい項目 から、下記を選択する。

追加したResourceDictionaryの.xamlの内容を、下記のようにし、ライト/ダーク/ハイコントラスト時のリソースを書けるようにする。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary x:Key="Dark">
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <!--ダークテーマ用のリソースを定義してやる-->
                    <Color x:Key="RegionColor">#FF000000</Color>
                    <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
        <ResourceDictionary x:Key="Light">
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <!--ライトテーマ用のリソースを定義してやる-->
                    <Color x:Key="RegionColor">#FFFFFFFF</Color>
                    <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
        <ResourceDictionary x:Key="HighContrast">
            <!--ハイコントラスト時のリソースを定義してやる-->
            <StaticResource x:Key="RegionColor" ResourceKey="SystemColorWindowColor" />
            <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
        </ResourceDictionary>
    </ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

上記は、Fluent Xaml Theme Editorが吐いたxamlをもとに、要らない部分を削除したもの。
(こちら参照)
ただし、テーマごとの背景色として使用するRegionBrushは、残している。
(背景色のブラシだけは、自動でセットされないため。詳しくはこちらのreadme参照。)

作ったリソースディクショナリをアプリに適用する

作成したリソースディクショナリを、App.xamlxamlの中でアプリに適用する。 (UWPなどでは、App.xaml以外の場所でMergeDictionaryできたが、WinUI3ではApp.xamlでやらないと適用されなかった)

<Application
    x:Class="CheckboxThemeColorJikken.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CheckboxThemeColorJikken">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
                <!-- Other merged dictionaries here -->
                <!-- ★ココ★ -->
                <ResourceDictionary Source="ResourceDictionary1.xaml"/>
            </ResourceDictionary.MergedDictionaries>
            <!-- Other app resources here -->
        </ResourceDictionary>
    </Application.Resources>
</Application>

背景色をセットする

上で書いた通り、RegionBrushは自分で一番後ろになる部分のBackgroundにセットしてあげないといけないので、まずセットする。 今回は、MainWindowの一番外側にあるGridのBackgroundにセットした。

<Window
    x:Class="CheckboxThemeColorJikken.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:CheckboxThemeColorJikken"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource RegionBrush}"> <!-- ★ココ★ -->
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="2*"/>
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0" Orientation="Horizontal">
    ・
    ・

{ThemeResource ・・・}として設定すると、テーマ変更時に色を再度読み込みなおしてくれる。
 {StaticResource ・・・}だと、起動時のテーマの色にはなるが、テーマ変更時には追従してくれない。

ここまでやってから、リソースディクショナリに書いている3か所のRegionBrushの色を適当に変えてみて、テーマをダーク、ライト、ハイコントラストに変えてみると、それぞれの色に変化してくれる。

標準のコントロールのstyleとControlTemplateをみて、どの色を使っているかを調べる

WinUI3では、コントロールの部位(BorderやBackground、foregroundなど)ごとに、使う色のブラシの名前(key)が標準で決まっている。 そのブラシを、同じ名前で自分の使いたい色で上書きしてやることで、別の色に変えることができる。

標準のコントロールのstyleは、下記ファイルのL6238あたりにある。
※上のパスの10.0.22000.0Windowsのバージョン。ほかのバージョンのgeneric.xamlもあるが、おそらく内容はそれほど変わらないので今回は10.0.22000.0でやってみる。

C:\Program Files (x86)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.22000.0\Generic\generic.xaml

★22/10/03追記

WinUI3の標準のResource、styleは上記ではなかった。
→こちらを参照
https://tera1707.com/entry/2022/10/02/235818


今回は、ボタンの背景色(Background)、枠線の色(BorderBrush)、文字の色(Foreground)を、テーマごとに好きな色にしてみようと思うので、それ周りの色として何という名前の色を使っているかをstyleを見て調べる。

Buttonでは、通常時の色は<Setter Property="Background" Value="{ThemeResource ButtonBackground}" />のように、Setterを使ってプロパティに値をセットしている様子。

またそれとは別に、マウスオーバー時、押したとき、無効になったとき、にも色が設定されていて、それらは<Setter Property="Template">の中でセットされているControlTemplateの中の、<VisualState x:Name="PointerOver">等のところでセットされている様子。

それぞれButtonで使われてる色をまとめると、

ボタンの状態 背景色(Background) 枠線の色(BorderBrush) 文字の色(Foreground)
通常時 "{ThemeResource ButtonBackground}" "{ThemeResource ButtonBorderBrush}" "{ThemeResource ButtonForeground}"
マウスオーバー時 "{ThemeResource ButtonBackgroundPointerOver}" "{ThemeResource ButtonBorderBrushPointerOver}" "{ThemeResource ButtonForegroundPointerOver}"
押下時 "{ThemeResource ButtonBackgroundPressed}" "{ThemeResource ButtonBorderBrushPressed}" "{ThemeResource ButtonForegroundPressed}"
無効時 "{ThemeResource ButtonBackgroundDisabled}" "{ThemeResource ButtonBorderBrushDisabled}" "{ThemeResource ButtonForegroundDisabled}"

テーマごとのコントロールの色のブラシには、それぞれ、コントロール名、部位、状態が合体したような、割と使い道が推測できそうな名前がついている様子。

コントロールの色に使われているブラシを上書きする

ブラシを上書き、つまり、使ってるブラシを別の色のものにすり替えてやる。

例えば、上の表のButtonBackgroundPointerOverのブラシを別の(色の)ものにすり替えれば、マウスオーバーしたときのボタンの背景の色だけすり替えた色になる、という具合。

下記のように、リソースディクショナリを編集する。

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary x:Key="Dark">
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <!--ダークテーマ用のリソースを定義してやる-->
                    <Color x:Key="RegionColor">#FF000000</Color>
                    <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />

                    <!-- 標準のボタンの色 -->
                    <!-- 通常時 -->
                    <SolidColorBrush x:Key="ButtonBackground" Color="DarkRed" />
                    <SolidColorBrush x:Key="ButtonBorderBrush" Color="DarkGreen" />
                    <SolidColorBrush x:Key="ButtonForeground" Color="White" />
                    <!-- マウスオーバー時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="DarkGoldenrod" />
                    <SolidColorBrush x:Key="ButtonBorderBrushPointerOver" Color="DarkGreen" />
                    <SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="White" />
                    <!-- 押下時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundPressed" Color="DarkGreen" />
                    <SolidColorBrush x:Key="ButtonBorderBrushPressed" Color="DarkGreen" />
                    <SolidColorBrush x:Key="ButtonForegroundPressed" Color="White" />
                    <!-- 無効時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundDisabled" Color="Black" />
                    <SolidColorBrush x:Key="ButtonBorderBrushDisabled" Color="DarkGreen" />
                    <SolidColorBrush x:Key="ButtonForegroundDisabled" Color="White" />

                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
        <ResourceDictionary x:Key="Light">
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <!--ライトテーマ用のリソースを定義してやる-->
                    <Color x:Key="RegionColor">#FFFFFFFF</Color>
                    <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />

                    <!-- 通常時 -->
                    <SolidColorBrush x:Key="ButtonBackground" Color="Red" />
                    <SolidColorBrush x:Key="ButtonBorderBrush" Color="Green" />
                    <SolidColorBrush x:Key="ButtonForeground" Color="Black" />
                    <!-- マウスオーバー時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundPointerOver" Color="Yellow" />
                    <SolidColorBrush x:Key="ButtonBorderBrushPointerOver" Color="Green" />
                    <SolidColorBrush x:Key="ButtonForegroundPointerOver" Color="Black" />
                    <!-- 押下時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundPressed" Color="Green" />
                    <SolidColorBrush x:Key="ButtonBorderBrushPressed" Color="Green" />
                    <SolidColorBrush x:Key="ButtonForegroundPressed" Color="Black" />
                    <!-- 無効時 -->
                    <SolidColorBrush x:Key="ButtonBackgroundDisabled" Color="Gray" />
                    <SolidColorBrush x:Key="ButtonBorderBrushDisabled" Color="Green" />
                    <SolidColorBrush x:Key="ButtonForegroundDisabled" Color="Black" />
                </ResourceDictionary>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
        <ResourceDictionary x:Key="HighContrast">
            <!--ハイコントラスト時のリソースを定義してやる-->
            <StaticResource x:Key="RegionColor" ResourceKey="SystemColorWindowColor" />
            <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
        </ResourceDictionary>
    </ResourceDictionary.ThemeDictionaries>

    <!--全ボタンのスタイル-->
    <Style TargetType="Button">
        <Setter Property="Background" Value="{ThemeResource ButtonBackground}" />
        <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" />
        <Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" />
        <Setter Property="CornerRadius" Value="4" />
    </Style>   
    
</ResourceDictionary>

ダークとライトテーマ時にのみ、ボタンの色用のSolidColorBrushのリソースを追加。
ハイコントラストは、ライト、ダーク時にどんな色であったとしても、WinUI3が標準のコントロールに対して行っている配色のままで、ハイコントラストの目的(色が見えにくい方にコントロールが見えやすくするということ)は果たせるはずなので、そのままにしておく。

また、通常時の色(上記ではButtonBackgroundButtonBorderBrushButtonForeground)は、ブラシをテーマのリソースに追加しただけでは反映されないので、別途下にButton用のstyleを追加し、その中で、各部位に色をセットしてやる。
( <!--全ボタンのスタイル--> の部分)

これで、画面上のすべてのボタンに、テーマごとにセットした色が反映される。

独自の色ボタンのための作業をする

ここまでで、下記のような見た目になっている。

上と下のボタンと背景に別々の色を設定するのがゴールだが、まだ全部同じ色になっている。

全ボタンの色を変えたいときは、標準のstyle(generic.xamlに書いてあるやつ)が使っている色をオーバーライドすることで変えることができたが、特定のボタンだけ個別に色を設定したいときは、

  • テーマリソースに、独自のブラシ(SolidColorBrush)を定義する。
  • generic.xamlから、標準のボタンのstyleをコピーして、自分のリソースディクショナリにコピーする。
  • コピーしたstyleの中に、各部位の各状態のときの色の設定が書かれているので、そこの色を独自のブラシに置き換える。
  • 作ったstyleを、画面のxaml上にある対象のボタンに適用する。

という流れでやる。

テーマリソースに、独自のブラシ(SolidColorBrush)を定義する

次に、別々の色にするために、独自の色のブラシをつくる。

テーマごとのリソースのところに、自分の好きな名前(Key)を付けたブラシを定義する。

<!-- 独自の色ボタンの色 -->
<!-- 通常時 -->
<SolidColorBrush x:Key="MyButtonBackground" Color="LightPink" />
<SolidColorBrush x:Key="MyButtonBorderBrush" Color="DarkGreen" />
<SolidColorBrush x:Key="MyButtonForeground" Color="White" />
<!-- マウスオーバー時 -->
<SolidColorBrush x:Key="MyButtonBackgroundPointerOver" Color="LightBlue" />
<SolidColorBrush x:Key="MyButtonBorderBrushPointerOver" Color="DarkGreen" />
<SolidColorBrush x:Key="MyButtonForegroundPointerOver" Color="White" />
<!-- 押下時 -->
<SolidColorBrush x:Key="MyButtonBackgroundPressed" Color="LightGoldenrodYellow" />
<SolidColorBrush x:Key="MyButtonBorderBrushPressed" Color="DarkGreen" />
<SolidColorBrush x:Key="MyButtonForegroundPressed" Color="White" />
<!-- 無効時 -->
<SolidColorBrush x:Key="MyButtonBackgroundDisabled" Color="LightCoral" />
<SolidColorBrush x:Key="MyButtonBorderBrushDisabled" Color="DarkGreen" />
<SolidColorBrush x:Key="MyButtonForegroundDisabled" Color="White" />

独自の色ボタンのために、元々あったブラシのKeyの前に"My"を付けた名前(Key)を、状態と部位の分だけ別途定義する。
これを、ダーク、ライト、ハイコントラストのところにそれぞれ定義する。(もちろん、それぞれのテーマで色を変える)

※標準のブラシを上書したときは、ハイコントラストのところにはなにも書かなかったが、独自のstyleにセットするためのブラシの定義は、ハイコントラストのところにも書く。(そうしないと、そのstyleの中で使っているブラシが存在しない、というエラーになってしまう)

※このハイコントラストのときの色に何をセットすればいいか、は後で書く。

generic.xamlから、標準のボタンのstyleをコピーして、自分のリソースディクショナリにコピーする

標準のstyleの中にある、ボタンの中の各部位、各状態の色を、色だけ外から差し替えることができない(できるかもしれないがやり方見つからなかった)ので、標準のstyleをコピーして、その中に書かれてる色を、独自の色に置き換えたstyleを作って、それを対象のボタンに適用する。

まずは独自の色のボタンのstyleを作るために、generic.xamlの中のButtonのstyleをまるっとコピーし、その中の色を、上で作ったブラシに置き換える。

今回の場合は、ResourceDictionary1.xamlの中の、先ほど書いた全ボタンのスタイルの下あたりに、標準のボタンのstyleをコピーしてくる。 その後、色の部分を置き換える。

下記のような感じ。<!--★--> の部分が、色を置き換えた部分。

<!--独自の色ボタンのスタイル-->
<Style TargetType="Button" x:Key="MyButtonStyle">
    <Setter Property="Background" Value="{ThemeResource MyButtonBackground}" /><!--★-->
    <Setter Property="BackgroundSizing" Value="OuterBorderEdge" />
    <Setter Property="Foreground" Value="{ThemeResource MyButtonForeground}" /><!--★-->
    <Setter Property="BorderBrush" Value="{ThemeResource MyButtonBorderBrush}" /><!--★-->
    <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
    <Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
    <Setter Property="HorizontalAlignment" Value="Left" />
    <Setter Property="VerticalAlignment" Value="Center" />
    <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
    <Setter Property="FontWeight" Value="Normal" />
    <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
    <Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
    <Setter Property="FocusVisualMargin" Value="-3" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <ContentPresenter x:Name="ContentPresenter"
          Background="{TemplateBinding Background}"
          BackgroundSizing="{TemplateBinding BackgroundSizing}"
          BorderBrush="{TemplateBinding BorderBrush}"
          BorderThickness="{TemplateBinding BorderThickness}"
          Content="{TemplateBinding Content}"
          ContentTemplate="{TemplateBinding ContentTemplate}"
          ContentTransitions="{TemplateBinding ContentTransitions}"
          CornerRadius="{TemplateBinding CornerRadius}"
          Padding="{TemplateBinding Padding}"
          HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
          VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
          AutomationProperties.AccessibilityView="Raw">

                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal">

                                <Storyboard>
                                    <PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" />
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="PointerOver">

                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBackgroundPointerOver}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBorderBrushPointerOver}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonForegroundPointerOver}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <PointerUpThemeAnimation Storyboard.TargetName="ContentPresenter" />
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="Pressed">

                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBackgroundPressed}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBorderBrushPressed}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonForegroundPressed}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <PointerDownThemeAnimation Storyboard.TargetName="ContentPresenter" />
                                </Storyboard>
                            </VisualState>

                            <VisualState x:Name="Disabled">

                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Background">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBackgroundDisabled}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonBorderBrushDisabled}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MyButtonForegroundDisabled}" /><!--★-->
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>

                        </VisualStateGroup>

                    </VisualStateManager.VisualStateGroups>
                </ContentPresenter>

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

これで、画面上の上のボタンと下のボタンで、別々の色になる。(マウスオーバーや押下時の色も別々になる)

ハイコントラストのときの色に何をセットすればいいか

ここまでで、ダーク、ライト、ハイコントラストで、標準の色のボタン、独自の色のボタンの色をセットできたが、独自の色のボタンのハイコントラストの時の色が、リソースディクショナリでセットした色そのままが出ている。

そうなるのは当たり前なのだが、ここはそうではなく、独自の色ボタンも、標準のボタンの色と同じようにハイコントラストの設定画面で設定した色になってほしい。

ハイコントラストの設定画面というのは、下記の画面のこと。

この画面の、"テキスト"、"ハイパーリンク"、"選択されたテキスト"などの色には、それぞれ名前がついているので、その名前(Key)を、ハイコントラスト時に使っているブラシ(今回は、自分で作ったMyButtonBackgroundなど)の色に指定してやると、ハイコントラストの標準ボタンと同じ色味にすることができる。

その名前は、下記のページに記載されている。

docs.microsoft.com

ハイコントラスト設定画面の色と、それぞれの色の名前の対応は下記の通り。

今回は、実際にハイコントラストモードにして、設定画面の色を見ながら、実際のボタンのどこにどの色が使われてるか、を目で見て洗い出して、その色のキーを、自分のリソースディクショナリにセットした。

※標準のボタンstyleが、ハイコントラストの時に上記のブラシのどれを使ってるか、は、generic.xamlの上の方の、コントロール毎のリソース(ThicknessやFontWight、ColorやSolidColorBrushなど色々)を定義しているところの、

            ******************************************************************
            HIGH CONTRAST COMMON CONTROL PROPERTIES
            ******************************************************************

の下にある

<SolidColorBrush x:Key="ButtonBackgroundThemeBrush" Color="{ThemeResource SystemColorButtonFaceColor}" />

<SolidColorBrush x:Key="ButtonPointerOverBackgroundThemeBrush" Color="{ThemeResource SystemColorHighlightColor}" />

を見ればわかるっぽいのだが、本当にそうなのかがわかるようなドキュメントが見つけられなかった。
(そこら辺を見つつ、実際の動きを見て間違ってないか、を確認してやれば、今のところはよさそう)

結果、今回は下記のようにした。(間違いあるかも)

<!-- 独自の色ボタンの色 -->
<!-- 通常時 -->
<SolidColorBrush x:Key="MyButtonBackground" Color="{StaticResource SystemColorButtonFaceColor}" />
<SolidColorBrush x:Key="MyButtonBorderBrush" Color="{StaticResource SystemColorButtonTextColor}" />
<SolidColorBrush x:Key="MyButtonForeground" Color="{StaticResource SystemColorWindowTextColor}" />
<!-- マウスオーバー時 -->
<SolidColorBrush x:Key="MyButtonBackgroundPointerOver" Color="{StaticResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="MyButtonBorderBrushPointerOver" Color="{StaticResource SystemColorHighlightColor}" />
<SolidColorBrush x:Key="MyButtonForegroundPointerOver" Color="{StaticResource SystemColorWindowTextColor}" />
<!-- 押下時 -->
<SolidColorBrush x:Key="MyButtonBackgroundPressed" Color="{StaticResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="MyButtonBorderBrushPressed" Color="{StaticResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="MyButtonForegroundPressed" Color="{StaticResource SystemColorWindowTextColor}" />
<!-- 無効時 -->
<SolidColorBrush x:Key="MyButtonBackgroundDisabled" Color="{StaticResource SystemColorButtonTextColor}" />
<SolidColorBrush x:Key="MyButtonBorderBrushDisabled" Color="{StaticResource SystemColorButtonTextColor}" />
<SolidColorBrush x:Key="MyButtonForegroundDisabled" Color="{StaticResource SystemColorButtonTextColor}" />

微修正

細かい点だが、なぜか独自の色のボタンのほうが、自前のstyleを適用すると、ボタンの角がカクっとなってしまうので、styleにCornerRadiasをセットしてやる。

<Setter Property="CornerRadius" Value="4" />

できあがり

ここまでで、一通りテーマカラーの対応ができた。
(≒テーマごとに、ボタンに自分の好きな色をセットすることができた)

※最初のゴールのイメージ画像と、背景の色が無かったりしてちょっと違うが、まぁよしとするということで...

必要があればやること

今回、テーマリソースの記述や、styleの記述を、ResourceDictionaryの.xamlファイルに全部突っ込んでしまったが、少々見づらいので、できれば別の.xamlに分けるなどして見やすいようにしてあげた方がよいと思う。

参考

今回やりたい、テーマカラー対応のやり方が書かれている

docs.microsoft.com

ハイコントラスト時の色の名前(Key)が書かれている

docs.microsoft.com

参考書

WinUI3

WinUI3でアプリを作ろうと思ったときのとっかかりによかった。 msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。

C#①

表紙に書いてある通り、教科書として最適。 これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、 ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。

C#②

C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。