4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Win32は死なズ #3 高DPIに対応させる (追記)

4
Last updated at Posted at 2026-10-21

DPI対応について助言をもらった

先日公開させて頂いた
「Win32は死なズ #3 高DPIに対応させる」
ですが、有識者の友人から、
「これ、カスタムコントロール にしたら簡単にできない?」
という助言を頂きました。

・・・はて?カスタムコントロール?
え?どういう・・・?

今回は、
私は、VisualStudioの「カスタムコントロール」の事を全く理解していなかった。
という話です。
・・・恥ずかしい。

そもそも「カスタムコントロール」って何だ?

私は、ずーっと「カスタムコントロール」とは、

「Windows標準コントロールには無い独自のコントロールを色々なコントロールの組み合わせで実現する事」

程度に思っていて、いわゆるコーディング技法の一つだと思い込んでいました。

・・・すみません、私の認識は間違っていました。
VisualStudoには「カスタムコントロール」と言うコントロールが存在していたのです(驚)。

カスタムコントロールを作る

じゃあ、一体どうやってつくるのか。

実は「カスタムコントロール」は リソースエディタ で作れます。

リソースエディタの ツールボックス をよーくさがしてみると・・・。
image.png

ある!
しれっとある!
これが「カスタムコントロール」です。

早速ダイアログに配置してみます。
image.png

・・・なんじゃこれは・・・?

プロパティを見てみます。
image.png

ずいぶんとこざっぱりしています。

注目すべきはプロパティにある クラス です。
なんとこの クラス は、あらかじめ RegisterClassEx で登録しておいたウィンドウクラス名を指定しておくと、Dialogbox の生成時に自動的にそのウィンドウ(コントロール)を ダイアログの子ウィンドウとして生成 してくれるのです(驚)。

つまり、こうです。

CToggleSwitch.h
#define TOGGLESWITCH_CLASS_NAME         _T("ToggleSwitch")
CToggleSwitch.cpp
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 を設定します。
image.png
そして、ダイアログを作成する前にウィンドウクラスの登録を行います。

sample.cpp
// ウィンドウクラス登録
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 でカスタムコントロールとして使うウィンドウクラスをあらかじめ登録しておく」
ことです。

image.png
こんな感じでリソースエディタで配置。
比較できるように標準コントロールのボタンも置きました。

さて、DPI 100% で実行したCToggleSwitchSampleの画面が下図です。
image.png

ここで、DPI を 125% に変更してみます。
image.png
うお!?い、いけてる?

では、150%・・・
image.png
これは・・・うまくいってますね・・・。

ちなみにコンパイルオプションは、
image.png
「DPI認識」は「なし」を指定し、

Sample.cpp
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 認識」 、としてテストしてみます。
image.png

image.png
これはDPIを 150% にしたところですが、ダイアログのサイズは大きくなっているので、WM_DPICHANGED は来ていることが分かります。
ですが、コントロールのサイズが大きくはなりませんでした。

つまり、コンパイルオプションの「モニターごとの高い DPI 認識」ではDPI対応は不十分ということが分かります。
おそらく、VS2022のコンパイルオプションの「モニターごとの高い DPI 認識」は PerMonitorV2 ではなく、PerMonitor 相当 であると推測できます。

manifestで指定してみる

では次は、マニフェストファイルにDPI設定を埋め込んでみます。

Sample.manifest
<?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認識」は「なし」です。
image.png

では、ビルドして実行。
さらにDPIを 150% に変更します。
image.png
成功しています。
ウィンドウサイズに追従して、コントロールのサイズも大きくなっているのが分かります。

まとめ

カスタムコントロールによる、可変DPI対応はうまくいったと思います。

ただし、 VisualStudio2022 のコンパイルオプションの「モニターごとの高い DPI 認識」はおそらく、PerMonitorV2 とは別物と思われます。

今回のように PerMonitorV2 の挙動を使いたい場合は、
manifest の

<dpiAwareness>PerMonitorV2</dpiAwareness>

または、
ソースコード上で、

SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)

を使う必要があります。

参考:
https://learn.microsoft.com/ja-jp/windows/win32/hidpi/setting-the-default-dpi-awareness-for-a-process

何にせよ カスタムコントロールは便利 でした。
今まで知らなかったけど・・・。

Gitのリポジトリはこちらです。

Win32は死なズ。
では、また。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?