Windows 8.1 では、モニターごとに異なる DPI 設定を使用できる機能が導入されました。この機能は、「モニターごとの高 DPI サポート」と呼ばれます。それは持続し、Windows 10 でさらに洗練されました。
アプリケーションがオプトインしない場合 (つまり、DPI 非対応または高 DPI 対応の場合)、アプリケーションは DWM によって適切な DPI に自動的にスケールアップされます。ほとんどのアプリケーションは、Windows にバンドルされているほとんどのユーティリティ (メモ帳など)を含め、これら 2 つのカテゴリのいずれかに分類されます。私のテスト システムでは、高 DPI モニターは 150% スケール (144 DPI) に設定され、通常のモニターはシステム DPI (100% スケール、96 DPI) に設定されています。したがって、高 DPI 画面でこれらのアプリケーションのいずれかを開く (またはそこにドラッグする) と、仮想化が開始され、すべてが拡大されますが、信じられないほどぼやけてしまいます。
一方、アプリケーションがモニターごとの高 DPI をサポートすることを明示的に示している場合、仮想化は実行されず、開発者はスケーリングを担当します。マイクロソフトはここにかなり包括的な説明をしています*が、自己完結型の質問のために、要約します。まず、<dpiAware>True/PM</dpiAware>
マニフェストに設定することでサポートを示します。これにより、新しい DPI 設定と、ウィンドウの推奨される新しいサイズと位置の両方を通知するWM_DPICHANGED
メッセージの受信が選択されます。また、 GetDpiForMonitor関数を呼び出して実際のDPI を取得することもできます。互換性の理由で嘘をつくことはありません。Kenny Kerr も包括的なチュートリアルを作成しています。
小さな C++ テスト アプリで、これらすべてを正常に実行できました。これは多くの定型文であり、ほとんどがプロジェクト設定であるため、ここに完全な例を投稿してもあまり意味がありません。テストする場合は、Kenny の指示に従うか、MSDN のこのチュートリアルに従うか、公式の SDK サンプルをダウンロードしてください。これで、クライアント領域のテキストはきれいに見えますが (私の処理のおかげでWM_DPICHANGED
)、仮想化が実行されなくなったため、非クライアント領域のスケーリングはありません。その結果、タイトル/キャプション バーとメニュー バーのサイズが正しくありません。高 DPI 画面では大きくなりません。
問題は、ウィンドウの非クライアント領域を新しい DPI に合わせるにはどうすればよいかということです。
独自のウィンドウ クラスを作成するか、ダイアログを使用するかは問題ではありません。この点では同じ動作をします。
非クライアント領域を含むウィンドウ全体をカスタム描画するしかないという答えはありません。これは確かに可能であり、Windows 10 Calculator などの UWP アプリ (以前は Metro と呼ばれていたもの) が実際に行っていることですが、多くの非クライアント ウィジェットを使用し、ネイティブに見えることを望んでいるデスクトップ アプリケーションにとっては、実行可能なオプションではありません。
それは別として、それは明らかに誤りです。カスタム描画のタイトル バーが正しい動作を実現する唯一の方法ではありません。Windows シェル チームがそれを行ったからです。謙虚な実行ダイアログは期待どおりに動作し、DPI が異なるモニター間でドラッグすると、クライアント領域と非クライアント領域の両方のサイズが適切に変更されます。
Spy++ を使用して調査した結果、これは標準的な Win32 ダイアログにすぎず、特別なものではないことが確認されました。すべてのコントロールは、標準の Win32 SDK コントロールです。これは UWP アプリではなく、カスタム描画されたタイトル バーでもありませんWS_CAPTION
。スタイルはそのままです。これは、モニターごとの高 DPI 対応としてマークされている explorer.exe プロセスによって起動されます (Process Explorer および 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 モニターに移動すると、Windowsはタイトル バーの最小化/最大化/閉じるグリフのサイズをわずかに大きくすることがわかります。仮想化されているときほど大きくはありません。 、ただし、スケーリングされていないシステム DPI アプリケーションに使用されるグリフよりもわずかに大きいです。)
これまでのところ、私の最善のアイデアは、WM_NCCALCSIZE
メッセージを処理して非クライアント領域のサイズを調整することでした。関数SWP_FRAMECHANGED
でフラグを使用することにより、ウィンドウのサイズを強制的に変更し、 に応答して非クライアント領域を再描画することができます。これは、タイトル バーの高さを減らしたり、完全に削除したりするのには問題なく機能しますが、それ以上高くなることはありません。キャプションは、システム DPI によって決定される高さでピークに達するようです。うまくいったとしても、システム描画のメニュー バーやスクロール バーには役立たないので、これは理想的な解決策とは言えませんが、少なくとも最初の一歩にはなるでしょう。他のアイデア?SetWindowPos
WM_DPICHANGED
* この記事には、「モニターごとの DPI 対応アプリケーションの非クライアント領域は Windows によってスケーリングされず、高 DPI ディスプレイではそれに比例して小さく表示されることに注意してください」と記載されていることは知っています。それが (1) 間違っていて、(2) 不十分である理由については、上記を参照してください。非クライアント領域をカスタム描画する以外の回避策を探しています。