次の方法で共有


モニターごとの DPI 対応 WPF アプリケーションの開発

重要な API

Note

このページでは、Windows 8.1用の従来の WPF 開発について説明します。 Windows 10用の WPF アプリケーションを開発している場合は、GitHub の最新のドキュメントを参照してください。

 

Windows 8.1を使用すると、開発者はモニターごとの DPI 対応のデスクトップ アプリケーションを作成するための新しい機能が提供されます。 この機能を利用するには、モニターごとの DPI 対応アプリケーションで次の操作を行う必要があります。

  • ウィンドウのサイズを変更して、任意のディスプレイで一貫性のある物理サイズを維持する
  • 新しいウィンドウ サイズのグラフィックスの再レイアウトと再レンダリング
  • DPI レベルに合わせて適切にスケーリングされるフォントを選択する
  • DPI レベルに合わせて調整されたビットマップアセットを選択して読み込む

モニターごとの DPI 対応アプリケーションの作成を容易にするために、Windows 8.1には次の Microsoft Win32 API が用意されています。

  • SetProcessDpiAwareness (または DPI マニフェスト エントリ) は、プロセスを指定された DPI 認識レベルに設定し、Windows が UI をスケーリングする方法を決定します。 これは SetProcessDPIAware よりも優先されます。
  • GetProcessDpiAwareness は DPI 認識レベルを返します。 これは IsProcessDPIAware よりも優先されます。
  • GetDpiForMonitor はモニター の DPI を返します。
  • WM_DPICHANGED ウィンドウ通知は、ウィンドウの位置が変更されたときにモニターごとの DPI 対応アプリケーションに送信されます。これにより、その領域の大部分が、位置が変化する前またはユーザーがディスプレイ スライダーを動かす前に DPI とは異なる DPI でモニターと交差します。 ユーザーが別のディスプレイに移動したときにサイズを変更して再レンダリングするアプリケーションを作成するには、この通知を使用します。

Windows 8.1のデスクトップ アプリケーションでサポートされるさまざまな DPI 認識レベルの詳細については、「デスクトップアプリケーションと Win32 アプリケーションDPI-Aware記述する」トピックを参照してください。

DPI スケーリングと WPF

Windows Presentation Foundation (WPF) アプリケーションは、既定でシステム DPI 対応です。 さまざまな DPI 認識レベルの定義については、「 デスクトップと Win32 アプリケーションDPI-Aware記述する」トピックを参照してください。 WPF グラフィックス システムでは、デバイスに依存しない単位を使用して、解像度とデバイスの独立性を可能にします。 WPF は、現在のシステム DPI に基づいて、各デバイスに依存しないピクセルを自動的にスケーリングします。 これにより、ウィンドウがオンになっているモニターの DPI が同じシステム DPI である場合、WPF アプリケーションは自動的にスケーリングできます。 ただし、WPF アプリケーションはシステム dpi 対応であるため、アプリケーションを別の DPI でモニターに移動したとき、またはコントロール パネルのスライダーを使用して DPI を変更すると、アプリケーションは OS によってスケーリングされます。 OS でスケーリングすると、特にスケーリングが一体でない場合に WPF アプリケーションがぼやけて表示されることがあります。 WPF アプリケーションのスケーリングを回避するには、モニターごとの DPI 対応に更新する必要があります。

モニターごとの対応 WPF サンプル チュートリアル

モニターごとの対応 WPF サンプルは、モニターごとの DPI 対応に更新されたサンプル WPF アプリケーションです。 このサンプルは、2 つのプロジェクトで構成されます。

  • NativeHelpers.vcxproj: これは、上記の Win32APIs を利用して WPF アプリケーションをモニターごとの DPI 対応にするコア機能を実装するネイティブ ヘルパー プロジェクトです。 プロジェクトには、次の 2 つのクラスが含まれています。
    • PerMonDPIHelpers: アクティブ モニターの現在の DPI の取得、プロセスのモニターごとの DPI 対応の設定など、DPI 関連の操作用のヘルパー関数を提供するクラス。
    • PerMonitorDPIWindow: WPF アプリケーション ウィンドウをモニターごとの dpi 対応にする機能を実装する System.Windows.Window から派生した基本クラス。 システム DPI ではなくモニターの DPI に基づいて、ウィンドウ サイズ、グラフィックス レンダリング サイズ、フォント サイズを調整します。
  • WPFApplication.csproj: PerMonitorDPIWindow (PerMonitorDPIWindow) を使用し、ウィンドウを別の DPI でモニターに移動したとき、またはディスプレイ コントロール パネルのスライダーを使用して DPI を変更したときに、アプリケーション ウィンドウとレンダリングのサイズを変更するサンプル WPF アプリケーション。

サンプルを実行するには、次の手順に従います。

  1. モニターごとの対応 WPF サンプルをダウンロードして解凍する
  2. Microsoft Visual Studio を起動し、[ファイル] [プロジェクト/ソリューションを>開く>] の順に選択します
  3. 解凍したサンプルが含まれているディレクトリを参照します。 サンプルの という名前のディレクトリに移動し、Visual Studio Solution (.sln) ファイルをダブルクリックします
  4. F7 キーを押すか、ビルド ソリューションのビルド>を使用してサンプルをビルドします
  5. Ctrl キーを押しながら F5 キーを押すか 、デバッグなしでデバッグ > 開始を 使用してサンプルを実行します

サンプルの基本クラスを使用してモニターごとの DPI 対応に更新された WPF アプリケーションに対する DPI の変更の影響を確認するには、アプリケーション ウィンドウを異なる DPI を持つディスプレイとの間で移動します。 ウィンドウがモニター間で移動されると、ウィンドウ サイズと UI スケールは、OS によってスケーリングされるのではなく、WPF のスケーラブルなグラフィックス システムを使用してディスプレイの DPI に基づいて更新されます。 アプリケーションの UI はネイティブにレンダリングされ、ぼやけて表示されません。 DPI が異なる 2 つのディスプレイがない場合は、[表示] コントロール パネルのスライダーを変更して DPI を変更します。 スライダーを変更して [ 適用 ] をクリックすると、アプリケーションのウィンドウのサイズが変更され、UI スケールが自動的に更新されます。

WPF サンプルのヘルパー プロジェクトを使用して、既存の WPF アプリケーションをモニターごとの dpi 対応に更新する

既存の WPF アプリケーションがあり、サンプルの DPI ヘルパー プロジェクトを利用して DPI 対応にする場合は、次の手順に従います。

  1. モニターごとの対応 WPF サンプルをダウンロードして解凍する

  2. Visual Studio を起動し、[ファイル] [プロジェクト/ソリューションを>開く>] の順に選択します

  3. 既存の WPF アプリケーションを含むディレクトリを参照し、Visual Studio ソリューション (.sln) ファイルをダブルクリックします。

  4. [ソリューション>] [既存のプロジェクトの追加] を右クリックします。[追加>: 既存のプロジェクト] メニューの選択を示すスクリーンショット

  5. ファイル選択ダイアログで、解凍したサンプルを含むディレクトリを参照します。 サンプルの という名前のディレクトリを開き、フォルダー "NativeHelpers" を参照し、Visual C++ プロジェクト ファイル "NativeHelpers.vcxproj" を選択し、[ OK] をクリックします

  6. プロジェクト NativeHelpers を右クリックし、[ビルド] を選択 します。 これにより、ビルド メニューの選択を示すスクリーン ショットを次の手順で WPF アプリケーションへの参照として追加するNativeHelpers.dllが生成されます

  7. WPF アプリケーションからNativeHelpers.dllへの参照を追加します。 WPF アプリケーション プロジェクトを展開し、[参照] を右クリックし、[参照の追加]をクリックします。

  8. 結果のダイアログで、[ ソリューション ] セクションを展開します。 [プロジェクト] で [NativeHelpers] を選択し、リソース マネージャー ダイアログを示すスクリーン ショットを[OK] をクリックします

  9. WPF アプリケーション プロジェクトを展開し、[ プロパティ] を展開して AssemblyInfo.cs を開きます。 AssemblyInfo.cs に次の追加を行います

    • 参照セクションに System.Windows.Media への参照を追加します (System.Windows.Media を使用)。
    • DisableDpiAwareness 属性を追加する ([assembly: DisableDpiAwareness])

    追加のプロパティを示すスクリーン ショット

  10. PerMonitorDPIWindow 基本クラスからメイン WPF ウィンドウ クラスを継承する

    • PerMonitorDPIWindow 基本クラスから継承するように、メイン WPF ウィンドウの .cs ファイルを更新します

      • 行を追加して、Reference セクションに NativeHelpers への参照を追加します using NativeHelpers;
      • PerMonitorDPIWindow クラスからメイン ウィンドウ クラスを継承する

      c++ リファレンスを示すスクリーン ショット

    • PerMonitorDPIWindow 基本クラスから継承するように、メイン WPF ウィンドウの .xaml ファイルを更新します

      • 行を追加して、Reference セクションに NativeHelpers への参照を追加します xmlns:src="clr-namespace:NativeHelpers;assembly=NativeHelpers"
      • PerMonitorDPIWindow クラスからメイン ウィンドウ クラスを継承する

      .xaml 参照の追加を示すスクリーン ショット

  11. F7 キーを押すか、ビルド ソリューションのビルド>を使用してサンプルをビルドします

  12. Ctrl キーを押しながら F5 キーを押すか 、デバッグなしでデバッグ > 開始を 使用してサンプルを実行します

モニターごとの対応 WPF サンプル アプリケーションは、WM_DPICHANGED ウィンドウ通知に応答することで、WPF アプリケーションをモニターごとの DPI 対応に更新する方法を示しています。 ウィンドウ通知に応答して、このサンプルでは、ウィンドウがオンになっているモニターの現在の DPI に基づいて、WPF で使用されるスケール変換を更新します。 ウィンドウ通知の wParam には、 wParam の新しい DPI が含まれています。 lParam には、新しい DPI 用にスケーリングされた新しい推奨ウィンドウのサイズと位置を持つ四角形が含まれています。

メモ:

Note

このサンプルでは、WPF ウィンドウのルート ノードのウィンドウ サイズとスケール変換が上書きされるため、次の場合は、アプリケーション開発者が追加の作業を行う必要があります。

  • ウィンドウのサイズは、この WPF ウィンドウが別のアプリケーション内でホストされているなど、アプリケーションの他の部分に影響します。
  • このクラスを拡張している WPF アプリケーションは、ルート ビジュアルに他の変換を設定しています。このサンプルでは、WPF アプリケーション自体によって適用されている他の変換が上書きされる可能性があります。

 

WPF サンプルのヘルパー プロジェクトの概要

既存の WPF アプリケーションをモニターごとの DPI 対応にするために、NativeHelpers ライブラリには次の機能があります。

  • WPF アプリケーションをポイントールごとの DPI 対応としてマークします。 WPF アプリケーションは、現在のプロセスに対して SetProcessDpiAwareness を呼び出すことによって、モニターごとの DPI 対応としてマークされます。 アプリケーションをモニターごとの DPI 対応としてマークすると、

    • システム DPI が、アプリケーション ウィンドウがオンになっているモニターの現在の DPI と一致しない場合、OS はアプリケーションをスケーリングしません
    • ウィンドウの DPI が変更されるたびに、 WM_DPICHANGED メッセージが送信されます
  • ウィンドウのサイズを調整し、グラフィックス コンテンツを再レイアウトして再レンダリングし、ウィンドウが表示されているモニターの初期 DPI に基づいてフォントを選択します 。アプリケーションがモニターごとの DPI 対応としてマークされると、WPF はシステム DPI に基づいてウィンドウ サイズ、グラフィックス、フォント サイズをスケーリングします。 アプリの起動時に、システム DPI は、ウィンドウが起動されているモニターの DPI と同じであることが保証されないため、ライブラリは、ウィンドウが読み込まれるとこれらの値を調整します。 基本クラス PerMonitorDPIWindow、OnLoaded() ハンドラーでこれらを更新します。

    ウィンドウのサイズは、Window の Width プロパティと Height プロパティを変更することで更新されます。 レイアウトとサイズは、WPF ウィンドウのルート ノードに適切なスケール変換を適用することで更新されます。

    void PerMonitorDPIWindow::OnLoaded(Object^ , RoutedEventArgs^ ) 
    {   
    if (m_perMonitorEnabled)
        {
        m_source = (HwndSource^) PresentationSource::FromVisual((Visual^) this);
        HwndSourceHook^ hook = gcnew HwndSourceHook(this, &PerMonitorDPIWindow::HandleMessages);
        m_source->AddHook(hook); 
    
        //Calculate the DPI used by WPF.                    
        m_wpfDPI = 96.0 *  m_source->CompositionTarget->TransformToDevice.M11; 
    
        //Get the Current DPI of the monitor of the window. 
        m_currentDPI = NativeHelpers::PerMonitorDPIHelper::GetDpiForWindow(m_source->Handle);
    
        //Calculate the scale factor used to modify window size, graphics and text
        m_scaleFactor = m_currentDPI / m_wpfDPI; 
    
        //Update Width and Height based on the on the current DPI of the monitor
        Width = Width * m_scaleFactor;
        Height = Height * m_scaleFactor;
    
        //Update graphics and text based on the current DPI of the monitor
    UpdateLayoutTransform(m_scaleFactor);
        }
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    
  • WM_DPICHANGED ウィンドウ通知に応答します。 ウィンドウ通知で渡された DPI に基づいて、ウィンドウ サイズ、グラフィックス、フォント サイズを更新します。 基本クラス PerMonitorDPIWindow、HandleMessages() メソッドでウィンドウ通知を処理します。

    ウィンドウ サイズは、ウィンドウ メッセージの lparam で渡された情報を使用して SetWindowPos を呼び出すことによって更新されます。 レイアウトとグラフィックス のサイズは、WPF ウィンドウのルート ノードに適切なスケール変換を適用することで更新されます。 スケール ファクターは、ウィンドウ メッセージの wparam で渡された DPI を使用して計算されます。

    IntPtr PerMonitorDPIWindow::HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% )
    {
    double oldDpi;
    switch (msg)
        {
        case WM_DPICHANGED:
        LPRECT lprNewRect = (LPRECT)lParam.ToPointer();
        SetWindowPos(static_cast<HWND>(hwnd.ToPointer()), 0, lprNewRect->left, lprNewRect-
            >top, lprNewRect->right - lprNewRect->left, lprNewRect->bottom - lprNewRect->top, 
           SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
        oldDpi = m_currentDPI;
        m_currentDPI = static_cast<int>(LOWORD(wParam.ToPointer()));
        if (oldDpi != m_currentDPI) 
            {
            OnDPIChanged();
            }
        break;
        }
    return IntPtr::Zero;
    }
    
    void PerMonitorDPIWindow::OnDPIChanged() 
    {
    m_scaleFactor = m_currentDPI / m_wpfDPI;
    UpdateLayoutTransform(m_scaleFactor);
    DPIChanged(this, EventArgs::Empty);
    }
    
    void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor)
    {
    // Adjust the rendering graphics and text size by applying the scale transform to the top         
    level visual node of the Window     
    
    if (m_perMonitorEnabled) 
        {       
            auto child = GetVisualChild(0);
            if (m_scaleFactor != 1.0) 
           {
            ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor);
            child->SetValue(Window::LayoutTransformProperty, dpiScale);
            }
            else 
            {
            child->SetValue(Window::LayoutTransformProperty, nullptr);
            }           
        }
    }
    

画像などの資産の DPI 変更の処理

グラフィックス コンテンツを更新するために、サンプル WPF アプリケーションは、WPF アプリケーションのルート ノードにスケール変換を適用します。 これは、WPF によってネイティブにレンダリングされるコンテンツ (四角形、テキストなど) に適していますが、これは、イメージなどのビットマップアセットが WPF によってスケーリングされることを意味します。

WPF アプリケーション開発者は、スケーリングによってビットマップがぼやけないようにするために、ウィンドウがオンになっているモニターの現在の DPI に基づいて別の資産を選択するカスタム DPI イメージ コントロールを作成できます。 イメージ コントロールは、DPI が変更されたときに PerMonitorDPIWindow から使用する WPF ウィンドウに対して発生する DPIChanged() イベントに依存できます。

Note

イメージ コントロールは、 Loaded() WPF ウィンドウ イベント ハンドラーでアプリの起動時に適切なコントロールを選択する必要もあります。