LoginSignup
11
17

More than 3 years have passed since last update.

高DPI環境におけるWindowsAPI/MFCのウィンドウ矩形・マウス座標、DPIの取れかたの違いなど

Last updated at Posted at 2018-04-13

はじめに

 マグネットウィンドウというウィンドウがくっつくアプリを自作してて、自分自身長年愛用しています。
 ただここ数年はほぼほぼノーメンテで。機能追加やこのウィンドウが動かないあのウィンドウが動かないーというお話を頂きますが全然お返事すら出来ていませんすみません。機能追加というよりむしろ削ってC#+C++で書き直すべく水面下でやってますがほとんど進んでません…(やる気が…)。
 話は脱線しましたが、私の開発環境もおそまきながら高DPI環境になり、マグネットウィンドウが高DPI未対応だったので重い腰を上げることにしました。
 ということで高DPIの調査をしていてある程度まとまったのでメモとして残します。
 なおここで取り扱うのは自作アプリ用調査なので主にウィンドウ座標系です。

テスト環境・条件

  • Windows 10 pro (1709)
  • モニター 23inch FHD(1920x1080)
  • 調査用のソフトはGithubにバイナリとソース置きました。
  • 調査用ソフトを起動してウィンドウ左上座標を0, 0にする。
  • ディスプレイ設定の拡大縮小で100%と150%にしたときの調査用ソフトのawareタイプとunawareタイプでそれぞれの矩形情報を読み取り、論理座標(拡大された時の仮想的な座標系)と物理座標(モニターの物理的な解像度をもとにした座標系)の値がどのように変化するかを観察する。

ソフトの表示の説明

名前 概要
Desktop このアプリがあるデスクトップ矩形とサイズ
MDT_RAW_DPI GetDpiForMonitorで取得。デバイスから取得した情報から計算された本当のDPI。sqrt(Width * Width + Height * Height) / inchで求まります
MDT_EFFECTIVE_DPI GetDpiForMonitorで取得。WindowsUIで認識しているDPI。100% = 96dpiです。拡大率 = 表示DPI / Windows基準DPI(96) * 100で求まります
GetCursorPos カーソル座標
->L2P カーソル座標を物理座標系に変換(LogicalToPhysicalPointForPerMonitorDPI)
GetPhysicalCursorPos カーソル座標
OnMovingArg MFCのOnMoving(SDKでいうWM_MOVING)の引数の矩形座標。GetWindowRectと同じものが入っている
->L2P 上記の矩形座標を物理座標系に変換(LogicalToPhysicalPointForPerMonitorDPI)
DwmGetWindowAttribute DWMWA_EXTENDED_FRAME_BOUNDSを指定して取得したこのウィンドウの矩形座標
->P2L 上記の矩形座標を論理座標に変換(PhysicalToLogicalPointForPerMonitorDPI)

表示:100% / DPI設定:モニターごとの高いDPI認識

100_aware.PNG

MDT_RAW_DPI
自分で計算しても95DPIになりました
GetCursorPos、->L2P、GetPhysicalCursorPos
100%なので変換しても変化なし
OnMovingArg、->L2P
100%なので変換しても変化なし
DwmGetWindowAttribute、->P2L
Windows10系は見えない部分もウィンドウの矩形となっているようです(サイズ変更時の判定範囲を広げつつ枠自体は1ピクセルで表示するためのよう)

表示:100% / DPI設定:なし

100_unaware.PNG

マウス座標は違いますが「表示:100%、DPI設定:モニターごとの高いDPI認識」と同じ結果になりました。100%なので当然の結果ですね。

表示:150% / DPI設定:モニターごとの高いDPI認識

150_aware.PNG

Desktop
150%ですが、解像度自体はもとのままです
MDT_RAW_DPI
100%と同じ
MDT_EFFECTIVE_DPI
144という数値になりました。150% = 144 / 96 * 100で辻褄が合ってますね
GetCursorPos、->L2P、GetPhysicalCursorPos
高DPI対応のため同じ数値
OnMovingArg、->L2P
高DPI対応のため同じ数値
DwmGetWindowAttribute、->P2L
高DPI対応のため同じ数値

高DPI対応にすると論理座標と物理座標がイコールになるため矩形情報などはすべて100%と同じ情報が返りますね。

表示:150% / DPI設定:なし

150_unaware.PNG

Desktop
デスクトップ自体が150%拡大したような座標になります。GetCursorPosやGetWindowRectなどもこの矩形領域で動作しています
MDT_RAW_DPI
てっきり物理的に表示可能なDPIを表示するものと思ってたのですが違いました。表示解像度を入れて計算したら63DPIになりました。
MDT_EFFECTIVE_DPI
てっきり150%になると思っていたのですが、Unawareの場合96DPIで表示していると思い込ませるために変化させないみたいですね
GetCursorPos
->L2P、GetPhysicalCursorPos:GetCursorPosが論理座標取得で、GetPhysicalCursorPosが物理座標取得と思っていたのですが同じでした。
->L2Pを行うと物理座標と一致しました
OnMovingArg、->L2P
表示は拡大されてるけど矩形の大きさ自体は100%と同じですね。
->L2Pを行うと物理座標と一致しました
DwmGetWindowAttribute、->P2L
DwmGetWindowAttributeはどうも物理座標系で取得出来るみたいですね
->P2Lを行うと、DWM矩形の物理座標系を論理座標系に変換する。矩形は100%時のものと一致。

まとめ

scale aware per monitor unaware
100% 100_aware.PNG 100_unaware.PNG
150% 150_aware.PNG 150_unaware.PNG
  • DPI設定なしで100%以外だとサイズが変わる(物理座標系を返す。)
  • アプリのDPI設定にかかわらずOnMovingの引数は100%と同じ
  • アプリのDPI設定にかかわらずDwmGetWindowAttributeのDWMWA_EXTENDED_FRAME_BOUNDSは物理座標系を返す。
  • アプリのDPI設定にかかわらずGetCursorPosとGetPhysicalCursorPosは論理座標系を返す。

追記:
 マグネットウィンドウはアプリのウィンドウ同士をくっつけるためにウィンドウの座標やサイズが重要になってくるんですが、アプリによってDPI設定がまちまちな上に取得できる値がどうもおかしい、というのが調査のきっかけです。
 調査結果をもとに、GetProcessDpiAwareness APIでunaware(DPI設定なし)ならマウス座標やOnMovingその他の論理座標系で取れてしまうものを物理座標系に変換して内部処理し、一部は元の論理座標系に戻して返すなどをしています。

参考

11
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
17