Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/content/blog-metas/2024-12-24-highdpi.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"postDate": "2024-12-22T15:04:11.245Z"
"postDate": "2024-12-22T15:04:11.245Z",
"updateDate": "2024-12-24T12:25:07.303Z"
}
11 changes: 5 additions & 6 deletions src/content/blogs/2024-12-24-highdpi.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ High DPI とは Windows 独自の概念で「**画面の細かさを表す仮想

![](2024-12-24-highdpi/gdiscaling.png)

GDI Scaling を有効にしても Windows 10 Creators Update (1703) より前の OS では完全に High DPI 非対応のピンぼけアプリとして振る舞うほか、対応環境でも 150% のように整数倍でない場合は仕組み上縮小が掛かり少しピンぼけするため、ネイティブに High DPI 対応するのが一番ですが、あまりコストをかけたくないとか、High DPI 対応は最新環境だけで十分といった状況では最適です。詳細は [Improving the high-DPI experience in GDI based Desktop Apps](https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/) を参考にしてください。本記事では、以降ネイティブに High DPI 対応する方法を解説します。
GDI Scaling を有効にしても Windows 10 Creators Update (1703) より前の非対応 OS では完全に High DPI 非対応のピンぼけアプリとして振る舞うほか、対応環境でも 150% のように整数倍でない場合は仕組み上縮小が掛かり少しピンぼけするため、ネイティブに High DPI 対応するのが一番ですが、あまりコストをかけたくないとか、High DPI 対応は最新環境だけで十分といった状況では最適です。詳細は [Improving the high-DPI experience in GDI based Desktop Apps](https://blogs.windows.com/windowsdeveloper/2017/05/19/improving-high-dpi-experience-gdi-based-desktop-apps/) を参考にしてください。本記事では、以降ネイティブに High DPI 対応する方法を解説します。

## High DPI の魔境

Expand Down Expand Up @@ -97,7 +97,7 @@ GDI Scaling を有効にしても Windows 10 Creators Update (1703) より前の
</assembly>
```

**プログラム実行中の DPI 変化に追従する**のは簡単で、単に window message [`WM_DPICHANGED`](https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged) (`0x2E0`) で `HIWORD(wParam)` を新 DPI 値、`lParam` を `const RECT *` 型の新ウィンドウサイズ及びウィンドウ位置として、ウィンドウやその中の UI 要素群を再配置すれば OK です。それこそ、[`WM_SIZE`](https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-size) でその時点の DPI 値に合わせて UI 要素を再配置する実装になっていれば、以下のフックを window procedure に入れておいて
**プログラム実行中の DPI 変化に追従する**のは簡単で、単に window message [`WM_DPICHANGED`](https://learn.microsoft.com/en-us/windows/win32/hidpi/wm-dpichanged) (`0x2E0`) で `HIWORD(wParam)` を新 DPI 値、`lParam` を `const RECT *` 型の新ウィンドウサイズ及びウィンドウ位置として、ウィンドウやその中の UI 要素群を再配置すれば OK です。それこそ、[`WM_SIZE`](https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-size) でその時点の DPI 値に合わせて UI 要素を再配置する実装になっていれば、以下のフックを window procedure に入れておいて

```c
case 0x2E0: // WM_DPICHANGED
Expand Down Expand Up @@ -268,13 +268,13 @@ High DPI 対応にあたり、考慮が必要な API があります。ここで
- `TaskDialog()` が使える場合はこれを使う
- 使えない場合は `MessageBoxW()` を使う

以下の `messageBox()` が最低限の実装例です(全機能をサポートしているわけではない)。関数仕様は `MessageBoxW()` 側に合わせていますが、`TaskDialog()` 側の都合で `hInst` が必須になっていること、`TaskDialog()` が対応しないメッセージ種は `MessageBoxW()` に fallback させていること、対応しない `MB_ICONQUESTION` は `TaskDialog()` 使用時には情報アイコンに置き換えるなどの違いがあります。実装としては、`MessageBoxW()` スタイルの引数を解析し、それに相当するダイアログが得られるように `TaskDialog()` の引数に変換していることと、非対応機能が使われた場合や `TaskDialog()` が存在しない場合は fallback として `MessageBoxW()` を使うようにしているのがポイントです。
以下の `messageBox()` が最低限の実装例です(全機能をサポートしているわけではない)。関数仕様は `MessageBoxW()` 側に合わせていますが、`TaskDialog()` 側の都合で `hInst` が必須になっていること、`TaskDialog()` が対応しないメッセージ種は `MessageBoxW()` に fallback させていること、対応しない `MB_ICONQUESTION` は `TaskDialog()` 使用時には情報アイコンに置き換えることなど、留意点がいくつかあります。実装としては、`MessageBoxW()` スタイルの引数を解析し、それに相当するダイアログが得られるように `TaskDialog()` の引数に変換していることと、非対応機能が使われた場合や `TaskDialog()` が存在しない場合は fallback として `MessageBoxW()` を使うようにしているのがポイントです。

```c
// TaskDialog() の関数ポインタ型定義
typedef HRESULT (WINAPI *TaskDialog_t)(HWND hwndOwner, HINSTANCE hInstance, const wchar_t *pszWindowTitle,
const wchar_t *pszMainInstruction, const wchar_t *pszContent,
int dwCommonButtons, const wchar_t *pszIcon, int *pnButton);
const wchar_t *pszMainInstruction, const wchar_t *pszContent,
int dwCommonButtons, const wchar_t *pszIcon, int *pnButton);

// TaskDialog() か MessageBoxW() を適宜呼び出してダイアログを出す
int messageBox(HWND hWnd, HINSTANCE hInst, const wchar_t *lpText, const wchar_t *lpCaption, unsigned uType) {
Expand Down Expand Up @@ -369,7 +369,6 @@ ReleaseDC(hWnd, cf.hDC);
if (ret) {
// 96 DPI に対応する値(基準値)に戻す
editFont.lfHeight = (editFont.lfHeight * 96 - (sysDPI - 1)) / sysDPI;
editFont.lfQuality = ANTIALIASED_QUALITY;

// ウィンドウサイズ変更時のハンドラを呼び出して UI 要素を再配置する
onSize(hWnd);
Expand Down