web-dev-qa-db-ja.com

モニターごとの高DPIサポートのための非クライアント領域(タイトルバー、メニューバー)のスケーリング

Windows 8.1では、モニターごとに異なるDPI設定を使用する機能が導入されました。この機能は、「モニターごとの高DPIサポート」と呼ばれます。それは持続し、Windows 10では さらに改良されました です。

アプリケーションがオプトインしない場合(i.e。、DPI非対応または高DPI対応のいずれか)、DWMによって適切なDPIに自動的にスケールアップされます。ほとんどのアプリケーションは、Windowsにバンドルされているほとんどのユーティリティ(e.g。、メモ帳)を含め、これら2つのカテゴリのいずれかに分類されます。私のテストシステムでは、高DPIモニターは150%スケール(144 DPI)に設定されていますが、通常のモニターはシステムDPI(100%スケール、96 DPI)に設定されています。したがって、高DPI画面でこれらのアプリケーションの1つを開く(またはそこにドラッグする)と、仮想化が始まり、すべてが拡大されますが、非常に不鮮明になります。

一方、アプリケーションがモニターごとの高DPIをサポートしていることをアプリケーションが明示的に示している場合、仮想化は実行されず、開発者がスケーリングを担当します。マイクロソフトはかなり包括的な説明をしています ここ*ですが、自己完結型の質問のために要約します。まず、マニフェストで<dpiAware>True/PM</dpiAware>を設定してサポートを示します。これにより、 WM_DPICHANGED messages が表示されます。これは、新しいDPI設定と、ウィンドウの推奨される新しいサイズと位置の両方を通知します。また、互換性の理由で嘘をつかずに、 GetDpiForMonitor 関数を呼び出してactual DPIを取得することもできます。 Kenny Kerrも書きました 包括的なチュートリアル

このすべてを小さなC++テストアプリで正常に実行できました。これは定型文が多く、ほとんどがプロジェクト設定であるため、ここで完全な例を投稿してもあまり意味がありません。テストする場合は、Kennyの指示に従って MSDNのこのチュートリアル をダウンロードするか、 公式のSDKサンプル をダウンロードしてください。これで、クライアント領域のテキストはよく見えます(WM_DPICHANGEDを処理したため)。仮想化が実行されなくなったため、非クライアント領域のスケーリングはありません。その結果、タイトル/キャプションバーとメニューバーは間違ったサイズとなり、高DPI画面では大きくなりません。

だから問題は、どうすればウィンドウの非クライアント領域を新しいDPIにスケーリングするのですか?
独自のウィンドウクラスを作成するか、ダイアログを使用するかは関係ありません。これらの点で同じ動作をします。

それは 提案されています そこにis答えはありません—唯一の選択肢は、非クライアント領域を含むウィンドウ全体をカスタム描画することです。これは確かに可能であり、実際にはWindows 10電卓のようにUWPアプリ(以前はMetroと呼ばれていました)が行うことですが、多くの非クライアントウィジェットを使用し、ネイティブに見えることを期待するデスクトップアプリケーションでは実行可能なオプションではありません。

それはさておき、それは明らかに誤りです。カスタム描画のタイトルバーできません Windowsシェルチームが行ったため、正しい動作を得る唯一の方法です。控えめな実行ダイアログは期待どおりに動作し、異なるDPIを持つモニター間でドラッグすると、クライアント領域と非クライアント領域の両方を適切にサイズ変更します。

Spy ++による調査により、これは単なる沼地の標準のWin32ダイアログであることが確認されています。すべてのコントロールは、標準のWin32 SDKコントロールです。これはUWPアプリではなく、タイトルバーをカスタム描画したものでもありません。WS_CAPTIONスタイルのままです。 isモニターごとの高DPI対応としてマークされているExplorer.exeプロセスによって起動されます(プロセスエクスプローラーとGetProcessDpiAwarenessで検証)。 このブログ投稿 は、Windows 10で[実行]ダイアログとコマンドプロンプトの両方が正しくスケーリングされるように書き直されたことを確認します(「コマンドシェルなど」を参照)。 タイトルバーのサイズを変更するために実行ダイアログは何をしていますか?

Common Item Dialog API は、新しいスタイルの[開く]および[保存]ダイアログを担当します。また、モニターごとに高DPI対応のプロセスから起動すると、[参照]をクリックすると正しくスケーリングされます。 「実行」ダイアログの「」ボタン。 Task Dialog API についても同じですが、作成する アプリが異なるサイズのタイトルバーでダイアログボックスを起動する奇妙な状況 を作成します。 (ただし、従来のMessageBox APIは更新されておらず、テストアプリと同じ動作を示します。)

シェルチームがそれを実行している場合、それが可能でなければなりません。モニターごとのDPIサポートの設計/実装を担当するチームが無視したとは想像できません。開発者が互換性のあるアプリケーションを作成するための合理的な方法を提供するため。このような機能は、開発者のサポートが必要であるか、そのまま使用できるものです。 WPFアプリケーションさえ壊れている— Microsoftの Per-Monitor Aware WPF Sample プロジェクトは非クライアント領域の拡大に失敗し、その結果、タイトルバーのサイズが間違っています。私は陰謀論にはあまり向いていませんが、これはデスクトップアプリの開発を阻止するためのマーケティングの動きの匂いです。その場合、公式な方法がないので、文書化されていない動作に依存する回答を受け入れます。

文書化されていない動作といえば、実行ダイアログが異なるDPI設定のモニター間でドラッグされたときにウィンドウメッセージを記録すると、文書化されていないメッセージ0x02E1を受け取ることが示されます。このメッセージIDは、ドキュメントに記載されている WM_DPICHANGED メッセージ(0x02E0)よりも1つ大きいため、これはやや興味深いものです。ただし、テストアプリは、DPI対応の設定に関係なく、このメッセージを受け取ることはありません。 (不思議なことに、詳細な検査により、ウィンドウわずかにがウィンドウが高DPIモニターに移動すると、タイトルバーの最小化/最大化/閉じるグリフのサイズが大きくなることがわかります。これらはまだそれほど大きくありません。仮想化されたときと同じですが、スケーリングされていないシステムDPIアプリケーションで使用されるグリフよりもわずかに大きくなります。)

これまでのところ、私の最良のアイデアは、非クライアント領域のサイズを調整するために WM_NCCALCSIZE メッセージを処理することでした。 SetWindowPos functionSWP_FRAMECHANGEDフラグを使用することにより、WM_DPICHANGEDに応答してウィンドウのサイズを変更し、非クライアント領域を再描画することができます。 これは、タイトルバーの高さを減らすか、完全に削除するためにうまく機能しますが、決して高くなることはありません 。キャプションは、システムのDPIによって決定される高さでピークになるようです。それが機能したとしても、システム描画のメニューバーやスクロールバーには役に立たないため、これは理想的なソリューションではありません。他のアイデア?

*この記事では "モニターごとの非クライアント領域-DPI対応アプリケーションはWindowsによってスケーリングされず、高DPIディスプレイではそれに比例して小さく表示されることに注意してください。" (1)誤りであり、(2)不十分である理由については、上記を参照してください。クライアント以外の領域をカスタム描画する以外の回避策を探しています。

36
Cody Gray

最新のWindows Insiderビルド(ビルド> = 14342、SDKバージョン#> = 14332)には、最上位のHWNDの非クライアントDPIスケーリングを有効にするEnableNonClientDpiScaling API(HWNDを引数として受け取ります)があります。 。この機能では、最上位ウィンドウがモニターごとのDPI認識モードで実行されている必要があります。このAPIは、ウィンドウのWM_NCCREATEハンドラーから呼び出す必要があります。このAPIがトップレベルウィンドウで呼び出されると、そのキャプションバー、トップレベルスクロールバー、システムメニュー、およびメニューバーは、アプリケーションのDPIが変更されるとDPIスケーリングします(これは、アプリが異なるディスプレイスケーリングのディスプレイに移動されたときに発生する可能性があります)値、またはユーザーが設定を変更したり、RDP接続がスケール係数を変更したりするなど、他の理由でDPIが変更された場合)。

このAPIは、子ウィンドウのスクロールバーなど、子ウィンドウの非クライアント領域のスケーリングをサポートしていません。

私の知る限り、このAPIなしで非クライアント領域のDPIを自動的に拡大する方法はありません。

このAPIはまだ確定されておらず、Windows 10 Anniversaryアップデートでリリースされる前に変更される可能性があることに注意してください。最終版になりましたら、MSDNの公式ドキュメントをチェックしてください。

26
peterfelts

Windows 10 Creators Update(ビルド15063)のPer Monitor V2 DPI認識を使用すると、EnableNonClientDpiScalingなしでこれを簡単に解決できます。

Per Monitor V2 DPI認識を有効にしながら、古いPer Monitor DPI認識を古いWindows 10ビルドとWindows 8.1およびさらに古いバージョンのWindowsではDPI認識を使用して、アプリケーションを次のようにマニフェストにします。

<Assembly ...>
    <!-- ... --->
    <asmv3:application>
        <asmv3:windowsSettings xmlns="http://schemas.Microsoft.com/SMI/2005/WindowsSettings">
            <dpiAware>True/PM</dpiAware>
            <dpiAwareness xmlns="http://schemas.Microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
        </asmv3:windowsSettings>
    </asmv3:application>
</Assembly>

参照:

8
Martin Prikryl