Windows Formsの呪縛を解く:マルチモニター環境におけるDynamic DPI対応の極意
多くのVB.NETエンジニアが、Windows Forms(WinForms)という古のフレームワークと格闘している。「モニターを跨いで移動させるとUIが崩れる」「文字がぼやける」「ピクセルがずれる」。これらは単なる設定ミスではなく、WinFormsというアーキテクチャが「静的なレイアウト」を前提に設計されていることに起因する根源的な問題だ。
Windows 10/11の時代において、ユーザーは解像度が異なる複数のモニターを自由に行き来する。このダイナミックな環境下で、いかにして「崩れないUI」を維持するか。今日は、レガシーを愛し、かつ最新のWindows APIを掌握する者のための、DPI対応の「決定版」を伝授しよう。
—
1. なぜ「自動スケーリング」だけでは不十分なのか
WinFormsの `AutoScaleMode = Dpi` プロパティを設定すれば済むと思っているなら、それは大きな誤解だ。デフォルトの仕組みは、プロセス起動時のDPIに固定されがちであり、プロセス稼働中の動的なモニター移動(Per-Monitor DPI v2)には追従しない。
これを解決するには、Windows APIを直接叩き、OSの通知を泥臭くハンドリングする必要がある。
2. 実装の要諦:WM_DPICHANGED を掌握する
WinFormsのウィンドウプロシージャ(`WndProc`)をオーバーライドし、OSから送られてくる `WM_DPICHANGED` メッセージを捕捉する。これが唯一無二の正解だ。
実装コード例
Imports System.Runtime.InteropServices
Imports System.Drawing
Public Class FormMain
‘ DPI変更イベントを示す定数
Private Const WM_DPICHANGED As Integer = &H2E0
‘ ウィンドウメッセージをフックしてDPI変更を検知
Protected Overrides Sub WndProc(ByRef m As Message)
If m.Msg = WM_DPICHANGED Then
‘ 新しいDPI値を取得 (m.WParamの下位16bitがX軸のDPI)
Dim newDpi As Integer = LoWord(m.WParam.ToInt32())
Dim scaleFactor As Single = newDpi / 96.0F
‘ レイアウト再計算のトリガー
OnDpiChanged(scaleFactor)
‘ メッセージをOSへ返す
MyBase.WndProc(m)
Else
MyBase.WndProc(m)
End If
End Sub
Private Sub OnDpiChanged(scale As Single)
‘ スケールに応じたフォント再構築
‘ ※重要:フォントオブジェクトはGDIリソースを消費するため、
‘ 必ず旧オブジェクトをDisposeすること。さもなくばメモリリークの温床となる。
Dim oldFont As Font = Me.Font
Me.Font = New Font(“Meiryo UI”, 9.0F scale)
oldFont.Dispose()
‘ レイアウトの強制更新
Me.ResumeLayout(True)
Me.PerformLayout()
End Sub
Private Function LoWord(value As Integer) As Integer
Return value And &HFFFF
End Function
End Class
—
3. メモリ最適化とリソース管理の鉄則
前述のコードで最も注意すべきは、`Font` や `Image` などのGDIオブジェクトだ。DPI変更のたびに新しいフォントを生成することは、裏側でGDIハンドルを消費することを意味する。
- Disposeの徹底: `New Font(…)` を呼び出す前には、必ず既存のフォントオブジェクトの `.Dispose()` を呼ぶこと。これを怠れば、数時間の運用でアプリはメモリ不足に陥り、不可解なクラッシュを引き起こす。
- レイアウトのサスペンド: `SuspendLayout()` と `ResumeLayout(True)` を適切に挟むことで、コントロールが1つずつ再配置される際の「画面のチラつき」を抑止する。これはUXの基本だ。
4. 現場のシニアエンジニアへ:落とし穴を回避せよ
1. AutoScalingModeの精査:
`AutoScaleMode` を `Dpi` に設定すると、フレームワークが独自のスケーリングを試みる。自前で `WM_DPICHANGED` を実装する場合、一部のコントロールでダブルスケーリング(二重拡大)が発生することがある。その場合は、個々のコントロールの `AutoScaleMode` を `None` に設定し、自前のロジックで制御する勇気を持て。
2. App.configの manifest設定:
`app.manifest` で `dpiAwareness` を `PerMonitorV2` に設定していない限り、どれだけコードを書いてもOSがスケーリングを代行してしまい、UIはぼやける。
終わりに:技術は「流儀」である
WinFormsはレガシーかもしれない。しかし、その挙動をAPIレベルで制御し、美しく、軽快に動作させることは、アーキテクトとしての矜持そのものだ。
自動化やモダンなフレームワークに頼るだけでは見えない「OSとプロセスの境界線」。そこに踏み込み、制御することこそが、我々エンジニアの真価だ。モニターを跨いでも涼しい顔で動き続けるUI。それこそが、現場に求められる「真の堅牢性」である。
次回の記事では、このDPI対応を応用した「動的解像度切り替え時のコンポーネント再配置エンジン」の設計について深掘りしよう。健闘を祈る。
