もくじ
https://tera1707.com/entry/2022/02/06/144447#WinUI3
やりたいこと
Windowsの設定上で、ライト/ダークモードとハイコントラストのONOFFが変化したときに、なにか処理をしたい。
前提
- Windows10 Home 21H1 19043.1706
- VisualStudio2022 Community 17.2.3
- Windows App SDK 1.1.1
- 2022年7月の時点の調査
結論
イベントを取って、処理できそう。
調べた結果
UWPでは、Microsoft.Toolkit.Uwp.UIの中に、「ThemeListener」という、ライト/ダーク/ハイコントラストの変化を検出できる、そのものズバリなクラスがある。
Microsoft.Toolkit.Uwp.UI
https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI/7.1.2?_src=template
ThemeListenerクラスのページ
https://docs.microsoft.com/en-us/dotnet/api/microsoft.toolkit.uwp.ui.helpers.themelistener?view=win-comm-toolkit-dotnet-7.0
が、このnugetパッケージをWinUI3のプロジェクトで使おうとすると、「対応してません」と怒られる。
そこで、そのThemeListenerのコードがgithubに上がっているので、それを参考にWinUI3用のThemeListenerを自作できないかな?と思い、やってみた。
結果、できそう。
「できそう」、とちょっと不安げなのは、
- 今回作ったコードの中で「UISettings」クラスの中のColorValuesChangedというイベントを、
- ライト/ダーク/ハイコントラストの変化時のイベントとして使っていて、実際そのように動いてくれているが、
- ColorValuesChangedが、本当にその変化時のイベントとして使えるという公式資料が見つけられなかったのでちょっと不安
という理由。
Microsoft.Toolkit.Uwp.UIの中のThemeListenerのコードでは、
- ライト/ダークモードの変化イベントとして、UISettings クラスのColorValuesChanged イベントを使っている。
- ハイコントラストの変化イベントとして、AccessibilitySettings クラスのHighContrastChanged イベントを使っている。
のだが、どうもWinUI3アプリでは、AccessibilitySettings クラスのHighContrastChangedは動いてくれない様子。
参考:
[Preview 4] HighContrastChanged crashes app
https://github.com/microsoft/microsoft-ui-xaml/issues/4163
なので、ダメもとでUISettings クラスのColorValuesChanged イベントの中で、ライトダークとハイコントラスト両方を見るようにしてやると、ライト/ダークの変化時とハイコントラスト変化時の両方にうまくイベント発火してくれてるように見える。
ColorValuesChanged が本当にそういう使い方してもいいのか、を、MSのドキュメントの中をいろいろ探しては見たのだが、はっきりそうと書いてあるものは見つけられなかった。
一度試しに下記コードで実装して、様子を見ようと思う。
実験コード
Microsoft.Toolkit.Uwp.UI を参考に、今回作ったMyThemeListener
using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using System; using Windows.UI.ViewManagement; namespace ThemeListnerForWInUI3.Model { public delegate void ThemeChangedEvent(MyThemeListener sender); public class MyThemeListener :IDisposable { private UISettings _settings = new(); private AccessibilitySettings _accessible = new(); private DispatcherQueue dispatcherQueue = DispatcherQueue.GetForCurrentThread(); public ApplicationTheme CurrentTheme { get; set; } public bool IsHighContrast { get; set; } public event ThemeChangedEvent ThemeChanged; public MyThemeListener() { CurrentTheme = Application.Current.RequestedTheme; IsHighContrast = _accessible.HighContrast; _settings.ColorValuesChanged += _settings_ColorValuesChanged; ; } public void Dispose() { _settings.ColorValuesChanged -= _settings_ColorValuesChanged; } private void _settings_ColorValuesChanged(UISettings sender, object args) { dispatcherQueue.TryEnqueue(() => { if (CurrentTheme != Application.Current.RequestedTheme || IsHighContrast != _accessible.HighContrast) { CurrentTheme = Application.Current.RequestedTheme; IsHighContrast = _accessible.HighContrast; ThemeChanged?.Invoke(this); } }); } } }
それを使った画面のコードビハインド
using Microsoft.UI.Xaml; using System.Diagnostics; using ThemeListnerForWInUI3.Model; namespace ThemeListnerForWInUI3 { public sealed partial class MainWindow : Window { MyThemeListener mtl = new(); public MainWindow() { this.InitializeComponent(); // 自作テーマリスナーに登録 mtl.ThemeChanged += Mtl_ThemeChanged; this.Closed += MainWindow_Closed; } private void MainWindow_Closed(object sender, WindowEventArgs args) { // ウインドウを閉じるときに、イベント解除して自作テーマリスナーをDispose mtl.ThemeChanged -= Mtl_ThemeChanged; mtl.Dispose(); } private void Mtl_ThemeChanged(MyThemeListener sender) { // テーマとハイコントラストが変化したら、ココが呼ばれる Debug.WriteLine("テーマ:" + sender.CurrentTheme); Debug.WriteLine("ハイコントラスト:" + sender.IsHighContrast); } } }
※画面のxamlは、デフォで配置されるボタン削除して、Gridだけ配置されてる何もしないxamlなので割愛。
気づいたこと
Windowsがハイコントラストモードに入っているときは、「ダーク/ライトモード」は存在しない設定というか概念のようで、通常下記のように見えている「色」の設定画面が、
ハイコントラスト中は下記のようになる。
で、今回使ったApplication.Current.RequestedTheme
と_accessible.HighContrast
の値も、ハイコントラスト中は、
- Application.Current.RequestedTheme → Light
- _accessible.HighContrast → true
になっていた。
※もともと、ダークモードだった場合は、
- まず RequestedTheme = Dark / HighContrast = true になったあと、
- RequestedTheme = Light / HighContrast = true になる
という2段階の動きをする様子。2回通られると困るような処理をいれるのは避けた方がよいかも。
追記
ハイコントラスト時は
- RequestedTheme = Light / HighContrast = true
になってると書いたが、
- RequestedTheme = Dark / HighContrast = true
になっているケースもあった。ちょっとWindowsの中でどういう動きをしているかわからないので、あまり気にしない方がよいかも。
※ただ「RequestedTheme = Light / HighContrast = true」「RequestedTheme = Dark/ HighContrast = true」のどちらになるケースでも、元のモードによって2回通るケースがあるのは同じな様子。
追記2 SystemEvents.UserPreferenceChangedでハイコントラストの変化を取れないか?
WPFでは、下記のようにすると、ハイコントラストの変化を取ることができた。
using Microsoft.Win32; using System.Diagnostics; using System.Windows; using System.Windows.Forms; namespace WpfHighContrastChangedJikken { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged; } private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e) { if (e.Category == UserPreferenceCategory.Color) { Debug.WriteLine("ハイコントラストのONOFF変化しました"); Debug.WriteLine("{0} SystemParameters.HighContrast : {1}", e.Category, SystemInformation.HighContrast); } } } }
WInUI3でも同じことができないか?全くおなじコードを書いてやってみたが、動作してくれなかった。
ハイコントラストをONOFFしたときに、WPFではSystemEvents_UserPreferenceChanged
を複数Categoryで通っていたのに、WinUI3では一度も通ってくれなかった。
WInUI3では、機能しなくなっているのかも。
WM_THEMECHANGEDでハイコントラスト変化が取れる
コメントを頂いて知った内容。(とおりすがりさん、ありがとうございます)
アクセントカラー、透明効果の変更などは、WM_SETTINGCHANGE
で、
ハイコントラストの変更はWM_THEMECHANGED
で、取れるとのこと。
pinvokeするのが面倒だったので、C++のコードで試してみた。
流れとしては、
- WM_THEMECHANGEDが来たら、
- SystemParametersInfo()関数を読んで、
- 引数のHIGHCONTRAST構造体で受けたdwFlagsの
- ビットの0番目を見ると、
ハイコントラストのONOFFが取れるっっぽい。
SystemParametersInfoA関数
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
HIGHCONTRASTA 構造体
https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-highcontrasta
case WM_THEMECHANGED: { auto hc = HIGHCONTRAST(); hc.cbSize = sizeof(HIGHCONTRAST); auto si = SystemParametersInfo(SPI_GETHIGHCONTRAST, sizeof(HIGHCONTRAST), &hc, 0); break; }
ハイコントラストON時に取った情報
ハイコントラストOFFの時に取った情報
HIGHCONTRASTA 構造体のdwFlagsのハイコントラストONOFF情報
てな感じで、ハイコントラストONOFF変化時に、ハイコントラストONOFF情報が取れそう。
※結局、「Win32APIを使えば何でもできる」ということなのか...
参考
Microsoft.Toolkit.Uwp.UI の nugetパッケージ
https://www.nuget.org/packages/Microsoft.Toolkit.Uwp.UI/7.1.2?_src=template
Microsoft.Toolkit.Uwp.UIの中のThemeListenerのコード
[Preview 4] HighContrastChanged crashes app
WinUI3でHighContrastChanged を使うとアプリクラッシュするよ、というissue
https://github.com/microsoft/microsoft-ui-xaml/issues/4163
UISettings.ColorValuesChanged Event
今回、ライト/ダーク、ハイコントラスト変化時として使ったイベント
AccessibilitySettings.HighContrastChanged Event
WinUI3では動かなくなっている、ハイコントラスト変化時のイベント
参考書
WinUI3
WinUI3でアプリを作ろうと思ったときのとっかかりによかった。
msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。
C#①
表紙に書いてある通り、教科書として最適。
これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、
ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。
C#②
C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。