【実務・中級編】Windows Formsアプリケーションにおける高DPI環境でのカスタム描画とフォント滲み対策:AutoScaleMode.DpiとPerMonitorV2対応の全知識 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

Windows Forms高DPI地獄からの脱出:`PerMonitorV2`完全対応とカスタム描画の極意

開発現場のリーダーである私に、最近こんな悲鳴交じりの相談がよく舞い込んでくる。
「客先に納品した業務自動化ツールを4Kモニターやマルチディスプレイ環境で動かしたら、レイアウトが盛大に崩れ、文字はボケボケ、自家製カスタムボタンのアイコンは豆粒のように小さくなった……どうにかしてくれ」

よくある話だ。古い設計のWindows Forms(WinForms)アプリケーションをそのまま現代の高解像度環境放り込むと、OSの強制スケーリング(Bitmap Stretch)によって無残なボケ画像と化す。VB.NETerよ、いつまで`.AutoScaleMode = AutoScaleMode.Font`という「昭和の遺物」に頼っているのか?

今回は、4K、マルチモニター、そして異なるDPI混在環境(Per-Monitor V2)の荒波を完全に乗りこなし、プロフェッショナルな業務アプリケーションとしての尊厳を保つための「極限の知見」を授けよう。

—

1. なぜあなたのWinFormsアプリはボケるのか?(根本原因の理解)

原因はシンプルだ。OS(Windows 10/11)がアプリケーションを「DPI無知(DPI Unaware)」または「System DPI Aware」と判定し、高DPIモニター上で描画されたビットマップを単に拡大引き延ばしているからだ。

これを解決するには、アプリケーション自身がDPIの変化を完全にハンドリングし、OS側の粗い引き延ばしを無効化しなければならない。

必須の布陣:ManifestによるPerMonitorV2の宣言

コードを書く前に、アプリの生存戦略をOSに伝える必要がある。プロジェクトにアプリケーション マニフェスト ファイル(`app.manifest`)を追加し、``を正しく設定しなければ、いかにVB.NET側のコードを書き込もうが無意味に終わる。






PerMonitorV2, PerMonitor
true


`PerMonitorV2`を指定すると、モニターを移動した瞬間にフォームのサイズやDPIが動的に変更される。これを受け止めるコード側の設計が本題だ。

—

2. 現場で使える! `PerMonitorV2` 対応フォームのプロダクションコード

従来の `AutoScaleMode.Font` は、システムフォントのサイズ変化に依存するため、マルチモニター間でウィンドウを移動した際の動的なDPI変更に追従できない。

プロフェッショナルな現場では、`AutoScaleMode.Dpi` を採用し、さらにAPIレベルでフォントの滲みを防ぐためのカスタム描画ロジックをインジェクトする。

以下に、そのまま現場の基底フォーム(Base Form)として使える堅牢なVB.NETコードを提供する。

Imports System.Drawing
Imports System.Drawing.Text
Imports System.Windows.Forms

”’

”’ 高DPI・マルチモニター(PerMonitorV2)完全対応の基底フォーム
”’

Public Class HighDpiForm
Inherits Form

Public Sub New()
MyBase.New()

‘ 【最重要】古いフォントスケーリングを捨て、DPIベースのスケーリングを採用
Me.AutoScaleMode = AutoScaleMode.Dpi

‘ ダブルバッファリングを有効化し、カスタム描画時のチラつきを根絶する
Me.SetStyle(ControlStyles.OptimizedDoubleBuffer Or _
ControlStyles.AllPaintingInWmPaint Or _
ControlStyles.ResizeRedraw, True)
End Sub

”’

”’ モニター間の移動やDPI変更時にOSから呼び出されるイベント
”’

Protected Overrides Sub OnDpiChanged(e: DpiChangedEventArgs)
MyBase.OnDpiChanged(e)

‘ ディバッグ出力でDPIの変化を監視(実運用ではログ基盤へ接続)
System.Diagnostics.Debug.WriteLine($”[DPI Changed] 新しいDPI: {e.DeviceDpiNew}, 旧DPI: {e.DeviceDpiOld}”)

‘ ここでカスタムコントロールや動的フォントの再計算・再描画を強制する
Me.Invalidate(True)
End Sub

”’

”’ フォントの滲みを極限まで抑えるための描画オーバーライド
”’

Protected Overrides Sub OnPaint(e: PaintEventArgs)
‘ ClearTypeアンチエイリアスを強制し、現代的なシャープな文字描画を行う
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit

‘ 高品質な補間モードを設定(アイコンや画像の拡大縮小用)
e.Graphics.InterpolationMode = Drawing2D.InterpolationMode.HighQualityBicubic

MyBase.OnPaint(e)
End Sub

End Class

—

3. 業務アプリで頻発する「カスタム描画の罠」と解決策

業務効率化ツールでは、標準コントロールだけでは要件を満たせず、`OnPaint`をオーバーライドした「自製グリッド」や「カスタムボタン」を実装することが多い。ここで高DPI環境特有の罠を踏む開発者が後を絶たない。

罠:ハードコードされたピクセル値

「`Button1.Width = 100`」と書いていないか?
DPIが192(スケーリング倍率200%)の環境では、この「100」はそのまま100ピクセルのままで描画され、結果としてレイアウトが破綻する。

対策:`LogicalToDeviceUnits` の活用

VB.NETのコントロールには、論理座標を現在のDPIに応じたデバイス座標に変換する強力なメソッドが備わっている。これを使いこなせ。

Public Class CustomPanel
Inherits Panel

Protected Overrides Sub OnPaint(e: PaintEventArgs)
MyBase.OnPaint(e)

‘ 【NG】 Dim pen As New Pen(Color.Black, 2) -> 4Kでも細い線のまま、あるいはぼやける

‘ 【OK】 現在のDPIスケーリングに追従するペン幅を動的に算出
Dim rawWidth As Integer = 2
Dim scaledWidth As Integer = Me.LogicalToDeviceUnits(rawWidth)

Using pen As New Pen(Color.FromArgb(0, 120, 215), scaledWidth)
‘ 境界線の描画(スケーリングされてもシャープさを維持)
e.Graphics.DrawRectangle(pen, 0, 0, Me.ClientRectangle.Width – scaledWidth, Me.ClientRectangle.Height – scaledWidth)
End Using
End Sub
End Class

—

4. データベース・ファイル連携におけるDPI情報の扱い

「おい、UIの話とデータベースに何の関係がある?」と思ったか? 大ありだ。
近年の業務システムでは、ユーザーの「画面レイアウトのカスタマイズ状態(ウィンドウサイズ、スプリッターの位置、グリッドの列幅)」をユーザープロファイルとしてデータベース(SQL Server / SQLiteなど)やローカルのJSON/XMLファイルに保存・復元することが多い。

ここで高DPI環境を考慮しないと、以下のような大惨事が起きる。
1. フルHD環境(100%)でユーザーが調整したグリッド幅やウィンドウ位置を保存。
2. 翌日、4K環境(200%)のPCでログインしてアプリを起動。
3. データベースから「100%時のピクセル値」をそのまま読み込み、フォームに適用。
4. 結果、UIが画面の隅に縮こまって操作不能になる。

対策:DPI非依存の「論理座標(Unit-independent)」で永続化せよ

設定値をDBやファイルに保存する際は、必ず「現在のDPIスケールファクターで割った値(論理値)」として保存し、読み込み時に「現在の環境のDPIスケールファクターを掛けた値」に変換して復元しなければならない。

Public Module DpiPersistenceHelper

”’

”’ 現在のフォームのDPIからスケールファクター(例: 1.0, 1.5, 2.0)を取得する
”’

Public Function GetScaleFactor(form As Form) As Single
‘ 96DPIを基準(1.0)とした倍率を算出
Return form.DeviceDpi / 96.0F
End Function

”’

”’ DB保存用にピクセル値を論理値に正規化する
”’

public Function ToLogicalSize(devicePixels As Integer, form As Form) As Integer
Return CInt(devicePixels / GetScaleFactor(form))
End Function

”’

”’ DBから読み込んだ論理値を、現在のデバイスのピクセル値に復元する
”’

Public Function ToDeviceSize(logicalPixels As Integer, form As Form) As Integer
Return CInt(logicalPixels GetScaleFactor(form))
End Function

End Module

この変換レイヤーをデータバインドや設定復元ロジックの間に一枚噛ませるだけで、マルチモニター間をウィンドウが行き来しようとも、環境が異なろうとも、レイアウト情報は寸分狂わず美しく復元される。

—

5. チーフアーキテクトからの最後の一言

Visual BasicおよびWindows Formsは、レガシーな技術だとやゆされることがある。だが、ビジネスの現場において、迅速に堅牢な業務自動化ツールを構築するための生産性において、これを超えるプラットフォームはいまだに存在しない。

「動けばいい」という妥協が生む数ピクセルのズレや文字のボケは、エンドユーザーの眼精疲労と、システムへの信頼低下という莫大なコストを会社に支払わせる。

今回伝授した `PerMonitorV2` の宣言、`LogicalToDeviceUnits` による動的スケーリング、そしてDPI非依存の永続化設計。これらをプロジェクトの標準として組み込み、時代遅れなどと言わせない、圧倒的に洗練されたデスクトップアプリを構築してほしい。君たちのコードの品質が、組織の信頼を形作るのだ。

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