【テクニカル・上級編】Windows FormsにおけるDPI変更動的検知:DynamicDPI対応でモニター移動時のレイアウト崩れを防ぐ – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

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はぼやける。


PerMonitorV2

終わりに:技術は「流儀」である

WinFormsはレガシーかもしれない。しかし、その挙動をAPIレベルで制御し、美しく、軽快に動作させることは、アーキテクトとしての矜持そのものだ。

自動化やモダンなフレームワークに頼るだけでは見えない「OSとプロセスの境界線」。そこに踏み込み、制御することこそが、我々エンジニアの真価だ。モニターを跨いでも涼しい顔で動き続けるUI。それこそが、現場に求められる「真の堅牢性」である。

次回の記事では、このDPI対応を応用した「動的解像度切り替え時のコンポーネント再配置エンジン」の設計について深掘りしよう。健闘を祈る。

タイトルとURLをコピーしました