Edited at

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

More than 1 year has passed since last update.


はじめに

 マグネットウィンドウというウィンドウがくっつくアプリを自作してて、自分自身長年愛用しています。

 ただここ数年はほぼほぼノーメンテで。機能追加やこのウィンドウが動かないあのウィンドウが動かないーというお話を頂きますが全然お返事すら出来ていませんすみません。機能追加というよりむしろ削って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設定にかかわらずDwmGetWindowAttributeのDWMWA_EXTENDED_FRAME_BOUNDSは物理座標系を返す。

  • アプリのDPI設定にかかわらずGetCursorPosとGetPhysicalCursorPosは論理座標系を返す。


参考