次の方法で共有


Windows での高 DPI デスクトップ アプリケーション開発

このコンテンツは、ディスプレイ スケール ファクター (1 インチあたりのドット数、DPI) の変更を動的に処理するようにデスクトップ アプリケーションを更新しようとしている開発者を対象としています。これにより、レンダリングされているディスプレイでアプリケーションが鮮明になります。

まず、新しい Windows アプリをゼロから作成する場合は、ユニバーサル Windows プラットフォーム (UWP) アプリケーションを作成することを強くお勧めします。 UWP アプリケーションは、実行されている各ディスプレイに対して自動的かつ動的にスケーリングされます。

以前の Windows プログラミング テクノロジを使用したデスクトップ アプリケーション (未加工の Win32 プログラミング、Windows フォーム、Windows Presentation Framework (WPF) など) は、追加の開発者作業なしで DPI スケーリングを自動的に処理することはできません。 このような作業がないと、多くの一般的な使用シナリオでアプリケーションのサイズがぼやけたり、サイズが間違って表示されたりします。 このドキュメントでは、デスクトップ アプリケーションを正しくレンダリングするために何が関係するかについてのコンテキストと情報を提供します。

ディスプレイ スケール ファクター & DPI

ディスプレイ技術の進歩に伴い、ディスプレイパネルメーカーは、パネル上の物理空間の各ユニットに増加するピクセル数を詰め込んでいます。 その結果、モダン ディスプレイ パネルの 1 インチあたりのドット数 (DPI) は、これまでよりもはるかに高くなっています。 以前は、ほとんどのディスプレイには、物理空間の線形インチあたり 96 ピクセル (96 DPI) がありました。2017年には、300 DPI以上のディスプレイがすぐに利用できます。

ほとんどのレガシ デスクトップ UI フレームワークには、プロセスの有効期間中にディスプレイ DPI が変更されないことを前提として組み込まれています。 この前提は、アプリケーション プロセスの有効期間を通じて表示 DPI が一般的に数回変更されることで、当てはまらなくなりました。 表示倍率/DPI の変更が次のような一般的なシナリオがあります。

  • 各ディスプレイに異なるスケール ファクターがあり、アプリケーションが 1 つのディスプレイから別のディスプレイ (4K や 1080p ディスプレイなど) に移動される複数モニターのセットアップ
  • 低 DPI 外付けディスプレイを使用した高 DPI ノート PC のドッキングとドッキング解除 (またはその逆)
  • 高 DPI ラップトップ/タブレットから低 DPI デバイス (またはその逆) にリモート デスクトップ経由で接続する
  • アプリケーションの実行中に表示倍率の設定を変更する

これらのシナリオでは、UWP アプリケーションは新しい DPI に対して自動的に再描画されます。 既定では、デスクトップ アプリケーションは追加の開発者作業を行いません。 DPI の変更に対応するためにこの追加の作業を行わないデスクトップ アプリケーションは、ユーザーに対してぼやけたり、サイズが間違って表示されたりすることがあります。

DPI 認識モード

デスクトップ アプリケーションは、DPI スケーリングをサポートしているかどうかを Windows に通知する必要があります。 既定では、システムはデスクトップ アプリケーションの DPI を認識しないとみなし、ウィンドウをビットマップストレッチします。 使用可能な次のいずれかの DPI 認識モードを設定することで、アプリケーションは DPI スケーリングの処理方法を Windows に明示的に通知できます。

DPI Unaware

DPI 認識されていないアプリケーションは、固定 DPI 値 96 (100%) でレンダリングされます。 ディスプレイ スケールが 96 DPI を超える画面でこれらのアプリケーションが実行されるたびに、Windows はアプリケーション ビットマップを予想される物理サイズに拡大します。 これにより、アプリケーションがぼやけて表示されます。

システム DPI 認識

システム DPI 対応のデスクトップ アプリケーションは、通常、ユーザーのサインイン時点でプライマリ接続モニターの DPI を受け取ります。 初期化時に、そのシステム DPI 値を使用して、UI を適切にレイアウトします (コントロールのサイズ設定、フォント サイズの選択、アセットの読み込みなど)。 そのため、システム DPI 対応アプリケーションは、その 1 つの DPI でレンダリングを表示するときに、Windows によって DPI スケーリング (ビットマップ ストレッチ) されません。 アプリケーションが別のスケール ファクターを持つディスプレイに移動された場合、または表示倍率が変更された場合、Windows はアプリケーションのウィンドウをビットマップスケールし、ぼやけて表示します。 効果的に、システム DPI 対応のデスクトップ アプリケーションは、1 つのディスプレイ スケール ファクターでのみ鮮明にレンダリングされ、DPI が変更されるたびにぼやけてしまいます。

Per-Monitor と Per-Monitor (V2) DPI 認識

デスクトップ アプリケーションは、モニターごとの DPI 認識モードを使用するように更新することをお勧めします。これにより、DPI が変更されるたびにすぐに正しくレンダリングできます。 アプリケーションがこのモードで実行することを Windows に報告する場合、DPI が変更されたときに Windows はアプリケーションをビットマップストレッチせず、代わりにアプリケーション ウィンドウに WM_DPICHANGED を送信します。 その後、新しい DPI のサイズ変更自体を処理するのは、アプリケーションの完全な役割です。 デスクトップ アプリケーションで使用されるほとんどの UI フレームワーク (Windows コモン コントロール (comctl32)、Windows フォーム、Windows Presentation Framework など) は自動 DPI スケーリングをサポートしていないため、開発者はウィンドウ自体のコンテンツのサイズを変更して再配置する必要があります。

アプリケーション自体を登録できる Per-Monitor 認識には、バージョン 1 とバージョン 2 (PMv2) の 2 つのバージョンがあります。 プロセスを PMv2 認識モードで実行するように登録すると、次の結果が得られます。

  1. DPI が変更されたときに通知を受け取るアプリケーション (最上位と子の両方の HWND)
  2. 各ディスプレイの生のピクセルが表示されるアプリケーション
  3. アプリケーションが Windows によってビットマップスケーリングされることはありません
  4. クライアント以外の自動領域 (ウィンドウ キャプション、スクロール バーなど)Windows による DPI スケーリング
  5. Win32 ダイアログ (CreateDialogから) は、Windows によって自動的に DPI スケーリングされます
  6. 一般的なコントロール (チェックボックス、ボタンの背景など) のテーマ描画ビットマップアセットが、適切な DPI スケール ファクターで自動的にレンダリングされる

Per-Monitor v2 認識モードで実行すると、DPI が変更されたときにアプリケーションに通知されます。 アプリケーションが新しい DPI のサイズを変更しない場合、アプリケーション UI は小さすぎるか大きすぎるように見えます (前の DPI 値と新しい DPI 値の違いによって異なります)。

手記

Per-Monitor V1 (PMv1) の認識は非常に限られています。 アプリケーションでは PMv2 を使用することをお勧めします。

次の表は、さまざまなシナリオでアプリケーションがどのようにレンダリングされるかを示しています。

DPI 認識モード 導入された Windows バージョン DPI に関するアプリケーションのビュー DPI の変更に対する動作
知らない N/A すべてのディスプレイは 96 DPI です ビットマップストレッチ (ぼかし)
遠望 すべてのディスプレイの DPI は同じです (現在のユーザー セッションが開始されたときのプライマリ ディスプレイの DPI) ビットマップストレッチ (ぼかし)
Per-Monitor 8.1 アプリケーション ウィンドウが主に配置されているディスプレイの DPI
  • 最上位の HWND に DPI の変更が通知される
  • UI 要素の DPI スケーリングはありません。

Per-Monitor V2 Windows 10 Creators Update (1703) アプリケーション ウィンドウが主に配置されているディスプレイの DPI
  • 最上位レベルの の子 HWND に DPI の変更が通知されます

の自動 DPI スケーリング:
  • クライアント以外の領域
  • 共通コントロールのテーマ描画ビットマップ (comctl32 V6)
  • ダイアログ (CreateDialog)

モニターごと (V1) DPI 認識

Per-Monitor V1 DPI 認識モード (PMv1) が Windows 8.1 で導入されました。 この DPI 認識モードは非常に制限されており、以下に示す機能のみを提供します。 デスクトップ アプリケーションでは、Windows 10 1703 以降でサポートされている v2 認識モード Per-Monitor 使用することをお勧めします。

モニターごとの認識の初期サポートでは、次のアプリケーションのみが提供されました。

  1. 最上位の HWND には DPI の変更が通知され、新しい推奨サイズが提供されます
  2. Windows でアプリケーション UI がビットマップ ストレッチされない
  3. アプリケーションでは、すべての表示が物理ピクセルで表示されます (仮想化を参照)

Windows 10 1607 以降では、PMv1 アプリケーションは、WM_NCCREATE中に EnableNonClientDpiScaling 呼び出して、Windows がウィンドウの非クライアント領域を正しくスケーリングするように要求することもできます。

UI フレームワーク/テクノロジによるモニターごとの DPI スケーリングのサポート

次の表は、Windows 10 1703 以降のさまざまな Windows UI フレームワークによって提供されるモニターごとの DPI 認識サポートのレベルを示しています。

フレームワーク/テクノロジ 支える OS バージョン DPI スケーリングは次によって処理されます その他の資料
ユニバーサル Windows プラットフォーム (UWP) 一杯 1607 UI フレームワーク ユニバーサル Windows プラットフォーム (UWP)
Raw Win32/Common Controls V6 (comctl32.dll)
  • すべての HWND に送信される DPI 変更通知メッセージ
  • テーマ描画アセットが一般的なコントロールで正しくレンダリングされる
  • ダイアログの自動 DPI スケーリング
1703 アプリケーション GitHub サンプル
Windows フォーム 一部のコントロールに対するモニターごとの DPI スケーリングの制限付き自動 1703 UI フレームワーク Windows フォーム での高 DPI サポートの
Windows Presentation Framework (WPF) ネイティブ WPF アプリケーションは、他のフレームワークでホストされている WPF を DPI スケールし、WPF でホストされている他のフレームワークは自動的にスケーリングしません 1607 UI フレームワーク GitHub サンプル
GDI 何一つ N/A アプリケーション GDI High-DPI スケーリング を参照してください
GDI+ 何一つ N/A アプリケーション GDI High-DPI スケーリング を参照してください
MFC 何一つ N/A アプリケーション N/A

既存のアプリケーションの更新

DPI スケーリングを適切に処理するように既存のデスクトップ アプリケーションを更新するには、少なくともその UI の重要な部分が DPI の変更に対応するように更新する必要があります。

ほとんどのデスクトップ アプリケーションは、システム DPI 認識モードで実行されます。 システム DPI 対応アプリケーションは、通常、プライマリ ディスプレイの DPI (Windows セッションの開始時にシステム トレイが配置されていたディスプレイ) にスケーリングします。 DPI が変わると、Windows はこれらのアプリケーションの UI をビットマップで拡大し、多くの場合、それらの UI がぼやけます。 システム DPI 対応アプリケーションをモニターごとの DPI 対応になるように更新する場合、UI レイアウトを処理するコードは、アプリケーションの初期化中だけでなく、DPI 変更通知 (Win32 の場合はWM_DPICHANGED) が受信されるたびに実行されるように更新する必要があります。 これには通常、UI を 1 回だけスケーリングする必要があるという、コード内の前提条件を見直す必要があります。

また、Win32 プログラミングの場合、多くの Win32 API には DPI や表示コンテキストがないため、システム DPI に対する相対値のみが返されます。 これらの API の一部を検索し、DPI 対応のバリアントに置き換えるには、コードを詳しく説明すると便利です。 DPI 対応のバリアントを持つ一般的な API の一部を次に示します。

単一 DPI バージョン Per-Monitor バージョン
GetSystemMetrics GetSystemMetricsForDpi
AdjustWindowRectEx AdjustWindowRectExForDpi
SystemParametersInfo SystemParametersInfoForDpi
GetDpiForMonitor GetDpiForWindow

また、一定の DPI を想定するハードコーディングされたサイズをコードベースで検索し、DPI スケーリングを正しく考慮するコードに置き換えることをお勧めします。 次に、これらの提案をすべて組み込んだ例を示します。

例:

次の例は、子 HWND を作成する場合の簡略化された Win32 ケースを示しています。 CreateWindow の呼び出しでは、アプリケーションが 96 DPI (USER_DEFAULT_SCREEN_DPI 定数) で実行されており、ボタンのサイズも位置も高い DPI では正しくありません。

case WM_CREATE: 
{ 
    // Add a button 
    HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",  
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,  
        50,  
        50,  
        100,  
        50,  
        hWnd, (HMENU)NULL, NULL, NULL); 
} 

次の更新されたコードは次のとおりです。

  1. ウィンドウ作成コード DPI は、親ウィンドウの DPI に対する子 HWND の位置とサイズをスケーリングします
  2. 子 HWND の位置変更とサイズ変更による DPI の変更への対応
  3. ハードコーディングされたサイズが削除され、DPI の変更に応答するコードに置き換えられました
#define INITIALX_96DPI 50 
#define INITIALY_96DPI 50 
#define INITIALWIDTH_96DPI 100 
#define INITIALHEIGHT_96DPI 50 

// DPI scale the position and size of the button control 
void UpdateButtonLayoutForDpi(HWND hWnd) 
{ 
    int iDpi = GetDpiForWindow(hWnd); 
    int dpiScaledX = MulDiv(INITIALX_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI); 
    int dpiScaledY = MulDiv(INITIALY_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI); 
    int dpiScaledWidth = MulDiv(INITIALWIDTH_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI); 
    int dpiScaledHeight = MulDiv(INITIALHEIGHT_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI); 
    SetWindowPos(hWnd, hWnd, dpiScaledX, dpiScaledY, dpiScaledWidth, dpiScaledHeight, SWP_NOZORDER | SWP_NOACTIVATE); 
} 
 
... 
 
case WM_CREATE: 
{ 
    // Add a button 
    HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",  
        WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON, 
        0, 
        0, 
        0, 
        0, 
        hWnd, (HMENU)NULL, NULL, NULL); 
    if (hWndChild != NULL) 
    { 
        UpdateButtonLayoutForDpi(hWndChild); 
    } 
} 
break; 
 
case WM_DPICHANGED: 
{ 
    // Find the button and resize it 
    HWND hWndButton = FindWindowEx(hWnd, NULL, NULL, NULL); 
    if (hWndButton != NULL) 
    { 
        UpdateButtonLayoutForDpi(hWndButton); 
    } 
} 
break; 

システム DPI 対応アプリケーションを更新する場合、一般的な手順をいくつか次に示します。

  1. アプリケーション マニフェスト (または使用される UI フレームワークに応じて他の方法) を使用して、プロセスをモニターごとの DPI 対応 (V2) としてマークします。
  2. UI レイアウト ロジックを再利用可能にし、DPI の変更が発生したときに再利用できるようにアプリケーション初期化コードから移動します (Windows (Win32) プログラミングの場合WM_DPICHANGED)。
  3. DPI に依存するデータ (DPI/フォント/サイズなど) を更新する必要がないと想定するコードを無効にします。 プロセスの初期化時にフォント サイズと DPI 値をキャッシュすることは、非常に一般的な方法です。 モニターごとの DPI 対応になるようにアプリケーションを更新する場合は、新しい DPI が検出されるたびに DPI に依存するデータを再評価する必要があります。
  4. DPI の変更が発生したら、新しい DPI のビットマップ アセットを再読み込み (または再ラスター化) するか、必要に応じて、現在読み込まれているアセットを適切なサイズに拡大します。
  5. DPI 対応ではない API の Grep を Per-Monitor し、Per-Monitor DPI 対応 API に置き換えます (該当する場合)。 例: GetSystemMetrics を GetSystemMetricsForDpi に置き換えます。
  6. マルチディスプレイ/マルチ DPI システムでアプリケーションをテストします。
  7. 適切な DPI スケールに更新できないアプリケーションの最上位レベルのウィンドウの場合は、混合モードの DPI スケーリング (後述) を使用して、システムによるこれらのトップレベル ウィンドウのビットマップ ストレッチを許可します。

Mixed-Mode DPI スケーリング (Sub-Process DPI スケーリング)

モニターごとの DPI 認識をサポートするようにアプリケーションを更新すると、アプリケーション内のすべてのウィンドウを一度に更新することが現実的でなくなる場合や不可能になる場合があります。 これは、すべての UI の更新とテストに必要な時間と労力、または実行する必要があるすべての UI コードを所有していない (アプリケーションがサードパーティの UI を読み込む場合) ことが原因である可能性があります。 このような状況では、Windows では、アプリケーション ウィンドウの一部 (最上位レベルのみ) を元の DPI 認識モードで実行しながら、UI のより重要な部分を更新する時間とエネルギーに集中できるようにすることで、モニターごとの認識の世界を容易にする方法が提供されます。

次の図は、既存のモード ("セカンダリ ウィンドウ") で他のウィンドウを実行している間に、モニターごとの DPI 認識で実行するようにメイン アプリケーション UI (図の "メイン ウィンドウ") を更新します。

認識モード間の dpi スケーリングの違いを

Windows 10 Anniversary Update (1607) より前のバージョンでは、プロセスの DPI 認識モードはプロセス全体のプロパティでした。 Windows 10 Anniversary Update 以降、このプロパティは最上位レベルの ウィンドウ 設定できるようになりました。 ( ウィンドウは、親のスケーリング サイズと一致し続ける必要があります)。最上位のウィンドウは、親のないウィンドウとして定義されます。 これは通常、最小化、最大化、閉じるボタンを含む "標準" ウィンドウです。 サブプロセス DPI 認識の目的は、プライマリ UI の更新に時間とリソースを集中しながら、Windows によってセカンダリ UI をスケーリング (ビットマップ ストレッチ) することです。

サブプロセス DPI 認識を有効にするには、ウィンドウ作成呼び出し 前後に SetThreadDpiAwarenessContext 呼び出します。 作成されたウィンドウは、SetThreadDpiAwarenessContext を使用して設定した DPI 認識に関連付けられます。 2 番目の呼び出しを使用して、現在のスレッドの DPI 認識を復元します。

サブプロセス DPI スケーリングを使用すると、アプリケーションの DPI スケーリングの一部を Windows に依存させることができますが、アプリケーションの複雑さが増す可能性があります。 このアプローチの欠点と、導入される複雑さの性質を理解することが重要です。 サブプロセス DPI 対応の詳細については、「DPI スケーリングと DPI 対応 API の Mixed-Mode」を参照してください。

変更のテスト

モニターごとの DPI 対応になるようにアプリケーションを更新した後は、混合 DPI 環境での DPI の変更にアプリケーションが適切に応答することを検証することが重要です。 テストの詳細には、次のようなものがあります。

  1. 異なる DPI 値の表示間でアプリケーション ウィンドウを前後に移動する
  2. さまざまな DPI 値の表示でアプリケーションを起動する
  3. アプリケーションの実行中にモニターのスケール ファクターを変更する
  4. プライマリ ディスプレイとして使用するディスプレイを変更 、Windowsからサインアウトしてから、再びサインインした後でアプリケーションを再テストします。 これは、ハードコーディングされたサイズ/ディメンションを使用するコードを見つける場合に特に便利です。

一般的な落とし穴 (Win32)

WM_DPICHANGED で提供される推奨される四角形を使用しない

Windows がアプリケーション ウィンドウに WM_DPICHANGED メッセージを送信すると、このメッセージには、ウィンドウのサイズを変更するために使用する必要がある、推奨される四角形が含まれます。 次のように、アプリケーションでこの四角形を使用してサイズを変更することが重要です。

  1. ディスプレイ間をドラッグするときに、マウス カーソルがウィンドウ上の同じ相対位置に留まるようにする
  2. 1 つの DPI 変更によって後続の DPI 変更がトリガーされ、さらに別の DPI 変更がトリガーされる再帰的な dpi 変更サイクルにアプリケーション ウィンドウが入らないようにします。

windows がWM_DPICHANGED メッセージで提供する推奨される四角形を使用できないようにするアプリケーション固有の要件がある場合は、WM_GETDPISCALEDSIZEを参照してください。 このメッセージは、上記の問題を回避しながら、DPI の変更が発生した後に使用する必要があるサイズを Windows に提供するために使用できます。

仮想化 に関するドキュメントがない

HWND またはプロセスが DPI 非認識またはシステム DPI 対応として実行されている場合は、Windows によってビットマップストレッチできます。 この場合、Windows は DPI に依存する情報をスケーリングし、一部の API から呼び出し元スレッドの座標空間に変換します。 たとえば、DPI 非対応スレッドが高 DPI ディスプレイで実行中に画面サイズを照会する場合、Windows は画面が 96 DPI 単位であるかのようにアプリケーションに与えられた回答を仮想化します。 または、システム DPI 対応スレッドが現在のユーザーのセッションの開始時に使用されていたのとは異なる DPI でディスプレイと対話している場合、Windows は、元の DPI スケール ファクターで実行されている場合に HWND が使用する座標空間に対して、一部の API 呼び出しを DPI スケールします。

デスクトップ アプリケーションを DPI スケールに適切に更新すると、スレッド コンテキストに基づいて仮想化された値を返すことができる API 呼び出しを把握するのが困難になる場合があります。この情報は現在、Microsoft によって十分に文書化されていません。 DPI 非対応またはシステム DPI 対応のスレッド コンテキストからシステム API を呼び出すと、戻り値が仮想化される可能性があることに注意してください。 そのため、画面または個々のウィンドウを操作するときに予想される DPI コンテキストでスレッドが実行されていることを確認します。 SetThreadDpiAwarenessContext 使用してスレッドの DPI コンテキストを一時的に変更する場合は、アプリケーションの他の場所で不適切な動作が発生しないように、完了したら、必ず古いコンテキストを復元してください。

多くの Windows API には DPI コンテキスト がありません

多くのレガシ Windows API には、インターフェイスの一部として DPI または HWND コンテキストが含まれていません。 その結果、開発者は多くの場合、サイズ、ポイント、アイコンなど、DPI に依存する情報のスケーリングを処理するために追加の作業を行う必要があります。 たとえば、LoadIcon を使用する開発者は、ビットマップ ストレッチ 読み込みアイコンを使用するか、代替 API を使用して、適切な DPI の適切なサイズのアイコン (LoadImageなど) を読み込む必要があります。

プロセス全体の DPI 認識 の強制リセット

一般に、プロセスの初期化後にプロセスの DPI 認識モードを変更することはできません。 ただし、ウィンドウ ツリー内のすべての HWND が同じ DPI 認識モードであるという要件を解除しようとすると、Windows はプロセスの DPI 認識モードを強制的に変更できます。 Windows 10 1703 以降、すべてのバージョンの Windows では、HWND ツリー内の異なる HWND を異なる DPI 認識モードで実行することはできません。 このルールを破る子と親の関係を作成しようとすると、プロセス全体の DPI 認識をリセットできます。 これは、次の方法でトリガーできます。

  1. 渡された親ウィンドウが呼び出し元スレッドとは異なる DPI 認識モードである CreateWindow 呼び出し。
  2. 2 つのウィンドウが異なる DPI 認識モードに関連付けられている SetParent 呼び出し。

次の表は、この規則に違反しようとした場合の動作を示しています。

操作 Windows 8.1 Windows 10 (1607 以前) Windows 10 (1703 以降)
CreateWindow (In-Proc) N/A 子は を継承します (混合モード) 子は を継承します (混合モード)
CreateWindow (クロスプロセス) 強制リセット (呼び出し元のプロセスの) 子は を継承します (混合モード) 強制リセット (呼び出し元のプロセスの)
SetParent (In-Proc) N/A 強制リセット (現在のプロセスの場合) 失敗 (ERROR_INVALID_STATE)
SetParent (クロスプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス)

高 DPI API リファレンス

DPI スケーリングと DPI 対応 API を Mixed-Mode します。