Windows Forms
デスクトップGUIアプリケーションを構築するための.NET/.NET FrameworkのUIフレームワーク。
DPI認識モード
アプリが「自分はどこまでDPIを扱えるか」をOSに申告する区分。Unaware・System Aware・Per-Monitor・Per-Monitor V2の4つを基本とする。
Unaware(DPI非対応)
DPI認識モードを何も宣言しない既定の状態で、アプリからは常に96 DPIに見える。
System DPI Aware
サインイン時の主モニターのDPIに合わせて描画が固定されるDPI認識モード。主モニター以外や、サインイン後にDPIが変わった場合はOSが拡大表示する。WPFは宣言なしでこの状態になる。
システムDPI認識
Per-Monitor(V1)
ウィンドウがあるモニターのDPIをトップレベルウィンドウへ通知するだけで、スケーリングは全部自前で処理する必要があるDPI認識モード。
Per-Monitor V2(PMv2)
子ウィンドウまでDPI変更が通知され、タイトルバーなど非クライアント領域はOSが自動でスケールするDPI認識モード。Windows 10 (1703)以降で利用できる。
GDIスケーリング
アプリとしてはUnawareのまま、GDIで描かれるテキストや図形だけをOSがベクターレベルで拡大する、Windows 10 1809以降の改良版ビットマップ拡大。
DPI仮想化
DPI対応を宣言していないアプリに対し、OSが「画面は96 DPIだ」と偽って描かせ、その結果をビットマップとして拡大表示する救済措置。レイアウトは崩れない代わりに全体がにじむ。
ビットマップストレッチ
DeviceDpi基準の自前描画スケーリング
Control.DeviceDpiを基準に96 DPI換算の設計値を現在のDPIへ変換する、自前描画コントロールのスケーリング手法。
AutoScaleMode
DPI認識モードとは別に、WinFormsのフォーム(ContainerControl)自身が持つ、デザイン時と実行時の環境差を吸収する自動スケーリング機構。
WinFormsの自動スケーリング機構
AutoScaleDimensions
デザイン時の環境での基準値(フォームのフォント寸法など)をDesigner.csに記録する、AutoScaleModeの基準値プロパティ。
AutoScaleDimensions プロパティ
デザイナーの高DPI事故
150%等の高DPIモニターでWinFormsデザイナーを開いてフォームを保存すると、Designer.csのAutoScaleDimensionsや座標・サイズが高DPI環境の値で上書きされ、100%環境でビルドするとレイアウトが縮んで見える事故。
Designer.csの高DPI破壊
exeプロパティの高DPI設定上書き
exeファイルのプロパティ「互換性」タブから、利用者側でそのアプリのDPIスケーリング動作を上書きできる設定。
互換性タブの高DPI設定の変更
Mixed-Mode DPI Scaling
Windows 10 1607以降、SetThreadDpiAwarenessContextによってトップレベルウィンドウ単位で異なるDPI認識モードを混在させられる仕組み。
トップレベルウィンドウ単位のDPI認識モード混在
.NETの高DPI設定(ApplicationHighDpiMode/SetHighDpiMode)
.NET(Core 3.1以降)のWinFormsアプリで、プロジェクトファイルのApplicationHighDpiModeプロパティまたはコードのApplication.SetHighDpiMode呼び出しによりDPI認識モードを宣言する仕組み。既定はSystemAware。
app.configでのDPI宣言
.NET Framework 4.7以降のWinFormsで、app.configのSystem.Windows.Forms.ApplicationConfigurationSectionにDpiAwarenessキーを設定してDPI認識モードを宣言する方法。
DpiAwareness設定
マニフェストでのDPI宣言
実行ファイルに埋め込むマニフェストのdpiAware(Vista以降)/dpiAwareness(Windows 10 1607以降)要素でDPI認識モードを宣言する方法。
WinFormsアプリの高DPI対応の第一歩は、自分のアプリが今どのDPI認識モードで動いているかを把握することである。
DPI認識モードの選択肢の1つが、何も宣言しない既定のUnawareである。
DPI認識モードの選択肢の1つが、サインイン時の主モニターDPIに固定されるSystem Awareである。
DPI認識モードの選択肢の1つが、通知のみでスケーリングを自前実装するPer-Monitor(V1)である。
DPI認識モードの選択肢の1つが、非クライアント領域もOSが追従するPer-Monitor V2である。
DPI認識モードに準じる選択肢として、コードを変えずに文字のにじみだけ軽減できるGDIスケーリングがある。
DPI対応を宣言していないアプリ(Unaware)には、OSが「画面は96 DPIだ」と偽ってビットマップ拡大するDPI仮想化が働く。
Per-Monitor(V1)は通知が来るだけでスケーリングを全部自前で処理する素のWin32向けの仕組みで、フレームワーク支援がなくWinFormsから選ぶ理由がない。
Per-Monitor V2ではモニター間移動のたびにDeviceDpiが変わるため、自前描画はDpiChanged系イベントでキャッシュ値を作り直す設計が必要になる。
WinFormsのフォームはDPI認識モードとは別に、AutoScaleModeという自動スケーリング機構を持つ。
AutoScaleModeは、デザインした環境での基準値をAutoScaleDimensionsとしてDesigner.csに記録し、実行時の環境値と比較して子コントロールを拡大縮小する。
AutoScaleDimensionsはデザイン時の環境値を記録する仕組みであるため、150%モニターでデザイナーを開いて保存すると値が書き換わり、100%環境でのビルド時にレイアウトが縮んで見える事故につながることがある。
互換性タブの高DPI設定で「システム(拡張)」を選ぶと、GDIスケーリングが適用されGDI描画のテキストだけくっきりする。
互換性タブの高DPI設定で「システム」を選ぶと、DPI仮想化を強制する(崩れないがにじむ)。
本記事が想定する段階移行のシナリオでは、Mixed-Modeは、メイン画面をPer-Monitor V2にしつつ直しきれないダイアログをUnawareのままOSに拡大させるという段階移行をWin32レベルでサポートする。
.NET(Core 3.1以降)のWinFormsは、プロジェクトファイルのApplicationHighDpiModeまたはApplication.SetHighDpiModeでDPI認識モードを宣言する。
マニフェストのdpiAware/dpiAwareness記述はapp.configのDpiAwareness設定を上書きするため、マニフェストで宣言しているアプリにapp.config側の設定を併用することは公式に推奨されていない。併記自体はでき、マニフェストが優先される
.NET Framework 4.6以下やVB6/MFC混在アプリでは、マニフェストでSystem Awareを宣言するところまでが現実的な対応になる。
.NET Framework 4.7以降は、マニフェストでWindows 10互換を宣言したうえで、app.configのDpiAwareness設定でPer-Monitor V2などを宣言する。
対応レベルの引き上げは、まずSystem Aware化で主モニターをくっきりさせることを第一段階とし、利用環境とアプリの寿命が見合う場合にPer-Monitor V2完全対応を第二段階として進める2段構えが現実的とされる。
WinFormsアプリがフレームワークの自動スケーリングに任せている場合、System Awareにすると起動時の主モニターDPIに合わせたスケーリングが一度だけ働き、主モニター上ではくっきり表示される。System AwareはWinForms固有ではないプロセス単位の設定であり、AutoScaleMode.Noneにして自前でレイアウトすることもできる。