モノトーンの伝説日記

OBS Studio と Blackmagic Design が大好き。

Per-monitor DPI 再考 ~Windows 10 を迎える前にもう一度チェック!~

 今日は Per-monitor DPI について調査し、再考していくという企画。結構めんどくさいですが、標準のリファレンス実装的な File Explorer にもとうとう Per-monitor DPI が実装されたようですので、それを基準に見ていこうと思います。

 なお、詳細検証等は以下の記事が詳しいです。こちらも参考にしてください。

WPFにおけるPer-Monitor DPI対応の実装

概要

  1. Per-monitor DPI 変更の 3 つのケース
    1.1 静止およびリサイズ状態での変更
    1.2 移動状態での変更
  2. 変更ステートの取り扱い
  3. まとめ
  4. 具体的な実装方法 (追記分)

1. Per-monitor DPI 変更の 3 つのケース

 Per-monitor DPI における WM_DPICHANGED による DPI 変更のケースは 3 つの場合が考えられます:

  1. ウィンドウが静止している状態
     …設定画面で設定を変更した直後にイベントが発生します。
  2. ウィンドウが移動 (タイトルバークリックして) している状態
     …ウィンドウが移動し現在の横幅の半分以上が別モニターに移ったときに発生
  3. ウィンドウがリサイズ (ウィンドウ枠をクリックして) している状態
     …ウィンドウがリサイズし現在の横幅の半分以上が別モニターに移ったときに発生

この 3 つにおけるケースはどれも違いますが、1, 3 は実装的に容易です。まずそこから見ていきましょう。

1.1. 静止およびリサイズ状態での変更

 Windows 10 の File Explorer 実装を触ってもらうとわかりますが、基本的にウィンドウのリサイズはせずコンテンツ DPI だけ変更する実装になってます (リサイズ中にウィンドウ枠サイズいじると大変なことになるしね)。

 ただ、静止状態での DPI 変更は何故か位置まで移動してるっぽいですが… (もしかしたらウィンドウ枠も拡大するほうがシステム挙動としていいのかもしれない)

1.2. 移動状態での変更

 移動状態では Windows 10 File Explorer でもウィンドウサイズをいじってるわけですが、いろいろと遊んでもらうとわかるんですが、「必ず DPI が再変更されないような位置に来たときにリサイズ処理を行う」ということをやっています。

 これはどういうことかというと、DPI 変更はモニターの半分を超えたときに通知が来るわけですが、通知が来た瞬間にウィンドウリサイズすると再びウィンドウがモニターの半分を超えてない状態になって、また元のサイズに戻って(ry っていうような無限ループ状態になってしまうことが容易に想像できます。これを対策するために、expected size がウィンドウの半分を超えたときになったら DPI を変更するという処理が必要です。

2. 変更ステートの取り扱い

 WM_NCHITTEST (https://msdn.microsoft.com/en-us/library/windows/desktop/ms645618.aspx) をとらえるのが一番楽でしょう。これを使えばどこの non-client がクリックされてるのかわかるので、それに応じて状況を分岐することができます。タイトルバーのときは state = reposition、ウィンドウ枠のときは state = resize です。それ以外の状況で DPI 変更が来た場合、設定変更されたということになります。

3. まとめ

 Windows 10 は Universal Windows Platform apps が前面に押し出されてますが、個人的にあんな何もできない環境でデスクトップ的なアプリ開発しても面白くないと思っており、やっぱり UWP app はタッチ向けアプリ開発するとき向けだよねー! って思ってるので、こんな記事を書いてみました。

 いまさらですがなんでこんなことしてるのかというと、MntoneUIFramework (GitHub) というものを開発してていろいろ Windows 10 上で動作を検証しているという感じになります。Windows 10 はまだ File Explorer ぐらいしか Per-monitor DPI 実装されてるのは確認できてませんがほかにあるんでしょうか? あったら教えてください。

 ということで以上です。

4. 具体的な実装方法 (追記分)

 実際に実装してみたので、それについて。

ll. 338-357

https://github.com/mntone/MntoneUIFramework/blob/master/MntoneUIFramework/source/window.cpp#L338-357

 WM_NCHITEST をとらえて先に DefWindowProcW に投げそしてその値で今どの境界線が触れられているのかを特定することで、「境界線によるリサイズ」なのかを特定しています。

 なお、設定画面からの変更の場合 WM_ENTERSIZEMOVE が飛んできませんからこの 2 つのフラグを監視することで十分に実装できます。

 以上。