更新履歴(3件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 結論を骨子と系統別の表に整理し、用語の説明を追加しました。あわせて症状を手元で再現・確認する手順(拡大鏡での見分け方を含む)と、マニフェスト追加の操作手順を追加しています。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21589917)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「WPFの高DPI対応 ── 「DPIに強いはず」なのにぼやける・にじむ原因と対処」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21589917 https://staging.comcomponent.com/blog/wpf-high-dpi-guide/
- DOI(最新版)
- 10.5281/zenodo.21589917
- DOI(この版)
- 10.5281/zenodo.21732913
「WPF なら DPI 対応は何もしなくていいはずですよね?」という質問を、高DPIがらみの相談の場でよく受けます。半分は正しくて、半分は違います。実際の症状として持ち込まれるのは、「ノート PC 単体ではきれいなのに、会議室の外部モニターにつなぐと画面全体がにじむ」「文字はくっきりしているのにツールバーのアイコンだけぼやける」「一覧の罫線が場所によって太くなったり消えたりする」「画面に埋め込んだ古い帳票コントロールの部分だけ小さい」──どれも「DPI に強いはず」の WPF アプリで現実に起きるものです。
前日の記事「WinForms の高DPI対応」では、Windows の DPI スケーリングの仕組みと DPI 認識モード(Unaware / System Aware / Per-Monitor V2)、そして 96 DPI 直書き時代の WinForms をどう救うかを整理しました。この記事はその WPF 版です。DPI 仮想化や DPI 認識モードの一般論は WinForms 記事に譲り、ここでは「最初から DPI 対応しているはずの WPF で、なぜ・どこに問題が残るのか」という WPF 固有の話に集中します。症状の切り分け、Per-Monitor DPI 対応の宣言方法(.NET Framework 4.6.2 / .NET それぞれ)、単線とビットマップのにじみ対策、WindowsFormsHost 混在の罠、そしてどこまでやるかの判断表まで、実務で使う順に並べます。
この記事で使う用語
DPI 認識モードの一般論は WinForms 記事に譲りますが、本文で断りなく使う語だけ先に押さえておきます。
| 用語 | 意味 |
|---|---|
| DIP | Device Independent Pixel(デバイス非依存単位)。1/96 インチを 1 とする WPF のレイアウト単位。XAML の Width="120" の 120 はこれです |
| HWND | Windows がウィンドウ1枚ごとに割り当てる識別子(ウィンドウハンドル)。WPF はトップレベルのウィンドウにだけ HWND を持ち、中のボタンや文字は WPF が自前で描きます。HWND を持ち込む要素だけが WPF の描画の「外」になります(7 章) |
| GDI | Windows 従来の 2D 描画 API。ピクセル単位で描く仕組みで、Per-Monitor DPI スケーリングのサポートは公式表で「なし」とされています1 |
| サブピクセル配置 | 要素の境界が物理ピクセルの整数位置に乗らない状態。半端な位置のエッジはアンチエイリアス(境界を中間色でぼかす処理)で描かれ、にじんで見えます(5.1 節) |
| DPI 仮想化 | Unaware / System Aware のアプリに対し、OS がウィンドウの描画結果をビットマップとして拡大・縮小する救済措置。レイアウトは崩れない代わりに全体がにじみます1 |
| マニフェスト | 実行ファイルに埋め込む XML。WPF で DPI 認識モードを宣言する唯一の入り口です(4 章) |
1. まず結論
まず、この記事全体の骨子となる 3 点です。
- WPF は「起動時の DPI」には最初から正しく追従します。 レイアウト単位が DIP で、描画系がシステム DPI に合わせて自動スケールするため、既定で System DPI Aware です。WinForms のような AutoScaleMode の設定・点検は不要です。2
- それでも残る問題は 4 系統に分かれます。(a) DPI の違うモニターへ移したときの全体のにじみ、(b) ビットマップ画像・アイコンのぼやけ、(c) 単線・罫線のにじみ、(d) WindowsFormsHost / WebBrowser などの混在コンテンツ。原因も直し方も別物なので、まず 3 章の表で切り分けてください。
- WPF 側の作業は「宣言 1 行 + 残りの点検」です。レイアウトの追従はフレームワークがやるので、本当の改修対象はピクセルを直接扱っているコードと、ビットマップ資産と、混在コンテンツだけです(8 章)。
4 系統それぞれの結論は次のとおりです。
| 系統 | 結論 | 詳細 |
|---|---|---|
| (a) 全体のにじみ | 根治は Per-Monitor DPI 対応。.NET Framework 4.6.2 以降の WPF はこれをフレームワークとしてサポートしており、マニフェストで宣言すればウィンドウの再スケーリング自体は自動で行われる34。.NET(Core 3.1〜.NET 8)の WPF も既定は System Aware のままで、WinForms の ApplicationHighDpiMode / SetHighDpiMode に当たる仕組みは WPF にはない |
4 章 |
| (b) ビットマップのぼやけ | Path / Geometry などのベクター資産を第一候補にする。ビットマップしかない資産は複数解像度を用意して DPI で切り替える。WPF の既定の補間(Linear)は、非整数倍のスケールでアイコンのような小さい画像をいちばんぼやけさせる5 | 5.3 節 |
| (c) 単線のにじみ | DPI 以前のサブピクセル配置の問題。ルート要素への UseLayoutRounding="True" が第一手で、SnapsToDevicePixels は描画時スナップという別の道具。どちらも既定は無効67 |
5.1 節 |
| (d) 混在コンテンツ | Per-Monitor 対応の上限を決める要素。特に ElementHost / HwndSource に「載せられた」WPF の Per-Monitor 動作は公式にサポート外と明言されている4 | 7 章 |
なお、DPI 認識モードの一般論(DPI 仮想化、System Aware と Per-Monitor V2 の違い、exe プロパティからの利用者側上書き)と混在 DPI テスト環境の作り方は、WinForms 記事の 2〜3 章・7 章と共通なので本記事では繰り返しません。
この記事の知識マップ
WPFはレイアウト単位がDIPで既定でSystem DPI Awareのため起動時の主モニターDPIには自動で追従するが、System Awareのままだと異なるDPIのモニターへ移した際にOSのDPI仮想化が働き画面全体がにじむ。これを根治するのがPer-Monitor DPI対応で、アプリケーションマニフェストへの宣言だけでウィンドウの再スケーリングがフレームワークにより自動化される。残る改修対象は、UseLayoutRoundingで防ぐサブピクセル配置のにじみ、ベクター資産や複数解像度の切り替えで防ぐビットマップのぼやけ、そしてWriteableBitmapのようなピクセルバッファを自前で扱うコードのOnDpiChangedへの追従に絞られる。WindowsFormsHostやWebBrowserコントロールのようにHWNDを持ち込む混在コンテンツは、Per-Monitor対応の上限を決める要素として別枠で扱う必要がある。
flowchart LR
accTitle: WPFの高DPI対応の知識マップ
accDescr: WPFがDIPとSystem DPI Awareによって起動時のDPIには自動追従する一方、異なるDPIのモニター間ではOSのDPI仮想化がにじみを引き起こし、Per-Monitor DPI対応の宣言がそれを防ぐこと、UseLayoutRoundingやベクター資産がそれぞれサブピクセルのにじみとビットマップのぼやけを防ぐこと、WindowsFormsHostやWebBrowserなどHWNDを持つ混在コンテンツがPer-Monitor対応の上限になることを示す図。
wpf["WPF"]
per_monitor_dpi_awareness["Per-Monitor DPI対応"]
dip["DIP(デバイス非依存単位)"]
system_dpi_aware["System DPI Aware"]
cross_monitor_blur["モニター間移動時の全体のにじみ"]
dpi_virtualization["DPI仮想化"]
app_manifest["アプリケーションマニフェスト"]
dotnet_framework[".NET Framework"]
windowsformshost["WindowsFormsHost"]
hosted_content_scaling_limitation["ホストしたコンテンツのスケーリング制限"]
windows_forms["Windows Forms"]
hwnd["HWND(ウィンドウハンドル)"]
webview2["Microsoft Edge WebView2"]
webbrowser_control["WebBrowserコントロール"]
bitmap_blur["ビットマップ画像のぼやけ"]
vector_graphics_asset["ベクター画像資産"]
bitmap_scaling_mode["BitmapScalingMode"]
ondpichanged["OnDpiChanged(DPI変更への追従)"]
pixel_buffer_code["ピクセルバッファを自前で扱うコード"]
subpixel_rendering["サブピクセル配置"]
use_layout_rounding["UseLayoutRounding"]
snaps_to_device_pixels["SnapsToDevicePixels"]
textformattingmode_display["TextFormattingMode(Ideal/Display)"]
wpf -->|"利用する"| dip
wpf -->|"実装を担う"| system_dpi_aware
system_dpi_aware -->|"原因になり得る"| cross_monitor_blur
dpi_virtualization -->|"原因になり得る"| cross_monitor_blur
per_monitor_dpi_awareness -->|"防止する"| cross_monitor_blur
per_monitor_dpi_awareness -->|"前提とする"| app_manifest
per_monitor_dpi_awareness -.->|"前提とする"| dotnet_framework
windowsformshost -.->|"原因になり得る"| hosted_content_scaling_limitation
wpf -.->|"利用する"| windowsformshost
windowsformshost -->|"利用する"| windows_forms
windowsformshost -->|"前提とする"| hwnd
hwnd -.->|"原因になり得る"| hosted_content_scaling_limitation
webview2 -.->|"の後継"| webbrowser_control
webbrowser_control -.->|"原因になり得る"| hosted_content_scaling_limitation
dip -->|"原因になり得る"| bitmap_blur
vector_graphics_asset -->|"防止する"| bitmap_blur
bitmap_scaling_mode -->|"軽減する"| bitmap_blur
per_monitor_dpi_awareness -.->|"前提とする"| ondpichanged
pixel_buffer_code -.->|"前提とする"| ondpichanged
dip -->|"原因になり得る"| subpixel_rendering
use_layout_rounding -->|"軽減する"| subpixel_rendering
snaps_to_device_pixels -->|"軽減する"| subpixel_rendering
use_layout_rounding -->|"より先に行うべき"| snaps_to_device_pixels
textformattingmode_display -.->|"用いるのは非推奨"| per_monitor_dpi_awareness
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全24件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. WPFはなぜ「DPIに強い」のか ── DIPとSystem DPI Aware
WinForms の高DPI問題の根っこは、座標もサイズも物理ピクセル直書きで、96 DPI 前提のレイアウトを実行時に換算し直す仕組み(AutoScale)を後付けで持っている点にありました。WPF は出発点が違います。XAML に書く Width="120" の 120 は物理ピクセルではなく、1/96 インチを 1 とするデバイス非依存単位(DIP)です。レイアウトは全部この単位で計算され、描画時にシステム DPI に応じた倍率(150% なら 1.5 倍)へ変換されます。文字もベクターフォントとして描画されるため、拡大してもビットマップ的な劣化がありません。この仕組みにより、WPF アプリは何も宣言しなくても System DPI Aware として動作します。2
WinForms との違いを一覧にしておきます。
| WinForms | WPF | |
|---|---|---|
| 座標・サイズの単位 | 物理ピクセル | DIP(1/96 インチ) |
| 何も宣言しない場合 | Unaware(OS のビットマップ拡大でぼやける) | System Aware(主モニターではくっきり) |
| 起動時 DPI への追従 | AutoScaleMode によるフォーム単位の再計算。設定と点検が必要 | 描画系が自動スケール。設定不要 |
| 高DPIで崩れる典型 | 固定座標レイアウトの重なり・見切れ | レイアウトは崩れず、にじみ・ぼやけとして現れる |
| デザイナー起因の事故 | AutoScaleDimensions の書き換わり(定番) | 原則なし(XAML は DIP のまま保存される) |
この表のとおり、WinForms で工数の中心だった「レイアウトが崩れるのを直す」「デザイナー事故を防ぐ」という仕事が、WPF ではほぼ発生しません。「WPF は DPI に強い」は正しい認識です。
ただし、自動なのは System Aware の範囲までです。System Aware とは「サインイン時の主モニターの DPI に合わせる」というモードであって、モニターごとに DPI が違う環境への追従(Per-Monitor)は含みません。また、DIP で拡大されるのはあくまで WPF が自分で描くもの(テキスト、図形、コントロール)で、ビットマップ画像は拡大すればぼやけますし、HWND を持ち込む混在コンテンツは WPF のスケーリングの外側にいます。「DPI に強いはずなのに」という相談は、ほぼこの残りの部分で起きています。
3. それでも起きる問題 ── 症状から原因を切り分ける
相談を受けたとき、当社が最初に使う切り分け表です。WPF では症状と原因の対応が WinForms よりさらにきれいに分かれるので、この表だけで原因がほぼ特定できます。
| 症状 | 原因 | 対処 |
|---|---|---|
| DPI の違うモニターへ動かすと全体が一様ににじむ。元のモニターへ戻すと直る | System Aware のまま。OS がウィンドウ全体をビットマップ拡大している | 4 章(Per-Monitor 化) |
| スケーリング設定を変えた直後や、高DPIクライアントからの RDP 接続でにじむ | 同上(System DPI はサインイン時に固定されるため) | 4 章 |
| 文字はくっきりなのにアイコン・画像だけぼやける、または小さい | ビットマップ資産が補間拡大されている | 5.3 節 |
| 1px の罫線・枠線が場所によって太さが違う、うっすらにじむ | サブピクセル配置とアンチエイリアス | 5.1 節 |
| 100%(96 DPI)のモニターで小さい文字がにじんで見える | 既定のテキスト整形(Ideal)のアンチエイリアス | 5.2 節 |
| 自前で作る画像(WriteableBitmap、RenderTargetBitmap 等)がぼやける | ピクセルサイズを 96 DPI 前提で計算している | 6 章 |
| ウィンドウ位置・マウス座標・スクリーンショットの座標がずれる | DIP と物理ピクセルの混同 | 6 章 |
| WindowsFormsHost / WebBrowser / 一部サードパーティ製コントロールの中だけ小さい・粗い・崩れる | 混在コンテンツ(WPF のスケーリング対象外) | 7 章 |
1 行目の「全体が一様ににじむ」について補足します。これは WinForms 記事の 2 章で書いた DPI 仮想化(OS のビットマップ拡大)が System Aware アプリに対して働いている状態で、アプリが壊れているわけではありません。System Aware のアプリは「サインイン時の主モニターの DPI」で描画し、それ以外の DPI のモニターでは OS が拡大・縮小して帳尻を合わせます。レイアウトは崩れない代わりににじむ、という OS の救済措置です。2 直すとは、この救済を断って「モニターごとの DPI に自分で追従します」と宣言すること、つまり Per-Monitor 化です。
逆に 3 行目以降の症状は、System Aware のままでも直せます。「マルチモニター運用はないが、150% の画面でアイコンと罫線が汚い」という相談なら、4 章を飛ばして 5 章から着手して構いません。
3.1 手元で症状を再現・確認する手順
この記事には画面キャプチャを掲載していません。にじみ・ぼやけは静止画で見せるより、手元の実機で再現させたほうが確実に分かります。次の手順で、上の表の症状を自分の目で確認してください。混在 DPI のテスト環境そのものの作り方は WinForms 記事の 7 章にありますが、モニター 1 枚でもここまでは確認できます。
- 拡大率を変える。 スタート > 設定 > システム > ディスプレイ >「拡大縮小とレイアウト」>「テキスト、アプリ、その他の項目のサイズを変更する」で 100% / 125% / 150% を切り替えます。8 効果がいちばん出るのは 125% と 150% です(非整数倍だから、というのが 4.1 節の話です)。
- アプリを起動し直す。 WPF は既定で System Aware なので、起動中のアプリは新しい拡大率に追従しません。変更後は必ず起動し直してください。それでも直らない場合はサインアウト・サインインまで行います(System DPI はサインイン時に固定されるためです)。
- 拡大鏡で細部を見る。 Windows ロゴキー +
+(プラス)で拡大鏡が起動します。9 400% 程度まで上げて、次の 3 か所を順に見ます。- 罫線・枠線 ── 線の片側または両側に薄いグレーが 1px はみ出していれば、5.1 節のサブピクセル配置です。同じ
1指定の線なのに、行によって濃さや太さが違って見えるのが決定的なサインです。 - アイコン・画像 ── 輪郭が二重ににじみ、本来 1px であるべき線が 2px 分の中間色になっていれば、5.3 節のビットマップ補間拡大です。同じ画面の文字がくっきりしていることが切り分けの決め手になります(文字はベクターなのでぼやけません)。
- 画面全体 ── 文字も罫線もアイコンも等しくぼやけていれば、個別の描画の問題ではなく OS の DPI 仮想化です。この場合は 4 章へ進みます。
- 罫線・枠線 ── 線の片側または両側に薄いグレーが 1px はみ出していれば、5.1 節のサブピクセル配置です。同じ
- モニター間を移動させる(2 枚以上ある場合)。 拡大率の違うモニターへウィンドウをドラッグします。移した先でにじみ、元へ戻すと直るなら、表の 1 行目です。逆に、移動しても見え方が変わらないなら (a) は起きていません。
- 実行中に拡大率を変える。 アプリを起動したまま 1. の設定を変更します。System Aware のアプリは、その場でにじみます。
この 1〜5 は、Per-Monitor 化した後の点検手順としてもそのまま使えます。公式の推奨テスト項目も、モニター間の移動・異なる DPI での起動・実行中の拡大率変更・主モニターを入れ替えてのサインアウト/サインインの 4 点です。1
4. マルチモニターのにじみ ── Per-Monitor DPI対応
4.1 System Awareの限界
System DPI はサインイン時に主モニターの DPI で固定されます。150% のノート内蔵ディスプレイが主モニターなら、WPF は全ウィンドウを 1.5 倍で描き、100% の外部モニターへ移すと OS が 2/3 に縮小表示します。この OS スケーリングは非整数倍のときに特にぼやけて見えます。2 200% と 100% の 2 倍差よりも、125% と 150% のような中途半端な組み合わせのほうが見た目の劣化が大きい、というのは検証してみると実感できます。
つまり System Aware の WPF で困るのは、DPI の違うモニターを行き来する使い方と、サインイン後にスケーリング設定が変わる状況(ドッキングステーションの抜き差しで主モニターが変わる、RDP でクライアント側の DPI が持ち込まれる、など)に限られます。全員が単一モニターの固定デスクトップで使う社内アプリなら、System Aware のままで実用上完結します。この判断は 8 章の表に戻ってきます。
4.2 .NET Framework 4.6.2以降での宣言方法
WPF の Per-Monitor DPI 対応は .NET Framework 4.6.2 で入りました。3 それ以前は、OS へ Per-Monitor を宣言しても WPF 側が DPI 変更に追従してくれず、ウィンドウの再スケーリングを全部自前で書く必要がありました(Windows 8.1 時代のサンプルがネイティブヘルパー DLL を使う大掛かりな作りだったのはこのためです)。4.6.2 以降は WPF が WM_DPICHANGED を処理し、ウィンドウサイズの調整・再レイアウト・再描画まで自動で行います。
要件は 2 つ、Windows 10 Anniversary Update(1607)以降の OS と、.NET Framework 4.6.2 以降をターゲットにしたビルドです。4 宣言はアプリケーションマニフェストに書きます。
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings>
<!-- .NET Framework 4.6.2〜4.7.2 は PerMonitor 単独で宣言する(開発者ガイドと同じ)。
WPF の PerMonitorV2 サポートは .NET Framework 4.8 以降のため、
4.8+ / .NET では「PerMonitorV2, PerMonitor」と V2 を先頭に置く -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings"
>PerMonitor</dpiAwareness>
<!-- dpiAwareness を認識しない古い OS 向け(System Aware にフォールバック) -->
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
この 2 段構えには意味があります。dpiAwareness 要素は Windows 10 1607 以降で認識され、カンマ区切りで並べた値のうち最初に認識できたものが使われます。10 dpiAwareness 要素自体を知らない古い OS では dpiAware の宣言(System Aware)にフォールバックする、という寸法です。
ここで PerMonitor と PerMonitorV2 をターゲットフレームワークで使い分ける点に注意してください。WPF が PerMonitorV2(と Mixed-Mode DPI)を正式にサポートするのは .NET Framework 4.8 以降で11、4.6.2 の開発者ガイドのサンプルも PerMonitor 単独の宣言です。4 4.6.2〜4.7.2 ターゲットで V2 を先頭に書くと、Windows 10 1703 以降の OS はフレームワークが対応していない V2 モードを選んでしまい、特に WindowsFormsHost などの混在コンテンツ周りで想定外の動きになります。4.8 以降(および .NET の WPF)に上げてから PerMonitorV2, PerMonitor と V2 を先頭に置き、タイトルバー・スクロールバーなど非クライアント領域のスケーリングを OS に任せる(WinForms 記事の 3 章参照)のが当社のおすすめです。
1 点、ターゲットフレームワークの罠があります。実行環境の .NET Framework が 4.6.2 以降でも、プロジェクトのターゲットが 4.6.1 以前のままだと Per-Monitor 追従は既定で無効です。この場合は app.config の AppContext スイッチで明示的に有効化します。4
<configuration>
<runtime>
<!-- 二重否定に注意: 「DPI変更でスケールしない」を false にする=有効化 -->
<AppContextSwitchOverrides value="Switch.System.Windows.DoNotScaleForDpiChanges=false"/>
</runtime>
</configuration>
「マニフェストを書いたのに動かない」という問い合わせの原因は、経験上ほぼこのターゲットフレームワークか、マニフェストがビルドに含まれていない(プロジェクト設定で既定マニフェストのままになっている)かのどちらかです。
4.3 .NET (Core 3.1〜.NET 8) での宣言方法
.NET 上の WPF も、マニフェストなしの既定は System Aware のままです。WinForms がプロジェクトファイルの ApplicationHighDpiMode や Application.SetHighDpiMode という専用の入り口を持つのに対し、WPF にはコードやプロジェクト設定から DPI 認識モードを切り替える公式の仕組みがありません。宣言方法は .NET Framework と同じです。
マニフェストの追加手順も画面キャプチャなしで書けるので、メニュー名をそのまま並べます。
- ソリューションエクスプローラーでプロジェクトを右クリックし、「追加」>「新しい項目」(ショートカットは Ctrl+Shift+A)を選びます。
- 一覧から「アプリケーション マニフェスト ファイル」テンプレートを選び、既定の名前
app.manifestのまま追加します。 - 追加された
app.manifestを開き、4.2 節と同じdpiAwareness宣言を<asmv3:application>要素として書きます。テンプレートにはrequestedExecutionLevel(UAC の要求レベル)などが最初から入っているので、それらは消さずに追記してください。 - プロジェクトファイルの
<PropertyGroup>に<ApplicationManifest>app.manifest</ApplicationManifest>を書いて参照させます(SDK スタイルのプロジェクトでは、app.manifestという名前で追加すれば自動的に認識されることもありますが、明示しておくほうが確実です)。
.NET Framework のプロジェクトの場合は 4. の代わりに、プロジェクトのプロパティ >「アプリケーション」タブ >「マニフェスト」のドロップダウンで追加したマニフェストを選びます。
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWPF>true</UseWPF>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
</Project>
ランタイムが新しいので 4.2 節の AppContext スイッチは不要です。「.NET に移行すれば高DPIも自動で解決」とはならない点だけ注意してください。移行で楽になるのは WinForms 側の話で(WinForms 記事 4.1 節)、WPF は .NET Framework 4.6.2 の時点で今と同じ水準に達しています。
宣言後にやることが残っているのは WinForms と同じですが、中身が違います。WinForms ではレイアウト崩れの改修が主戦場でした。WPF ではレイアウトはフレームワークが追従してくれるので、残るのは全画面の表示点検と、ビットマップ資産の DPI 切替(5.3 節)、ピクセルを直接扱うコードの追従(6 章)、混在コンテンツの確認(7 章)です。画面数が同じなら、Per-Monitor 化の総工数は WinForms より一回り小さく済むのが通例です。
5. 描画のにじみ・ぼやけ ── 単線・文字・ビットマップ
この章の内容は Per-Monitor 化と独立です。System Aware のままでも効くので、マルチモニター運用のないアプリでも適用する価値があります。
5.1 単線・罫線のにじみ ── UseLayoutRoundingとSnapsToDevicePixels
DIP でレイアウトするということは、要素の境界が物理ピクセルの整数位置に乗るとは限らないということです。125% なら 1 DIP = 1.25px なので、幅 1 DIP の罫線は物理ピクセル 1.25 個分に相当し、ピクセルの中間に落ちたエッジはアンチエイリアスで半透明に描かれます。結果が「線がにじむ」「同じ 1px 指定なのに行によって太さが違って見える」です。96 DPI でも、Grid の星付きサイズ(*)の割り算や中央揃えの余白計算で 0.5px に乗れば同じことが起きます。これは WPF のサブピクセル描画の仕様であって、DPI 問題というより「DPI が上がると露見しやすくなる」問題です。6
第一手はレイアウト丸めです。ルート要素に 1 行足します。
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
UseLayoutRounding="True">
UseLayoutRounding はレイアウトパスで非整数のピクセル値を丸める仕組みで、既定は無効、ルートに設定すればビジュアルツリー全体へ伝播します。6 似た名前の SnapsToDevicePixels は役割が違い、レイアウトではなく描画時にエッジをピクセル境界へスナップします。こちらも既定は false で、設定はサブツリーへ継承されます。96 DPI を超える環境で単線周辺のアンチエイリアスによるにじみを抑える用途が公式ドキュメントにも挙げられています。7
| UseLayoutRounding | SnapsToDevicePixels | |
|---|---|---|
| 働くタイミング | レイアウトパス(Measure / Arrange の結果を整数ピクセルへ丸める) | 描画時(エッジをピクセル境界へスナップ) |
| 既定値 | false | false |
| 適用範囲 | ルートに設定すれば子孫へ伝播 | 同じく子孫へ継承 |
| 主な効き先 | レイアウト全般。要素サイズ・位置の 0.5px ずれ、それに由来する線・境界のにじみ | Border・単線など個別要素の残ったにじみ |
| 使い方の目安 | まずこれをルート Window へ。新規アプリは全ウィンドウ共通スタイルで | UseLayoutRounding で残った箇所にピンポイントで |
迷ったら「ルートに UseLayoutRounding="True"、それでも残る箇所に SnapsToDevicePixels="True"」の順です。丸めの副作用として、星付きサイズで等分していた列幅が 1px 単位で不均等になることがありますが、業務アプリの画面で問題になった経験はほとんどありません。なお DrawingContext で自前描画しているコードのにじみには GuidelineSet という低レベルの手段もありますが、まず描画座標を丸めるだけで大半は解決します。
5.2 文字のにじみ ── TextFormattingMode
「WPF は文字が薄い・にじむ」という昔からの不満は、DPI というよりテキスト整形モードの話です。WPF のテキスト整形には、フォント本来の理想メトリクスで配置する Ideal(既定)と、GDI 互換メトリクスで配置する Display の 2 モードがあります。12 Ideal は文字間隔が美しくスケーリングにも素直ですが、96 DPI(100%)で小さめの文字を描くとアンチエイリアスでにじんで見えることがあります。TextOptions.TextFormattingMode="Display" をルートに設定すると WinForms 的なくっきり感になります。
ただし高DPI環境では話が逆になります。Display は 96 DPI のピクセル格子に合わせる整形なので、スケーリング下ではかえって品質が落ちることがあります。100% 環境の利用者が多いなら Display、125% 以上が主流になった今の標準環境では既定の Ideal のまま、が実務的な割り切りです。100% のときだけ Display に切り替える実装も可能ですが、そこまでやる価値がある画面(テキストエディター的な画面)は限られます。
5.3 画像・アイコンのぼやけ ── ベクター優先と複数解像度
WPF は Image に指定したビットマップも DIP で配置し、DPI に応じて拡大します。16×16px のアイコンは 150% 環境では 24×24px に補間拡大され、既定の補間アルゴリズム(Linear)ではっきりぼやけます。5 「文字はきれいなのにアイコンだけ眠い」という WPF アプリの見た目は、ほぼ全部これです。
対策は優先順で 3 つあります。
- ベクター資産にする(第一候補)。
Path/Geometry/DrawingImageで持つアイコンはどの DPI でもくっきり描画され、切替コードも不要です。デザインツールや SVG からの XAML 変換ツールも充実しています。新規アプリのアイコンは最初からベクターで持つ、を規約にする価値があります。Segoe MDL2 Assets などのアイコンフォントも同じ効果です。 - 複数解像度のビットマップを DPI で切り替える。写真やスクリーンショットなどベクター化できない資産は、96 / 120 / 144 / 192 DPI 向け(16 / 20 / 24 / 32px など)を用意し、現在の DPI に応じて選びます。公式の開発者ガイドも、ぼやけ対策として DPI ごとの資産切替を推奨しています。2
RenderOptions.BitmapScalingModeで補間を選ぶ。資産を増やせない場合の緩和策です。200% のような整数倍ならNearestNeighborでドットのまま拡大するほうがくっきりし、大きい画像の縮小にはHighQuality(Fant)が向きます。5
2 の切替は、Per-Monitor 対応済みなら OnDpiChanged(.NET Framework 4.6.2 以降で利用可)で行います。
public partial class MainWindow : Window
{
protected override void OnDpiChanged(DpiScale oldDpi, DpiScale newDpi)
{
base.OnDpiChanged(oldDpi, newDpi);
// モニター間移動やスケーリング変更のたびに呼ばれる
AppIcon.Source = IconAssets.SelectFor(newDpi.DpiScaleX);
}
}
public static class IconAssets
{
// 16 / 24 / 32 px で用意した資産から拡大率に合うものを選ぶ
public static BitmapImage SelectFor(double scale) => scale switch
{
<= 1.0 => Load("icon16.png"),
<= 1.5 => Load("icon24.png"),
_ => Load("icon32.png"),
};
private static BitmapImage Load(string name) =>
new(new Uri($"pack://application:,,,/Assets/{name}"));
}
System Aware のままなら DPI は起動後変わらないので、起動時(Loaded)に VisualTreeHelper.GetDpi(this) で一度選ぶだけで済みます。切替コードを書く前に、「そもそもこの資産はベクターにできないか」を毎回問い直すのが、結局いちばん保守が楽です。
6. ピクセルを扱うコードとDPI変更への追従
WPF の自動スケーリングの傘の外に出るのが、物理ピクセルを自分で数えるコードです。典型は次の 3 つで、どれも「開発機(100%)では完璧、客先の 150% でだけぼやける・ずれる」という嫌な出方をします。
1 つめは WriteableBitmap / RenderTargetBitmap などピクセルバッファを自前で作るコードです。ピクセル数を DIP のサイズと同じ値で作ると、150% 環境では 1.5 倍に補間拡大されてにじみます。現在の DPI は VisualTreeHelper.GetDpi が返す DpiScale 構造体で取れるので、ピクセル数は物理ピクセルで、DPI はバッファに正しく焼き込みます。
// imageHost: 描画結果を表示する要素(ActualWidth/Height は DIP)
DpiScale dpi = VisualTreeHelper.GetDpi(imageHost);
int pixelWidth = (int)Math.Ceiling(imageHost.ActualWidth * dpi.DpiScaleX);
int pixelHeight = (int)Math.Ceiling(imageHost.ActualHeight * dpi.DpiScaleY);
// 96,96 固定で作らず、実際の DPI を渡す(1 物理ピクセル = 1 バッファピクセルになる)
var bitmap = new WriteableBitmap(
pixelWidth, pixelHeight,
dpi.PixelsPerInchX, dpi.PixelsPerInchY,
PixelFormats.Bgra32, null);
2 つめはスクリーン座標や Win32 相互運用です。PointToScreen が返す座標、WM_MOUSEMOVE やフックで受け取る座標、MoveWindow に渡す座標はすべて物理ピクセルで、XAML 側の DIP と混ぜると 125% 環境で 1.25 倍ずれます。変換は CompositionTarget の行列を使います。
var source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget is { } target)
{
// DIP → 物理ピクセル
Point device = target.TransformToDevice.Transform(new Point(x, y));
// 物理ピクセル → DIP
Point dip = target.TransformFromDevice.Transform(devicePoint);
}
3 つめはキャッシュです。DPI を織り込んで作ったビットマップ・レイアウト値・フォーマット済みテキストをキャッシュしている場合、Per-Monitor 化するとモニター間移動のたびに陳腐化します。5.3 節と同じく OnDpiChanged(またはウィンドウの DpiChanged イベント)で作り直してください。ここでも System Aware のままなら DPI は起動時に固定なので、追従コードは不要です。「Per-Monitor 化のコスト=ピクセルを扱っているコードの数」と考えて見積もると、実態とよく合います。
なお、こうした再生成処理を非同期でやる場合の UI スレッドの扱いは「WPF/WinForms の async と UI スレッド」で整理したとおりです。
7. 混在コンテンツの罠 ── WindowsFormsHost・WebBrowser・サードパーティ
WPF の自動スケーリングが及ぶのは、WPF が自分で描くものだけです。HWND を持ち込む要素は WPF の描画の「外」にあるため、DPI の扱いが一段ややこしくなります。Windows の公式資料でも、「WPF に載せた他フレームワーク、他フレームワークに載せた WPF は自動ではスケールしない」と明記されています。1
| 混在の形 | 何が起きるか | 現実的な扱い |
|---|---|---|
| WindowsFormsHost(WPF に WinForms を載せる) | DIP と物理ピクセルの 2 座標系をホストが変換するが、中身のスケーリングは WinForms コントロールが対応できる範囲まで13 | 中身の WinForms 側の DPI 対応(AutoScaleMode 等)を WinForms 記事の基準で点検。Per-Monitor 追従は期待しない |
| WebBrowser コントロール(IE エンジン) | 別 HWND のネイティブコンテンツ。ズーム率がアプリのスケーリングと一致しないことがある | レガシー扱い。WebView2 への移行を検討する際の材料に加える |
| ElementHost / HwndSource(WinForms や Win32 に WPF を載せる) | Per-Monitor シナリオは公式サポート外4 | ホスト側アプリの DPI 認識モードに合わせて System Aware までで設計する |
| サードパーティ製コントロール | 製品ごとに対応水準がばらばら。Per-Monitor 未対応の製品はその画面の上限になる | ベンダーの対応状況・対応バージョンを棚卸ししてから Per-Monitor 化を判断 |
実務でいちばん多いのは 1 行目です。「WPF に移行したが、帳票プレビューやグラフだけ古い WinForms / ActiveX コントロールを WindowsFormsHost で載せている」という構成のアプリを Per-Monitor 化すると、WPF 部分は完璧に追従するのに、ホストの中だけが移動後の DPI に追従せず、小さいまま・粗いままになります。Win32 レベルには混在 DPI をホストする仕組み(SetThreadDpiHostingBehavior)もありますが、WPF から気軽に使えるものではなく、当社では「混在部分が Per-Monitor 対応の上限を決める」と割り切って、先に混在コンテンツの一覧を作ることから始めます。混在をなくす方向の判断材料は「WinForms / WPF / WinUI をどう選ぶか」と「ActiveX/OCX を維持するか、ラップするか、置き換えるか」にまとめてあります。
8. どこまでやるか ── 判断表と段階的な進め方
WPF の場合、選択肢は実質 2 つです(WinForms のような「何もしない=Unaware」は、宣言しなくても System Aware なので存在しません)。
| System Aware のまま | Per-Monitor 対応 | |
|---|---|---|
| 見え方 | 主モニターではくっきり。DPI の違うモニター・スケーリング変更後・RDP ではにじむ | 全モニターでくっきり |
| 宣言の作業 | 不要(既定) | マニフェスト追加のみ(4 章) |
| 宣言後の残作業 | ── | 全画面の表示点検+ビットマップ資産の DPI 切替(5.3 節)+ピクセル系コードの DpiChanged 対応(6 章)+混在コンテンツの確認(7 章) |
| 上限を握る要素 | ── | WindowsFormsHost・WebBrowser・サードパーティ製コントロール |
| 向くケース | 単一モニター・固定デスクトップ中心の社内アプリ。混在コンテンツが多いアプリ | ノート+外部モニターの混在利用、長寿命の主力アプリ、顧客に配布する製品 |
判断軸は WinForms 記事 7 章と同じ(アプリの寿命・利用環境・改修予算)ですが、WPF は Per-Monitor 化の限界コストが小さい点が違います。宣言は 1 ファイル、レイアウトはフレームワークが追従、残作業はピクセル系コードと資産の数に比例。混在コンテンツが少ないアプリなら、Per-Monitor まで行く費用対効果は WinForms よりはっきり高いです。当社が実際の案件でおすすめしている進め方は次の 3 段階です。
- System Aware のままの品質改善: ルートへの
UseLayoutRounding、アイコンのベクター化・複数解像度化、WriteableBitmap系の DPI 修正。マルチモニターのにじみ以外はここで全部解消し、Per-Monitor 化しない判断をした場合もそのまま資産になります。 - Per-Monitor 宣言と点検: マニフェストを追加し、混在 DPI 環境で全画面を回して問題箇所を洗い出します。ここで見つかる問題は 5〜7 章のどれかに分類できるはずです。
- DPI 変更への追従実装:
OnDpiChangedでの資産切替・キャッシュ再生成。混在コンテンツはこの段階で「どこまで許容するか」を決めます。
テスト環境は WinForms 記事 7 章に書いた構成(スケーリングの違うモニター 2 枚、主モニター入替+再サインイン、実行中のスケーリング変更、高DPIクライアントからの RDP)がそのまま使えます。1 WPF で重点的に見るべきは、非整数倍(125% / 150%)での罫線・アイコン・自前描画と、モニター間をまたいでウィンドウをドラッグしたときの挙動です。利用環境の分布(何 % のユーザーがマルチモニターか)を先に把握しておくと投資判断がぶれません。この観点は「Windows アプリ UX 設計」でも書きました。
9. まとめ
WPF は DIP と自動スケーリングにより最初から System DPI Aware で、WinForms の主戦場だった「レイアウト崩れとの戦い」はほぼ存在しません。それでも残る問題は 4 系統──マルチモニターのにじみ(Per-Monitor 未対応)、ビットマップのぼやけ、単線のにじみ、混在コンテンツ──で、それぞれ対処が確立しています。
- マルチモニターのにじみは、.NET Framework 4.6.2 以降 / .NET の WPF ならマニフェスト宣言だけで Per-Monitor 追従が自動で手に入る
- 単線・罫線はルートの
UseLayoutRounding="True"が第一手、残りにSnapsToDevicePixels - アイコンはベクター資産を第一候補、ビットマップは複数解像度+DPI 切替
WriteableBitmapやスクリーン座標などピクセルを扱うコードだけが本当の改修対象。その数が Per-Monitor 化の工数を決める- WindowsFormsHost / WebBrowser / サードパーティが対応の上限を握る。先に棚卸しする
「WPF だから大丈夫なはず」で止まっていたアプリも、この整理で見れば直す場所は数えるほどしかないことが多いです。手元のアプリがどこまで直せるか、混在コンテンツを含めた現状調査や対応レベルの判断に迷う場合はお手伝いできます。
関連記事
- WinFormsの高DPI対応 ── 4Kモニターでぼやける・崩れる原因と現実的な対処
- WinForms/WPF/WinUIの選び方 - 実務判断表
- WPF/WinFormsのasyncとUIスレッドを一枚で整理
- WindowsアプリUX設計 - 利用環境別の優先順位
関連する相談領域
合同会社小村ソフトでは、WPF / WinForms アプリの高DPI対応(現状調査、Per-Monitor 化の可否判断、混在コンテンツの棚卸しと改修)、PC 入れ替え・4K モニター導入に伴う表示不具合の原因調査、UI 刷新の相談を扱っています。
参考リンク
-
Microsoft Learn, High DPI Desktop Application Development on Windows. UI フレームワーク別の Per-Monitor 対応表、WPF に載せた他フレームワーク・他フレームワークに載せた WPF が自動ではスケールしないこと、混在 DPI 環境でのテスト観点について。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Developing a Per-Monitor DPI-Aware WPF Application. WPF が既定で System DPI Aware であること、DIP による自動スケーリングの仕組み、DPI の異なるモニターへ移動すると OS がスケーリングし非整数倍で特にぼやけること、DPI ごとのビットマップ資産切替の考え方について。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, What’s new in .NET Framework. .NET Framework 4.6.2 の WPF で Per-Monitor DPI awareness が有効になったこと、Switch.System.Windows.DoNotScaleForDpiChanges スイッチ、GitHub 上の開発者ガイドについて。 ↩ ↩2
-
GitHub (microsoft/WPF-Samples), Per Monitor DPI Developer Guide. Windows 10 Anniversary Update と .NET Framework 4.6.2 以降という要件、マニフェストの dpiAwareness / dpiAware の記述、4.6.2 より古いターゲット向けの AppContextSwitchOverrides、HwndSource / ElementHost に WPF を載せる構成が Per-Monitor でサポート外であることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, BitmapScalingMode Enum. 既定(Unspecified)が Linear であること、HighQuality(Fant)・NearestNeighbor 各補間アルゴリズムの特性について。 ↩ ↩2 ↩3
-
Microsoft Learn, Layout - WPF. DIP によるレイアウトとサブピクセル描画でエッジがぼやける仕組み、レイアウト丸め(UseLayoutRounding)が既定で無効であること、ルート要素へ設定するとビジュアルツリーへ伝播することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, UIElement.SnapsToDevicePixels Property. 既定値が false であること、ルートに設定するとサブツリーへ継承されること、96 DPI 超の環境で単線付近のアンチエイリアスによる視覚的アーティファクトを軽減できることについて。 ↩ ↩2
-
Microsoft サポート, Windows で画面の解像度とレイアウトを変更する. 「スタート > 設定 > システム > ディスプレイ」の「拡大縮小とレイアウト」から「テキスト、アプリ、その他の項目のサイズを変更する」で拡大率を選ぶ手順について。 ↩
-
Microsoft サポート, 拡大鏡を使用して画面上の項目を見やすくする. Windows ロゴキー + プラス記号で拡大鏡を起動し、画面の一部を拡大表示できることについて。 ↩
-
Microsoft Learn, Setting the default DPI awareness for a process. dpiAwareness 要素(Windows 10 1607 以降)が dpiAware より優先されること、カンマ区切りで列挙した値のうち最初に認識できたものが使われるフォールバック動作について。 ↩
-
Microsoft Learn, What’s new in .NET Framework. .NET Framework 4.8 で WPF に Per-Monitor V2 DPI Awareness と Mixed-Mode DPI スケーリングのサポートが追加されたこと、ホストされた HWND / WinForms 相互運用の改善、有効化に必要な AppContext スイッチについて。 ↩
-
Microsoft Learn, TextFormattingMode Enum. テキスト整形の Ideal(理想メトリクス)と Display(GDI 互換メトリクス)の 2 モードについて。 ↩
-
Microsoft Learn, Layout Considerations for the WindowsFormsHost Element. WindowsFormsHost が DIP と物理ピクセルの 2 つの座標系を変換すること、スケーリングは中の Windows Forms コントロールが対応できる範囲までであることについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
WinFormsの高DPI対応 ── 4Kモニターでぼやける・崩れる原因と現実的な対処
4KモニターでWinFormsアプリがぼやける・崩れる原因を、DPI仮想化とDPI認識モード(System Aware / Per-Monitor V2)から整理します。.NETと.NET Frameworkの設定方法、AutoScaleModeの罠まで解説します。
WinForms/WPFアプリにEntra ID認証を組み込む ── MSAL.NETとWAMブローカーの実務構成
WinForms/WPFデスクトップアプリにEntra ID認証を組み込む手順を整理します。パブリッククライアントの考え方、アプリ登録、MSAL.NETのAcquireTokenSilent、WAMブローカー、トークンキャッシュの永続化まで解説します。
業務アプリの日時とタイムゾーン ── DateTimeの罠からUTC保存の原則、テスト設計まで
サーバー移設で時刻が9時間ずれる──日時事故の原因をDateTimeのKindと暗黙変換から整理します。DateTimeOffsetとの使い分け、UTC保存の原則、TimeZoneInfoと夏時間、TimeProviderによるテスト設計まで解説します。
Windowsアプリのタスクトレイ常駐とトースト通知 ── NotifyIconの落とし穴とAppNotificationの選び方
業務Windowsアプリのタスクトレイ常駐とトースト通知の実装を整理します。NotifyIconの正しい使い方、Explorer再起動時の再登録、トーストAPI3種の選定判断表、通知が届かないケースまで解説します。
WinForms/WPFアプリの多言語化 ── resx・サテライトアセンブリ・カルチャ切り替えの実務
Windowsデスクトップアプリの多言語化を整理します。CurrentCultureとCurrentUICultureの違い、resxとサテライトアセンブリの仕組み、WPFでの現実的な方式選択、実行時の言語切り替え、書式・RTLまで解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
UI スレッド / タイマーテーマ
WPF / WinForms、UI スレッド、async/await、タイマー設計を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
技術相談・設計レビュー
改修方針、設計レビュー、既存資産の扱いを整理するための技術相談です。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- WPFはDPI対応が不要と聞きましたが、なぜにじむのですか?
- WPFはレイアウト単位が1/96インチのデバイス非依存単位(DIP)で、既定でSystem DPI Awareとして動くため、起動時の主モニターDPIには正しく追従します。しかしSystem DPIはサインイン時に固定されるため、DPIの違うモニターへウィンドウを移すとOSがウィンドウ全体をビットマップ拡大・縮小して帳尻を合わせ、全体が一様ににじみます。特に125%と150%のような非整数倍の組み合わせで劣化が目立ちます。根治はマニフェストでPer-Monitor DPI対応を宣言することで、.NET Framework 4.6.2以降のWPFならウィンドウの再スケーリングは自動で行われます。
- WPFで1pxの罫線がにじむ・太さが揃わないのはなぜですか?
- DIPでレイアウトするため、要素の境界が物理ピクセルの整数位置に乗るとは限らないからです。125%環境では1 DIP = 1.25pxとなり、ピクセルの中間に落ちたエッジはアンチエイリアスで半透明に描かれてにじみます。第一手はルートWindowにUseLayoutRounding="True"を設定することで、レイアウトパスで非整数のピクセル値が丸められ、ビジュアルツリー全体へ伝播します。それでも残る箇所には描画時にエッジをピクセル境界へスナップするSnapsToDevicePixels="True"をピンポイントで適用します。どちらも既定は無効です。
- WPFで文字はきれいなのにアイコンだけぼやけるのはなぜですか?
- 文字はベクターフォントとして描画されるためどのDPIでもくっきりしますが、ビットマップ画像はDIPで配置されてDPIに応じて補間拡大されるためです。16×16pxのアイコンは150%環境で24×24pxに拡大され、既定の補間(Linear)でぼやけます。対策の優先順は、(1) Path/Geometry/DrawingImageやアイコンフォントなどベクター資産にする、(2) 複数解像度のビットマップを用意してVisualTreeHelper.GetDpiやOnDpiChangedでDPIに応じて切り替える、(3) RenderOptions.BitmapScalingModeで補間アルゴリズムを調整する、です。
- WPFのPer-Monitor対応はどう設定しますか?
- アプリケーションマニフェストにdpiAwareness要素を書いて宣言します。WinFormsのApplicationHighDpiModeのようなプロジェクト設定やコードからの切り替え手段はWPFにはありません。要件はWindows 10 1607以降のOSと.NET Framework 4.6.2以降のターゲットで、宣言すればWM_DPICHANGEDの処理からウィンドウの再スケーリングまでフレームワークが自動で行います。WPFのPerMonitorV2サポートは.NET Framework 4.8以降なので、4.6.2〜4.7.2ではPerMonitor単独で宣言します。ターゲットが4.6.1以前のままだと追従が無効のため、AppContextスイッチでの有効化が必要です。なおElementHostやHwndSourceに載せられたWPFのPer-Monitor動作は公式にサポート外です。