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

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

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

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

Display Scale Factor & DPI

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

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

  • 各ディスプレイに異なるスケール ファクターがあり、アプリケーションが 1 つのディスプレイから別のディスプレイ (4K や 1080p ディスプレイなど) に移動される複数モニターのセットアップ
  • 低 DPI 外部ディスプレイを備えた高 DPI ノート PC のドッキングとドッキング解除 (またはその逆)
  • 高 DPI ラップトップ/Tablet PCから低 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 スケール ファクターで自動的にレンダリングされる

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

Note

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

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

DPI 認識モード Windows バージョンの導入 アプリケーションの DPI ビュー DPI の変更に対する動作
非対応 該当なし すべてのディスプレイは 96 DPI です ビットマップストレッチ (ぼかし)
システム Vista すべてのディスプレイの 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 Application GitHub サンプル
Windows フォーム 一部のコントロールでは、モニターごとの DPI の自動スケーリングが制限されています 1703 UI フレームワーク Windows フォームの高 DPI サポート
Windows Presentation Foundation (WPF) ネイティブ WPF アプリケーションは、他のフレームワークでホストされている WPF を DPI スケールし、WPF でホストされている他のフレームワークは自動的にスケーリングしません 1607 UI フレームワーク GitHub サンプル
GDI なし 該当なし Application GDI の高 DPI スケーリングを参照してください。
GDI+ なし 該当なし Application GDI の高 DPI スケーリングを参照してください。
MFC なし 該当なし Application 該当なし

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

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

ほとんどのデスクトップ アプリケーションは、システム DPI 認識モードで実行されます。 システム DPI 対応アプリケーションは、通常、プライマリ ディスプレイの DPI (Windows セッションの開始時にシステム トレイが配置されていたディスプレイ) にスケーリングします。 DPI が変更されると、Windowsはこれらのアプリケーションの 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 で実行されており、ボタンのサイズも位置も、より高い 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 の子 HWND の位置とサイズをスケーリングするウィンドウ作成コード DPI
  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, 96); 
    int dpiScaledY = MulDiv(INITIALY_96DPI, iDpi, 96); 
    int dpiScaledWidth = MulDiv(INITIALWIDTH_96DPI, iDpi, 96); 
    int dpiScaledHeight = MulDiv(INITIALHEIGHT_96DPI, iDpi, 96); 
    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 スケーリング (サブプロセス DPI スケーリング)

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

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

differences in dpi scaling between awareness modes

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

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

サブプロセス DPI スケーリングを使用すると、Windowsに依存してアプリケーションの DPI スケーリングの一部を実行できますが、アプリケーションの複雑さが増す可能性があります。 このアプローチの欠点と、それが導入する複雑さの性質を理解することが重要です。 サブプロセス DPI 認識の詳細については、混合モード DPI スケーリングと DPI 対応 API に関する記事を参照してください。

変更のテスト

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

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

一般的な落とし穴 (Win32)

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

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

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

WM_DPICHANGED メッセージで提供される推奨される四角形Windows使用できないようにするアプリケーション固有の要件がある場合は、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 を使用して 、LoadImage などの適切な DPI の適切なサイズのアイコンを読み込む必要があります。

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

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

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

次の表は、この規則に違反しようとするとどうなるかを示しています。

操作 Windows 8.1 Windows 10 (1607 以前) Windows 10 (1703 以降)
CreateWindow (In-Proc) 該当なし 子継承 (混合モード) 子継承 (混合モード)
CreateWindow (クロスプロシージャ) 強制リセット (呼び出し元のプロセスの) 子継承 (混合モード) 強制リセット (呼び出し元のプロセスの)
SetParent (In-Proc) 該当なし 強制リセット (現在のプロセスの) 失敗 (ERROR_INVALID_STATE)
SetParent (クロスプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス)

高 DPI API リファレンス

混合モード DPI スケーリングと DPI 対応 API。