WinFormsの高DPI対応 ── 4Kモニターでぼやける・崩れる原因と現実的な対処

· 更新日: · · WinForms, 高DPI, Windows, .NET, C#, .NET Framework, レガシー保守, UI, 技術相談

更新履歴(4件・最終更新 2026年08月02日)

この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。

記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
用語の説明と、症状を手元で再現・確認する手順を追加しました。あわせて設定の操作手順、検証環境の構成図、設計時の値がずれる事故が差分にどう現れるかの例を追加しています。
本文中の関連記事へのリンクの文言が、リンク先の現在のタイトルと食い違っていたのを、実際のタイトルに揃えました。本文の内容は変えていません。
初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589913)

この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。

小村 豪(2026)「WinFormsの高DPI対応 ── 4Kモニターでぼやける・崩れる原因と現実的な対処」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589913 https://staging.comcomponent.com/blog/winforms-high-dpi-guide/

DOI(最新版)
10.5281/zenodo.21589913
DOI(この版)
10.5281/zenodo.21732907

「新しいノート PC に入れ替えたら、業務アプリの文字がにじんで読みにくい」「4K モニターにつないだらボタンとラベルが重なった」「客先の 125% 環境でだけ帳票プレビューが崩れる」。ここ数年、PC の入れ替え時期にこの種の相談が急増しています。フル HD を超える高解像度ディスプレイと 125〜200% の表示スケーリングが業務用 PC でも標準になり、96 DPI・100% 表示の時代に作られた WinForms アプリが、そのままでは快適に表示できなくなったためです。古い WinForms アプリの保守相談として、今いちばん頻度が高いテーマと言ってよいと思います。

厄介なのは、症状が「ぼやける」「崩れる」「一部だけ小さい」とばらばらに見えることです。実際には原因は数種類に整理でき、どの症状がどの原因に対応するかが分かれば、直し方も「どこまでやるか」の判断もできます。この記事では、Windows の DPI スケーリングの仕組みと DPI 認識モードを土台に、WinForms アプリ(.NET / .NET Framework 両方)の高DPI対応を、設定方法・定番の罠・段階的な対応戦略まで実務目線で整理します。

この記事で使う用語

結論の前に、本文で繰り返し出てくる語だけ先に押さえておきます。それぞれ本文の何章で扱うかも併記したので、意味が取れなくなったらここへ戻ってください。

用語 意味 詳細
DPI / 表示スケーリング 1 インチあたりのドット数。96 DPI を 100% として、125% = 120 DPI、150% = 144 DPI と対応します 2 章
DPI 仮想化 DPI 対応を宣言していないアプリに対し、OS が「画面は 96 DPI」と偽って描かせ、その結果をビットマップとして拡大表示する救済措置。レイアウトは崩れない代わりに全体がにじみます1 2 章
DPI 認識モード アプリが「自分はどこまで DPI を扱えるか」を OS に申告する区分。Unaware / System Aware / Per-Monitor / Per-Monitor V2 の 4 つ2 3 章
System Aware 「サインイン時の主モニターの DPI に合わせる」モード。主モニターではくっきり、他のモニターへ移すと OS が拡大してにじみます 3 章
PMv2 Per-Monitor V2 の略。モニターごとの DPI に追従するモード。タイトルバーなど非クライアント領域は OS が自動でスケールします2 3 章
GDI スケーリング アプリは Unaware のまま、GDI で描かれた文字や図形だけを OS がベクターレベルで拡大する改良版のビットマップ拡大。コードを変えずに文字のにじみだけ軽減できます2 3 章・4.3 節
マニフェスト 実行ファイルに埋め込む XML。DPI 認識モードの宣言先の 1 つです 4 章
AutoScaleMode DPI 認識モードとは別物の、WinForms のフォーム自身が持つ自動スケーリング機構 5 章

1. まず結論

  • 高DPI環境で古いアプリがぼやけるのはバグではなく、OS の救済措置(DPI 仮想化)です。DPI 非対応のアプリを Windows が 96 DPI で描かせてからビットマップ拡大しているので、レイアウトは崩れない代わりににじみます。1
  • 逆に「くっきりしているのに崩れる」のは、DPI 対応を宣言しているのにレイアウトが追従できていない状態です。ぼやけと崩れは原因が正反対なので、対処の前にどちらなのかを見分けてください。
  • 対応の第一歩は、自分のアプリが今どの DPI 認識モード(Unaware / System Aware / Per-Monitor V2)で動いているかを把握することです。マニフェスト・app.config・API 呼び出しのどれで(あるいは何も)宣言しているかを確認します。3
  • 設定方法は世代で分かれます。.NET(Core 3.1〜.NET 8)ならプロジェクトファイルか Application.SetHighDpiMode、.NET Framework 4.7 以降なら app.config、4.6 以下はマニフェストで System Aware までが現実的なラインです。45
  • デザイナーは必ず 100%(96 DPI)のモニターで開くこと。150% 環境でフォームを開いて保存すると AutoScaleDimensions が書き換わり、チーム全員のレイアウトが壊れる定番事故になります。6
  • 全面対応(Per-Monitor V2)は工数がかかります。「System Aware で主モニターだけくっきり」を第一段階、PMv2 完全対応を第二段階とする 2 段構えで、アプリの寿命と利用環境に見合った投資をするのが現実的です。
  • 自社で改修できない・間に合わない場合の暫定策として、exe のプロパティ→互換性から利用者側で上書きできる「高DPI設定」も知っておくと、問い合わせ対応が一段楽になります。2

この記事の知識マップ

WinFormsアプリが高DPI環境でぼやけるのは、DPI認識モードを宣言していないUnawareアプリに対してOSがDPI仮想化(ビットマップ拡大)を働かせる救済措置であり、逆にくっきりしているのに崩れるのはDPI対応を宣言したのにレイアウトが追従できていない状態である。System AwareはWinFormsのAutoScaleModeによる起動時一度きりのスケーリングで主モニターだけをくっきりさせ、モニター間移動まで追従させるにはPer-Monitor V2への完全対応が必要になる。設定方法は.NET・.NET Framework 4.7以降・4.6以下で異なり、マニフェストのdpiAware宣言とapp.configのDpiAwareness設定は併用してはならない。高DPIモニターでWinFormsデザイナーを開いてフォームを保存するとAutoScaleDimensionsが書き換わる事故が定番で、対策はデザイナーを100%で開くことである。

WinFormsの高DPI対応の知識マップWinFormsアプリのDPI認識モード(Unaware・System Aware・Per-Monitor V1/V2・GDIスケーリング)がDPI仮想化やAutoScaleMode、デザイナーの高DPI事故、世代別の設定方法とどう結び付くかを示す図。前提とする利用する利用する利用する利用する利用する原因になり得る用いるのは非推奨前提とする利用する利用する原因になり得る利用する利用する利用する利用する用いるのは非推奨利用する利用するより先に行うべき前提とするWindows FormsDPI認識モードUnaware(DPI非対応)System DPI AwarePer-Monitor(V1)Per-Monitor V2(PMv2)GDIスケーリングDPI仮想化DeviceDpi基準の自前描画スケーリングAutoScaleModeAutoScaleDimensionsデザイナーの高DPI事故exeプロパティの高DPI設定上書きMixed-Mode DPI Scaling.NETの高DPI設定(ApplicationHighDpiMode/SetHighDpiMode)app.configでのDPI宣言マニフェストでのDPI宣言

図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全21件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle

2. なぜぼやけるのか ── DPI仮想化の仕組み

Windows の表示スケーリングは 96 DPI を 100% とする倍率で表現されます。125% = 120 DPI、150% = 144 DPI、200% = 192 DPI です。4K(3840×2160)の 27 インチモニターを 100% で使うと文字が小さすぎるため、Windows は既定で 150% 前後を推奨してきます。つまり高解像度ディスプレイの普及は、そのまま「96 DPI 以外で動く環境の普及」を意味します。

問題は、古いデスクトップアプリの多くが「画面はずっと 96 DPI」という前提で書かれていることです。座標もフォントもアイコンもピクセル直書きで、150% 環境でそのまま描画すると、すべてが物理的に 2/3 の大きさに見えてしまいます。そこで Windows は、DPI 対応を宣言していないアプリには「画面は 96 DPI だ」と嘘をつき、アプリが 96 DPI のつもりで描いた結果をビットマップとして拡大表示します。これが DPI 仮想化(ビットマップストレッチ)です。1

この仕組みを踏まえると、症状と原因はきれいに対応します。最初の切り分けにこの表を使ってください。

症状 原因 状態
全体が一様ににじむ・ぼやける。レイアウトは崩れない DPI 仮想化(OS がビットマップ拡大している) DPI 非対応(Unaware)。安全だが汚い
文字はくっきりだが、コントロールが重なる・見切れる DPI 対応を宣言したがレイアウトが追従していない 中途半端な DPI 対応。ここからが改修対象
主モニターではくっきり、サブモニターに移すとぼやける System Aware(起動時の主モニター DPI 固定) 仕様どおり。PMv2 にするかの判断対象
文字サイズはよいが、アイコン・画像だけ小さい/粗い ビットマップ資産が 96 DPI 用のまま 画像資産の未対応(6 章)
特定の画面・コントロールだけ崩れる 固定座標レイアウト、自前描画、サードパーティ製コントロール 個別改修の対象(6 章)

重要なのは、「ぼやけている」アプリはむしろ壊れていないということです。OS の救済措置が正しく効いています。改修とは、この救済措置を断って「自分でスケーリングします」と宣言する(=DPI 認識モードを上げる)ことなので、宣言した瞬間にレイアウトの問題が全部自分の責任になります。「とりあえず PMv2 にしてみたら余計にひどくなった」という相談はこの構図で起きています。

2.1 手元で症状を再現・確認する手順

この記事には画面キャプチャを掲載していません。「ぼやける」「崩れる」の違いは静止画で見せるより、手元の実機で切り替えて見比べたほうが確実に分かります。次の手順で、上の表の 1 行目と 2 行目を自分の目で見分けてください。モニター 1 枚でもここまでは確認できます。

  1. 拡大率を 100% と 150% で切り替える。 スタート > 設定 > システム > ディスプレイ >「拡大縮小とレイアウト」>「テキスト、アプリ、その他の項目のサイズを変更する」で選びます。7 差がいちばん出るのは 100% と 150% の比較です。
  2. 必ずアプリを起動し直す。 Unaware も System Aware も、起動中のアプリは新しい拡大率に追従しません。変更後は毎回起動し直します。System Aware の場合は、主モニターを入れ替えたときだけサインアウト・サインインも必要です(System DPI はサインイン時に固定されるため)。
  3. 拡大鏡で細部を見比べる。 Windows ロゴキー + +(プラス)で拡大鏡が起動します。8 400% 程度まで上げて、次のどちらに当てはまるかを判定します。
    • 文字の輪郭が一様にぼやけ、線が中間色でにじんでいる ── DPI 仮想化(表の 1 行目)です。文字も罫線もアイコンも等しく劣化しているのが決め手で、アプリ側の問題ではありません。
    • 文字はくっきりしているのに、ボタンとラベルが重なる・端が切れている ── DPI 対応を宣言済みでレイアウトが追従できていない状態(表の 2 行目)です。ここからが 6 章の改修対象になります。
  4. アイコンだけを見る。 文字がくっきりしているのに ToolStrip のアイコンだけ豆粒・粗いなら、表の 4 行目(96 DPI 用ビットマップの直貼り)です。
  5. モニター間を移動させる(2 枚以上ある場合)。 拡大率の違うモニターへウィンドウをドラッグし、移した先でだけにじむなら System Aware(表の 3 行目)で、仕様どおりの動作です。

なお、いま自分のアプリがどのモードで動いているかは、上の見え方からほぼ逆算できます。100% と 150% のどちらでもくっきりしていて、モニター間を移動してもくっきりのままなら PMv2 相当主モニターだけくっきりなら System Awareどの環境でも一様ににじむなら Unaware です。宣言箇所(マニフェスト・app.config・API 呼び出し)の確認は 4 章で行います。

3. DPI認識モードの整理

アプリはプロセス(正確には Windows 10 以降はトップレベルウィンドウ)単位で、自分がどこまで DPI を扱えるかを OS に宣言します。モードは実質 4 つです。12

モード 導入 OS アプリから見える DPI モニター間移動・DPI 変更時 WinForms での現実的な位置づけ
Unaware ── 常に 96 OS がビットマップ拡大(ぼやける) 何も宣言しない場合の既定。ぼやけるが崩れない
System Aware Vista サインイン時の主モニターの DPI 固定 主モニター以外・変更後は OS が拡大(ぼやける) 全世代で使える。割り切り対応の本命
Per-Monitor (V1) 8.1 ウィンドウがあるモニターの DPI トップレベルに通知のみ。スケーリングは全部自前 フレームワーク支援がなく実用性に乏しい。選ばない
Per-Monitor V2 10 (1703) ウィンドウがあるモニターの DPI 子ウィンドウまで通知。非クライアント領域は OS が追従 完全対応の本命。.NET Framework 4.7+ / .NET で利用可

Per-Monitor V1 と V2 の差は実務では決定的です。V1 は「通知は来るがあとは全部自分で」という素の Win32 向けの仕組みで、WinForms から使う理由はありません。V2 はタイトルバー・スクロールバー・メニューなどの非クライアント領域を OS が自動でスケールし、WinForms 側のフレームワーク支援(後述)も V2 前提で作られています。Per-Monitor を選ぶなら V2 一択です。2

もうひとつ、GDI スケーリング(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED、Windows 10 1809〜)というモードがあります。アプリとしては Unaware のまま、GDI で描かれるテキストや図形だけ OS がベクターレベルで拡大してくれる改良版のビットマップ拡大です。2 コードを一切変えずに文字のにじみだけ軽減できる可能性があるため、改修できないアプリの暫定策として覚えておく価値があります(4.3 節)。

なお、Windows 10 1607 以降はトップレベルウィンドウ単位で異なるモードを混在させる Mixed-Mode もあり、「メイン画面だけ PMv2、直しきれないダイアログは Unaware のまま OS に拡大させる」という段階移行が Win32 レベルではサポートされています。9 WinForms から気軽に使えるものではありませんが、「全画面を一度に直さなくてもよい」という設計思想は 7 章の段階戦略につながります。

4. 設定方法 ── 世代別の正解

自分のアプリがどの世代かで、DPI 認識モードの宣言方法が違います。ここを取り違えると「設定したのに効かない」で時間を溶かすので、世代ごとに分けて書きます。

4.1 .NET (Core 3.1〜.NET 8) の WinForms

.NET では Application.SetHighDpiMode が用意されており、テンプレートが生成する ApplicationConfiguration.Initialize()(.NET 6 以降)がこれを呼び出します。既定は SystemAware です。4 設定はプロジェクトファイルに書くのが推奨で、Visual Studio のデザイナーもこの設定を参照します。

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <!-- SystemAware(既定)/ PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
    <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
  </PropertyGroup>
</Project>

注意点として、プロジェクトファイルの ApplicationHighDpiModeApplicationConfiguration.Initialize().NET 6 で入った仕組みです。4 .NET Core 3.1 / .NET 5 のプロジェクトではこのプロパティを書いても効かないので、次のように Application.SetHighDpiMode をコードで直接呼びます(ApplicationConfiguration.Initialize() を使わない古いスタイルの Main でも同様です)。どちらの場合も、ウィンドウを 1 つでも作る前に呼ぶ必要があります。

[STAThread]
static void Main()
{
    Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

.NET 6 以降は PMv2 時のコンテナーコントロールや MDI 子ウィンドウのスケーリングが改善されており、「200% のモニターから 100% のモニターへ移すとコントロールがずれる」といった .NET 5 までの問題がかなり解消されています。4 PMv2 で本気の対応をするなら、.NET Framework より新しい .NET のほうが明らかに戦いやすい、というのが実感です。.NET Framework からの移行を検討する材料は「.NET Framework から .NET への移行前チェックリスト」にまとめています。

4.2 .NET Framework 4.7以降

.NET Framework は 4.7 で高DPI対応が大きく強化されました。コントロールのスケーリング改善、DPI 変更イベント(DpiChanged 系)、DeviceDpi プロパティなどが入っています。ただしオプトインで、次の 2 点をセットで設定して初めて有効になります。5

まず、マニフェストで Windows 10 互換を宣言します(これがないと 4.7 の高DPI機能自体が有効になりません)。

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Windows 10 互換宣言 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

次に、app.config の System.Windows.Forms.ApplicationConfigurationSection で DPI 認識モードを宣言します。

<configuration>
  <System.Windows.Forms.ApplicationConfigurationSection>
    <add key="DpiAwareness" value="PerMonitorV2" />
    <!-- 自前でスケーリング済みの画面がある場合は個別機能をオフにできる -->
    <!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
  </System.Windows.Forms.ApplicationConfigurationSection>
</configuration>

あわせて Main の先頭で Application.EnableVisualStyles() を呼んでいることを確認してください。ここで注意が 1 つあります。昔からの方法であるマニフェストへの <dpiAware> / <dpiAwareness> 記述は、app.config の設定を上書きしてしまうため、4.7 の WinForms では併用しないのが公式の推奨です。5 過去に System Aware 化のためにマニフェストへ <dpiAware>true</dpiAware> を書いたアプリを 4.7+PMv2 へ上げるときは、マニフェスト側の宣言を整理するところから始めます。「app.config に書いたのに効かない」の原因はたいていこれです。

4.3 .NET Framework 4.6以下・VB6/MFC混在

4.6 以下の WinForms には PMv2 を支援するフレームワークコードがなく、無理に宣言してもレイアウト崩れを自力で全部処理することになります。この世代の現実解は マニフェストで System Aware を宣言するところまでです。OS レベルの宣言はマニフェストが推奨手段で、<dpiAware>(Vista〜)と <dpiAwareness>(Windows 10 1607〜)を併記すると新旧 OS の両方で意図どおりに解釈されます。3

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
  </asmv3:windowsSettings>
</asmv3:application>

System Aware にすると、起動時の主モニター DPI に合わせて WinForms の自動スケーリング(5 章)が一度だけ働き、主モニター上ではくっきり表示されます。AutoScaleMode.Font が正しく設定されたフォームなら、この宣言だけでかなり見られる状態になることが多いです。なお SetProcessDpiAwareness などの API で実行時に宣言する方法もありますが、ウィンドウ作成後には変更できず、マニフェストでの宣言が公式に推奨されています。10 VB6 や MFC のコードが混在するアプリでも考え方は同じで、exe のマニフェストでプロセス全体のモードが決まります(MFC 自体には自動スケーリング支援がないため、System Aware 宣言後の画面点検は必須です。この世代の資産の扱いは「WindowsのMFCとは」も参照してください)。

改修そのものができない場合の利用者側の暫定策も紹介しておきます。exe のプロパティから、そのアプリの DPI スケーリングを利用者側で上書きできます。2 電話口でそのまま読み上げられるよう、画面の項目名だけを順に並べます。

  1. 対象の exe ファイルを右クリックし、「プロパティ」を選びます。ショートカットしか手元にない場合は、右クリックの「ファイルの場所を開く」で実体のあるフォルダーへ移動してもらいます。
  2. 「互換性」タブを開きます。
  3. 「高DPI設定の変更」ボタンを押します。
  4. 開いたダイアログの「高DPIスケール設定の上書き」チェックボックスをオンにします。
  5. その下の「拡大縮小の実行元」ドロップダウンで動作を選びます。「システム」にすると DPI 仮想化を強制(=崩れないがにじむ)、「システム(拡張)」にすると 3 章で触れた GDI スケーリングが適用され、GDI 描画のテキストだけくっきりします。2
  6. 「OK」で 2 つのダイアログを閉じ、アプリを起動し直して見え方を確認します。

「システム」と「システム(拡張)」のどちらが良いかはアプリ次第なので、2.1 節の手順で見比べて決めてもらうのが確実です。万能ではありませんが、「客先で今日なんとかしたい」ときに電話口で案内できる手段として、サポート手順書に入れておく価値があります。ショートカットのプロパティにはこの項目がないので、実体の exe を開いてもらうという一点だけは、案内時に外さないでください。

5. AutoScaleModeとデザイナーの罠

WinForms には DPI 認識モードとは別に、フォーム自身の自動スケーリング機構があります。ここを理解していないと、System Aware にしても正しくスケールしません。

仕組みはこうです。デザイン時に各フォーム(ContainerControl)は、スケーリングの基準を AutoScaleMode に、デザインした環境での基準値を AutoScaleDimensions に記録します。実行時には現在の環境の値(CurrentAutoScaleDimensions)と比較し、差があれば子コントロールをまとめて拡大縮小します。11 つまり「デザイン時と実行時の環境差」を吸収する仕組みであり、基準値はコード(Designer.cs)に焼き込まれます。

AutoScaleMode の選択肢は実質 2 つです。

AutoScaleMode 基準 特徴
Font(推奨・既定) フォームのフォントの寸法 DPI が上がるとシステムフォントの実寸も変わるため DPI 追従を兼ねる。ユーザーのフォント設定変更にも追従
Dpi 画面の DPI DPI にだけ比例。グラフィックス主体の画面向け
None ── 自動スケーリング無効。96 DPI 直書きアプリはこれになっていることが多い

迷ったら Font です。注意点として、基底フォームと派生フォームで異なるモードを混在させると予期しない結果になることが公式にも明記されています。11 フォーム継承を使っているアプリでは、全フォームのモードをそろえる棚卸しが先です。

そして本題の罠です。AutoScaleDimensions は「デザインした環境の値」を記録する仕組みなので、150% のモニターで Visual Studio のデザイナーを開いてフォームを保存すると、Designer.cs の AutoScaleDimensions が 150% の値(Font モードなら 6F, 12F が 9F, 18F など)に書き換わります。座標・サイズも 1.5 倍値で保存されます。このフォームを 100% 環境のメンバーがビルド・実行すると全体が縮んで表示され、差分レビューでは全コントロールの座標が変わった巨大な diff が出ます。「高DPIのノート PC を支給された新メンバーがフォームを 1 枚触ったら、リポジトリのレイアウトが壊れた」という、高DPI相談の定番事故です。

この事故は、コミット前の git diff に必ず現れます。フォームを 1 枚開いて保存しただけなのに、Designer.cs に次のような差分が出ていたら、それが 150% 環境で開いた証拠です。

-            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);

この 1 行に続いて、ClientSize と全コントロールの Location / Size が軒並み 1.5 倍の値に書き換わった差分が並びます。レビューで見るべきはこの AutoScaleDimensions の 1 行だけで、ここが変わっていたら以降の座標の差分は読まずに差し戻して構いません。

対策は原則 1 つ、デザイナーは 100%(96 DPI)で開くことです。Visual Studio 自体は DPI 対応アプリですが、WinForms デザイナー(.NET Framework 向け)は DPI 非対応のため、高DPIモニターで開くと黄色い情報バーが出て「100% スケーリングで Visual Studio を再起動する」よう促されます。6 画面キャプチャは載せませんが、流れは次のとおりです。

  1. 高DPIモニター上の Visual Studio で、.NET Framework プロジェクトのフォームをデザイナーで開きます。
  2. デザイナーの上部に黄色い情報バーが出て、100% スケーリングでの再起動を促されます。この状態でフォームを保存しないでください。上の diff が発生します。
  3. 情報バーのリンクから Visual Studio を再起動します。VS 全体が DPI 非対応モードで立ち上がるため、VS 自身の表示は少しぼやけますが、これが正常な状態です。
  4. その状態でフォームを編集・保存し、git diffAutoScaleDimensions が変わっていないことを確認します。
  5. 作業が終わったら Visual Studio を通常どおり起動し直して、DPI 非対応モードを解除します。

コマンドラインから直接この状態で起動するなら devenv /noScale です。なお .NET 6 以降のプロジェクトでは、Visual Studio 2022 17.8 以降でプロジェクトファイルに <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware> を設定すると、VS 全体を再起動せずデザイナーのタブだけ DPI 非対応で動かせます(.NET Framework プロジェクトでは使えません)。6 対象が .NET 6 以降なら、3〜5 の運用そのものが不要になるので、こちらを先に検討してください。

チェックの仕組み化としては、「Designer.cs の AutoScaleDimensions が規定値以外になっている差分を CI や pre-commit で弾く」のが手軽で効きます。事故は起きてから直すより、コミットの入口で止めるほうが安上がりです。

6. 崩れの典型パターンと直し方

DPI 認識モードを上げた(または上げようとしている)ときに崩れる箇所は、だいたい次のパターンに収まります。

崩れるもの 原因 直し方
コントロールの重なり・見切れ 固定座標・固定サイズのレイアウト Anchor/Dock、TableLayoutPanel / FlowLayoutPanel へ置き換え。AutoSize の活用
アイコン・画像が小さい/粗い 96 DPI 用ビットマップの直貼り 複数解像度の画像を用意し DPI で切り替え。可能なら大きめ 1 枚から縮小
自前描画(グラフ、図面、帳票プレビュー) Graphics へのピクセル直書き DeviceDpi 基準で座標・線幅・フォントをスケール
DataGridView の行が詰まる RowHeight 等のピクセル指定 AutoSizeRowsMode の利用、または DPI でスケールした値を設定
ToolStrip のアイコンが豆粒 16×16 固定 ImageScalingSize を DPI に応じて設定
特定のサードパーティ/ActiveX コントロールだけ崩れる コントロール自体が DPI 非対応 ベンダーの対応版へ更新。なければそこが対応レベルの上限

レイアウトの置き換えが工数の中心です。逆に言えば、もともと TableLayoutPanel と Dock で組まれた画面は、DPI 認識モードを上げてもほとんど手がかかりません。今後の新規画面は最初からレイアウトパネルで組む、を規約にするだけで将来の負債が減ります。

自前描画のスケーリングは、Control.DeviceDpi(.NET Framework 4.7+ / .NET)を基準に 96 DPI 換算の値を変換するのが基本形です。

public partial class ChartPanel : Panel
{
    // 96 DPI 基準の設計値を現在の DPI へ変換
    private int Scale(int value96) => value96 * DeviceDpi / 96;

    protected override void OnPaint(PaintEventArgs e)
    {
        using var pen = new Pen(Color.Navy, Scale(2));
        e.Graphics.DrawRectangle(pen,
            Scale(16), Scale(16), Scale(320), Scale(120));
    }

    // PMv2 ではモニター間移動で DPI が変わるので再描画を促す
    protected override void OnDpiChangedAfterParent(EventArgs e)
    {
        base.OnDpiChangedAfterParent(e);
        Invalidate();
    }
}

System Aware までなら DPI は起動時に固定なので Scale の導入だけで済みますが、PMv2 ではモニター間移動のたびに DeviceDpi が変わるため、キャッシュしているフォント・画像・レイアウト値を DpiChanged 系イベントで作り直す設計が必要です。5 「自前描画の画面がいくつあるか」は、PMv2 対応の工数見積りにそのまま効いてきます。

サードパーティ製コントロールと ActiveX/OCX は、この改修の上限を決める要素です。コントロール自体が DPI 非対応なら、ホスト側でどう頑張ってもその画面は完全にはなりません。ベンダーの対応状況を確認し、更新が望めない ActiveX については「ActiveX/OCXを維持するか、ラップするか、置き換えるか」の判断表で扱いを決めてから、高DPI対応のゴールを設定してください。

7. 段階的な対応戦略 ── どこまでやるかの判断表

ここまでの内容を踏まえると、対応レベルは 3 段階に整理できます。全アプリを PMv2 にするのが技術的な理想ですが、改修予算とアプリの寿命を考えると、割り切りが正解になる場面は多いです。

  (1) 何もしない(Unaware) (2) System Aware 化 (3) PMv2 完全対応
見え方 全環境でぼやける(崩れない) 主モニターでくっきり。サブモニター・DPI 変更時はぼやける 全モニターでくっきり
主な作業 なし マニフェスト/設定の宣言+AutoScaleMode の点検+全画面の表示確認 (2)+レイアウト総点検+自前描画・画像資産の DPI 対応+DpiChanged 対応
工数感 ゼロ 小〜中(画面数に比例した確認作業が主) 大(崩れの改修が主。サードパーティ製コントロールに上限を握られる)
向くケース 数年で廃止予定。利用者が許容している 固定デスクトップ・単一モニター中心の社内アプリ。第一段階として ノート+外部モニターの混在利用。長寿命の主力アプリ。顧客に配布する製品

判断の軸は 3 つです。アプリの寿命(あと何年使うか)、利用環境(全員が同じ固定デスクトップなら System Aware で実質完結します。ノート PC+外部モニターの組み合わせが多いなら、モニター間移動のたびににじむ System Aware は不満が残ります)、そして改修予算。おすすめの進め方は、まず (2) を全アプリの標準として実施し、その過程で見つかった崩れの量とサードパーティ製コントロールの制約を材料に、(3) へ進むか・どの画面だけ進むかを決めることです。(2) は「宣言+点検」が中心で失敗のリスクが小さく、それでいて利用者の体感は大きく改善します。

テストについても触れておきます。高DPI問題は開発機で再現しないことが多いので、次の環境を意図的に作って確認します。1

  • 異なるスケーリングのモニターを 2 枚つなぎ(例: 150% のノート内蔵+100% の外部)、ウィンドウを行き来させる
  • 主モニターを入れ替えてサインインし直してから起動する(System DPI はサインイン時に固定されるため、再サインインしないと切り替わりません)
  • アプリ起動中にスケーリング設定を変更する
  • リモートデスクトップで高DPIのクライアントから接続する(RDP はクライアント側の DPI が持ち込まれるため、「サーバーのコンソールでは正常なのに RDP だと崩れる」はよくある報告です)

構成を図にすると次のとおりです。実機 1 台+モニター 2 枚が土台で、そこで足りない倍率を仮想マシンで足し、RDP 経由の確認を最後に重ねる、という積み方になります。

仮想マシン ── 足りない倍率を足す実機1台 ── モニター2枚が土台ウィンドウをドラッグして往復させる125%200%内蔵ディスプレイ 150%主モニターにする側外部モニター 100%検証対象のWinFormsアプリリモートデスクトップ高DPIのクライアントから接続

図の左右の往復矢印が、System Aware と PMv2 の差がいちばんはっきり出る操作です。主モニターの入れ替えは PHYS の中で NBEXT の役割を交換し、そのつどサインインし直して確認します。

「125% でだけ崩れる」という報告があったら、その倍率を実際に作って見るのが結局いちばん早い、というのが経験則です。仮想マシンでもスケーリング設定は変えられるので、100 / 125 / 150 / 200% の検証環境を用意しておくと切り分けが速くなります。

8. まとめ

高DPI環境での「ぼやける」は OS の救済措置(DPI 仮想化)、「崩れる」は中途半端な DPI 対応──この対応関係さえ押さえれば、症状から原因と打ち手を逆引きできます。設定方法は世代別に、.NET ならプロジェクトファイルの ApplicationHighDpiMode、.NET Framework 4.7 以降なら app.config(マニフェストの dpiAware と併用しない)、4.6 以下はマニフェストで System Aware まで。あわせて AutoScaleMode.Font の統一と、デザイナーは 100% で開くという運用規約が、地味ですが事故を最も減らします。

そのうえで、どこまでやるかは 7 章の 3 段階から。まず System Aware 化で主モニターをくっきりさせ、利用環境とアプリの寿命が見合うなら PMv2 へ──という 2 段構えが、当社が実際の改修案件でおすすめしている進め方です。UI フレームワーク自体を見直すタイミング(WPF は最初から DPI に強い設計です)に来ているなら、「WinForms / WPF / WinUI をどう選ぶか」もあわせて検討材料にしてください。手元のアプリがどの段階まで直せるか判断に迷う場合は、画面構成とコントロール構成の棚卸しからお手伝いできます。

関連記事

関連する相談領域

合同会社小村ソフトでは、古い WinForms / MFC アプリの高DPI対応(現状調査、対応レベルの判断、レイアウト改修)や、PC 入れ替えに伴う表示不具合の原因調査、UI 刷新の相談を扱っています。

参考リンク

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. DPI スケーリングの背景、各 DPI 認識モードの挙動、DPI 非対応アプリがビットマップ拡大される仕組み、混在 DPI 環境でのテスト観点について。  2 3 4 5

  2. Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED(GDI スケーリング、Windows 10 1809〜)各コンテキストの定義と挙動について。  2 3 4 5 6 7 8 9

  3. Microsoft Learn, Setting the default DPI awareness for a process. マニフェストの dpiAware / dpiAwareness 要素による宣言方法、両者の優先関係、API による宣言が推奨されないことについて。  2

  4. Microsoft Learn, What’s new in Windows Forms .NET 6. ApplicationConfiguration.Initialize によるブートストラップ、プロジェクトファイルの ApplicationHighDpiMode 設定(既定 SystemAware)、.NET 6 での PerMonitorV2 スケーリング改善について。  2 3 4

  5. Microsoft Learn, High DPI support - Windows Forms. .NET Framework 4.7 の高DPI強化の内容、app.config の System.Windows.Forms.ApplicationConfigurationSection(DpiAwareness=PerMonitorV2)、マニフェストでの宣言が app.config を上書きするため非推奨であること、DpiChanged 系イベントと DeviceDpi について。  2 3 4

  6. Microsoft Learn, Fix DPI display issues in Windows Form Designer. WinForms デザイナーが DPI 非対応であること、高DPIモニターで表示される情報バーからの 100% スケーリング再起動、devenv /noScale、.NET 6+ 向け ForceDesignerDPIUnaware について。  2 3

  7. Microsoft サポート, Windows で画面の解像度とレイアウトを変更する. 「スタート > 設定 > システム > ディスプレイ」の「拡大縮小とレイアウト」から「テキスト、アプリ、その他の項目のサイズを変更する」で拡大率を選ぶ手順について。 

  8. Microsoft サポート, 拡大鏡を使用して画面上の項目を見やすくする. Windows ロゴキー + プラス記号で拡大鏡を起動し、画面の一部を拡大表示できることについて。 

  9. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. SetThreadDpiAwarenessContext によるトップレベルウィンドウ単位の DPI 認識モード混在と、GetDpiForWindow 等の DPI 対応 API について。 

  10. Microsoft Learn, SetProcessDpiAwareness function. API によるプロセス既定 DPI 認識の設定と、マニフェストでの宣言が推奨されること、一度設定すると変更できないことについて。 

  11. Microsoft Learn, Automatic form scaling - Windows Forms. AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions による自動スケーリングの動作、Font と Dpi モードの混在が非サポートであることについて。  2

同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。

このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。

この記事は次のサービスページにつながります。近い入口からご覧ください。

よくある質問

この記事のテーマについて、相談時によくある質問をまとめています。

4KモニターでWinFormsアプリがぼやけるのはなぜですか?
バグではなくOSの救済措置(DPI仮想化)です。DPI対応を宣言していないアプリに対して、Windowsは「画面は96 DPIだ」と偽り、アプリが96 DPIのつもりで描いた結果をビットマップとして拡大表示します。このためレイアウトは崩れない代わりににじみます。逆に「くっきりしているのに崩れる」のはDPI対応を宣言したのにレイアウトが追従できていない状態で、原因が正反対です。対処の前にどちらの症状なのかを見分けることが第一歩です。
WinFormsの高DPI対応はどう設定しますか?
世代で方法が分かれます。.NET 6以降ならプロジェクトファイルのApplicationHighDpiModeプロパティ(既定はSystemAware)、.NET Core 3.1/.NET 5ならApplication.SetHighDpiModeをウィンドウ作成前に呼びます。.NET Framework 4.7以降はマニフェストでWindows 10互換を宣言したうえでapp.configのDpiAwareness設定を使いますが、マニフェストのdpiAware記述はapp.configを上書きするため併用しないのが公式推奨です。4.6以下はマニフェストでSystem Awareを宣言するまでが現実的なラインです。
System AwareとPer-Monitor V2はどちらを選ぶべきですか?
2段構えをおすすめします。第一段階はSystem Aware化で、起動時の主モニターDPIに合わせてスケーリングされ、主モニター上ではくっきり表示されます。宣言と点検が中心で失敗リスクが小さく、固定デスクトップ中心の社内アプリなら実質これで完結します。Per-Monitor V2はモニター間移動でも全画面くっきりになりますが、レイアウト総点検、自前描画・画像資産のDPI対応、DpiChangedイベント対応が必要で工数が大きく、サードパーティ製コントロールが非対応ならそこが上限になります。アプリの寿命・利用環境・改修予算で判断します。
デザイナーでフォームを保存したらレイアウトが壊れたのはなぜですか?
150%などの高DPIモニターでVisual StudioのWinFormsデザイナーを開いてフォームを保存すると、Designer.csのAutoScaleDimensionsが150%の値に書き換わり、座標・サイズも1.5倍値で保存されるためです。これを100%環境のメンバーがビルドすると全体が縮んで表示されます。対策はデザイナーを100%(96 DPI)で開くことで、高DPIモニターでは黄色い情報バーから100%スケーリングで再起動できます。.NET 6以降のプロジェクトではForceDesignerDPIUnawareの設定も使えます。AutoScaleDimensionsの想定外の差分をCIやpre-commitで弾く仕組み化も有効です。

著者プロフィール

記事の著者プロフィールページです。

小村 豪

合同会社小村ソフト 代表

Windows ソフト開発、技術相談、不具合調査を中心に、既存資産が残る案件や原因が見えにくい障害調査に強みがあります。

ブログ一覧に戻る