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

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

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

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

スケールファクター & DPI を表示する

表示技術が進歩するにつれて、画面パネルの製造元は、パネルの各物理領域の各ユニットに、より多くのピクセルをパックしています。 これにより、最新のディスプレイパネルのドット/インチ (DPI) が、従来よりも大幅に高くなりました。 以前は、ほとんどのディスプレイでは、物理領域が96ピクセル (96 DPI) になっていました。2017では、約 300 DPI 以上のディスプレイがすぐに使用できます。

ほとんどのレガシデスクトップ UI フレームワークには、プロセスの有効期間中に表示 DPI が変化しないという前提が組み込まれています。 この想定は、実際には存在しなくなりました。これは、通常、アプリケーションプロセスの有効期間全体にわたって、表示を複数回変更することです。 スケールファクター/DPI の変化を表示する一般的なシナリオを次に示します。

  • 複数モニターの設定では、各ディスプレイのスケールファクターが異なるため、アプリケーションがあるディスプレイから別のディスプレイに移動します (4K や1080p など)。
  • 高 DPI ラップトップを低 DPI の外部ディスプレイでドッキングおよびドッキング解除する (またはその逆)
  • 高 DPI ラップトップ/タブレットから低 DPI デバイスへのリモートデスクトップ経由での接続 (またはその逆)
  • アプリケーションの実行中に表示スケールファクターの設定を変更する

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

DPI 認識モード

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

DPI を認識しない

DPI 非対応アプリケーションは、96 (100%) という固定の DPI 値でレンダリングします。 これらのアプリケーションを画面上で実行するときに、表示スケールが 96 DPI を超えると、Windows はアプリケーションのビットマップを予想される物理サイズに拡大します。 その結果、アプリケーションがぼやけて表示されます。

システム DPI 認識

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

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

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

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

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

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

注意

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

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

DPI 認識モード Windows のバージョンが導入されました アプリケーションの DPI 表示 DPI 変更時の動作
互い N/A すべてのディスプレイは 96 DPI です ビットマップの伸縮 (ぼやけ)
システム Vista すべてのディスプレイに同じ DPI (現在のユーザーセッションが開始されたときのプライマリディスプレイの DPI) ビットマップの伸縮 (ぼやけ)
Per-Monitor 8.1 アプリケーションウィンドウが主に配置されているディスプレイの DPI
  • トップレベルの HWND は DPI の変更を通知します
  • UI 要素の DPI スケーリングはありません。

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

自動 DPI スケーリング:
  • 非クライアント領域
  • 一般的なコントロールでのテーマ描画ビットマップ (comctl32.dll V6)
  • ダイアログ (Createdialog)

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

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

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

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

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

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

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

フレームワーク/テクノロジ サポート OS バージョン によって処理される DPI スケーリング もっと読む
ユニバーサル Windows プラットフォーム (UWP) [完全] 1607 UI フレームワーク ユニバーサル Windows プラットフォーム (UWP)
生の Win32/コモンコントロール V6 (comctl32.dll)
  • すべての Hwnd に送信される DPI 変更通知メッセージ
  • テーマ描画アセットは、コモンコントロールで正しくレンダリングされます。
  • ダイアログの自動 DPI スケーリング
1703 Application GitHub のサンプル
Windows フォーム 一部のコントロールに対してモニターごとの自動 DPI スケーリングを制限 1703 UI フレームワーク Windows フォームでの高 DPI サポート
Windows Presentation Foundation (WPF) ネイティブ WPF アプリケーションは、他のフレームワークでホストされている DPI スケールと、WPF でホストされている他のフレームワークで自動的にスケーリングされない 1607 UI フレームワーク GitHub のサンプル
GDI なし 該当なし Application GDI の高 DPI スケーリング」を参照してください。
GDI+ なし 該当なし Application GDI の高 DPI スケーリング」を参照してください。
MFC なし 該当なし Application N/A

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

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

ほとんどのデスクトップアプリケーションは、システム 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 の一部を次に示します。

1つの DPI バージョン Per-Monitor のバージョン
GetSystemMetrics GetSystemMetricsForDpi
AdjustWindowRectEx AdjustWindowRectExForDpi
SystemParametersInfo SystemParametersInfoForDpi
GetDpiForMonitor GetDpiForWindow

また、コードベースのハードコーディングされたサイズを検索することもお勧めします。これは、一定の DPI を前提としており、DPI スケーリングに適切に対応するコードに置き換えます。 これらのすべての候補を含む例を次に示します。

例:

次の例は、子 HWND を作成する簡単な Win32 ケースを示しています。 CreateWindow の呼び出しでは、アプリケーションが 96 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, 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 DPI 対応の Api (該当する場合) に置き換えます。 Per-Monitor します。 例: GetSystemMetrics を GetSystemMetricsForDpi に置き換えます。
  6. マルチディスプレイ/マルチ DPI システムでアプリケーションをテストします。
  7. アプリケーション内の最上位レベルのウィンドウで適切な DPI スケールに更新できない場合は、混合モードの DPI スケーリング (以下で説明) を使用して、システムによってこれらのトップレベルウィンドウのビットマップを拡大できるようにします。

Mixed-Mode DPI スケーリング (サブプロセス DPI スケーリング)

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

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

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

Windows 10 記念日更新プログラム (1607) より前のプロセスの DPI 認識モードは、プロセス全体のプロパティでした。 Windows 10 記念日更新以降、このプロパティは トップレベル ウィンドウごとに設定できるようになりました。 ( ウィンドウは、親のスケーリングサイズと引き続き一致している必要があります)。トップレベルウィンドウは、親を持たないウィンドウとして定義されます。 これは通常、[最小化]、[最大化]、[閉じる] の各ボタンを含む "通常の" ウィンドウです。 サブプロセス DPI 認識の目的は、Windows によって拡張されたセカンダリ UI (ビットマップが拡張された状態) で、プライマリ UI の更新に時間とリソースを集中できるようにすることです。

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

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

変更のテスト

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

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

一般的な落とし穴 (Win32)

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

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

  1. 画面間をドラッグするときに、マウスカーソルがウィンドウ上で同じ相対位置にあることを確認する
  2. アプリケーションウィンドウが再帰的な dpi 変更サイクルを開始しないようにします。この場合、1つの DPI 変更によってその後の dpi 変更がトリガーされ、もう一度 dpi が変更されます。

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

仮想化に関するドキュメントの不足

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

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

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

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

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

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

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

次の表は、このルールに違反した場合の動作を示しています。

Operation Windows 8.1 Windows 10 (1607 以前) Windows 10 (1703 以降)
CreateWindow (インプロセス) N/A 子継承 (混在モード) 子継承 (混在モード)
CreateWindow (クロスプロセス) 強制リセット (呼び出し元のプロセス) 子継承 (混在モード) 強制リセット (呼び出し元のプロセス)
SetParent (インプロセス) N/A 強制リセット (現在のプロセス) 失敗 ( _ 無効な _ 状態のエラー)
SetParent (クロスプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス) 強制リセット (子ウィンドウのプロセス)

高 DPI API リファレンス

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