DPI対応について助言をもらった
先日公開させて頂いた
「Win32は死なズ #3 高DPIに対応させる」
ですが、有識者の友人から、
「これ、カスタムコントロール にしたら簡単にできない?」
という助言を頂きました。
・・・はて?カスタムコントロール?
え?どういう・・・?
今回は、
私は、VisualStudioの「カスタムコントロール」の事を全く理解していなかった。
という話です。
・・・恥ずかしい。
そもそも「カスタムコントロール」って何だ?
私は、ずーっと「カスタムコントロール」とは、
「Windows標準コントロールには無い独自のコントロールを色々なコントロールの組み合わせで実現する事」
程度に思っていて、いわゆるコーディング技法の一つだと思い込んでいました。
・・・すみません、私の認識は間違っていました。
VisualStudoには「カスタムコントロール」と言うコントロールが存在していたのです(驚)。
カスタムコントロールを作る
じゃあ、一体どうやってつくるのか。
実は「カスタムコントロール」は リソースエディタ で作れます。
リソースエディタの ツールボックス をよーくさがしてみると・・・。

ある!
しれっとある!
これが「カスタムコントロール」です。
・・・なんじゃこれは・・・?
ずいぶんとこざっぱりしています。
注目すべきはプロパティにある クラス です。
なんとこの クラス は、あらかじめ RegisterClassEx で登録しておいたウィンドウクラス名を指定しておくと、Dialogbox の生成時に自動的にそのウィンドウ(コントロール)を ダイアログの子ウィンドウとして生成 してくれるのです(驚)。
つまり、こうです。
#define TOGGLESWITCH_CLASS_NAME _T("ToggleSwitch")
bool CToggleSwitch::RegisterWndClass(HINSTANCE hInstance)
{
WNDCLASSEX wc{};
wc.cbSize = sizeof(wc);
if (GetClassInfoExW(hInstance, TOGGLESWITCH_CLASS_NAME, &wc)) return true;
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(nullptr, IDC_HAND);
wc.hbrBackground = nullptr;
wc.lpszClassName = TOGGLESWITCH_CLASS_NAME; // ウィンドウクラス名
return RegisterClassEx(&wc) != 0;
}
_T("ToggleSwitch") というクラス名で RegisterClassEx してますので、カスタムコントロールの クラス へ ToggleSwitch を設定します。

そして、ダイアログを作成する前にウィンドウクラスの登録を行います。
// ウィンドウクラス登録
CToggleSwitch::RegisterWndClass(hCurInst);
// ダイアログ作成
DialogBox(hCurInst, MAKEINTRESOURCE(IDD_SAMPLEBOX), nullptr, (DLGPROC)SampleDlgProc);
カスタムコントロールで作る利点
注意点
カスタムコントロールにすることで Winbdows が自動でウィンドウの作成をしてくれます。
これはとても便利な一方で、いままで CToggleSwitch::Create の様にコントロール作成用の自前関数でやっていたことを、 WM_CREATE もしくは WM_NCCREATE に移す必要があります。
併せて WM_DESTROY または WM_NCDESTROY で後始末は行う といった考慮も必要になります。
自作のコントロールを CreateWindowEx ではなく、カスタムコントロールを使う利点は色々あります。
例えば、
- コントロールの配置位置を整えてやすい。
例えば、スタティックで見出しを作りその座標に合わせてコントロールを配置したい場合などに、コントロールの位置合わせが簡単になります。 - リソースIDの管理が簡単。
vsが勝手にRESOURCE.hを作ってくれるのでリソースIDの管理が容易になります。
そして・・・、
カスタムコントロールで作成した自前のウィンドウクラスはダイアログに配置するため、DialogUnit(DLU) によるサイズ及び座標の管理になります。
つまり、他の標準コントロールと同様に DPIの変化に自動的に追従してくれるコントロールになる んじゃ無いか?と言う事です。
早速、前回作った ToggleSwitch をダイアログにカスタムコントロールとして配置して実行します。
カスタムコントロールのコツは、
「ダイアログを生成する前に RegsterClassEx でカスタムコントロールとして使うウィンドウクラスをあらかじめ登録しておく」
ことです。

こんな感じでリソースエディタで配置。
比較できるように標準コントロールのボタンも置きました。
さて、DPI 100% で実行したCToggleSwitchSampleの画面が下図です。

ここで、DPI を 125% に変更してみます。

うお!?い、いけてる?
では、150%・・・

これは・・・うまくいってますね・・・。
ちなみにコンパイルオプションは、

「DPI認識」は「なし」を指定し、
int APIENTRY _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPTSTR lpsCmdLine, int nCmdShow)
{
INITCOMMONCONTROLSEX icc{};
icc.dwSize = sizeof(icc);
icc.dwICC =
ICC_WIN95_CLASSES |
ICC_STANDARD_CLASSES;
InitCommonControlsEx(&icc);
// DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 指定
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
...
DialogBox(hCurInst, MAKEINTRESOURCE(IDD_SAMPLEBOX), nullptr, (DLGPROC)SampleDlgProc);
...
ソース内で SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); としています。
コンパイルオプションで指定してみる
では、ソース内の、
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
はコメントアウトして、コンパイルオプションの「DPI認識」を 「モニターごとの高い DPI 認識」 、としてテストしてみます。


これはDPIを 150% にしたところですが、ダイアログのサイズは大きくなっているので、WM_DPICHANGED は来ていることが分かります。
ですが、コントロールのサイズが大きくはなりませんでした。
つまり、コンパイルオプションの「モニターごとの高い DPI 認識」ではDPI対応は不十分ということが分かります。
おそらく、VS2022のコンパイルオプションの「モニターごとの高い DPI 認識」は PerMonitorV2 ではなく、PerMonitor 相当 であると推測できます。
manifestで指定してみる
では次は、マニフェストファイルにDPI設定を埋め込んでみます。
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<!-- Visual Styles 設定 -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<!-- DPI 設定 -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAwareness
xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
PerMonitorV2
</dpiAwareness>
</windowsSettings>
</application>
</assembly>
マニフェストファイルは上記のものを用意しました。
そしてコンパイルオプションの「追加のマニフェストファイル」へ 「Sample.manifest」 を指定します。
「DPI認識」は「なし」です。

では、ビルドして実行。
さらにDPIを 150% に変更します。

成功しています。
ウィンドウサイズに追従して、コントロールのサイズも大きくなっているのが分かります。
まとめ
カスタムコントロールによる、可変DPI対応はうまくいったと思います。
ただし、 VisualStudio2022 のコンパイルオプションの「モニターごとの高い DPI 認識」はおそらく、PerMonitorV2 とは別物と思われます。
今回のように PerMonitorV2 の挙動を使いたい場合は、
manifest の
<dpiAwareness>PerMonitorV2</dpiAwareness>
または、
ソースコード上で、
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)
を使う必要があります。
何にせよ カスタムコントロールは便利 でした。
今まで知らなかったけど・・・。
Gitのリポジトリはこちらです。
Win32は死なズ。
では、また。

