質問者
GDIアプリとマルチディスプレイ(異なるDPI)

質問
-
MFC+GDIで描画アプリを開発しております。(VC++2019)
マッピングモードは、MM_LOMETRICを使用。マルチディスプレイのとき、デバイスコンテキストは、メインディスプレイ(4K/17inch)の解像度が使用されるので、
サブディスプレイ(1280/1024/17inch)ので描画処理を行うと、線の幅を1ミリ(論理値:10)としたとき、
サブディスプレイでは、かなり太く描画されます。サブディスプレイに移った時に、サブディスプレイ側のデバイスコンテキストを使用するようにしたいのですが、
可能でしょうか。可能な場合、アドバイスいただけたらと思います。
すべての返信
-
ウィンドウがサブディスプレイに移ったタイミングでトップレベルウィンドウに WM_DPICHANGED が送られてくるので、そのタイミングでディスプレイのデバイスコンテキストを取得するのがよいかと思います。
MainFrm.h afx_msg LRESULT OnDpichanged(WPARAM, LPARAM); // 追加
MainFrm.cpp
ON_MESSAGE(WM_DPICHANGED, &CMainFrame::OnDpichanged) // 追加
LRESULT CMainFrame::OnDpichanged(WPARAM wParam, LPARAM lParam) // 追加
{
HMONITOR hMonitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
MONITORINFOEX monitorInfo = {};
monitorInfo.cbSize = sizeof(MONITORINFOEX);
GetMonitorInfo(hMonitor, &monitorInfo);
HDC hdc = CreateDC(L"DISPLAY", monitorInfo.szDevice, NULL, NULL);
//ここで必要な情報を取得し、必要に応じて再描画する
DeleteDC(hdc);
// DPIを取得したい場合は下記で取得できる
int nDPI = static_cast<int>(HIWORD(wParam));
return 0;
}
追伸。DPIが異なるディスプレイに対してプログラム側でDPIを認識して対応するには、マニフェストでの宣言が必要でした。プロジェクトのプロパティで「マニフェスト ツール」>「入出力」>「DPI 認識」で「モニターごとの高いDPI 認識」を選択してください。
- 編集済み kenjinoteMVP 2022年8月27日 6:02 マニフェストについて追記
-
モニタごとのDPIを利用するには、HighDPI対応が必要になります。
公式だとこのあたりかな?
Windowsでの高 DPI デスクトップ アプリケーション開発 - Win32 apps | Microsoft Docs
ほかにも、いろいろと検索してみてください。ここ10年くらいの記事であれば、WPF以外のものは .NET Framework(WinForms)の話であっても、C++でそのまま利用できるなどもあるのでかなり役に立つと思います。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
-
プロセスの既定の DPI 認識を設定する に記載されていますが、manifest の設定が必要です。現在なら、PerMonitorV2 を指定しておくのがよいと思います。
ただし、この通知はトップレベルウィンドウ(CMainFrameなど)にしか通知が来ないので、CView などは別途内部的に処理コードを実装する必要があります。
とっちゃん@わんくま同盟, Visual Studio and Development Technologies http://blogs.wankuma.com/tocchann/default.aspx
-
マニフェストファイルを作成して、「プロセスの既定の DPI 認識を設定する」に記載されている内容をコピーし、
追加のマニフェストに追加しました。
procexpにて、「DPI Awareness」列を追加して確認したところ、「Par-Monitor Aware」となっていました。
※EXEファイルのリソースも確認したところ実装されていました。しかし、メッセージが来ません。
-
ディスプレイのDPIが異なる想定で回答をしていましたが(DPIの変更の参考ページ)、ディスプレイのDPIの値が同じでしたらディスプレイ間を移動してもWM_DPICHANGEDメッセージは来ません。
その場合は、MainFrmウィンドウのOnMove(WM_MOVEイベント)でディスプレイが変わったかどうかで判定する方法はいかがでしょうか?例えば、OnMove関数内でMonitorFromWindow関数を呼び出し、その値を保持しておき、変更された値が来た場合は異なるディスプレイに移動したと判断して、上記のコードのGetMonitorInfo関数等でディスプレイのデバイスコンテキストを取得する。
-
上記参考にさせていただきます。
が、WM_DPICHANGEDをまず受信してみたいのですが、
21.5inch(1920*1080)->17inch(800*600)としみましたが、メッセージきません。しかし、21.5inch(1920*1080)->17inch(1280*1024)で
17inch側の「テキスト、アプリ、その他の項目サイズを変更する」を、「150%」で試してみましたら、
WM_DPICHANGEDが受信できました。21.5inch(1920*1080)->17inch(800*600*100%(推奨))->メッセージが飛んでこない
21.5inch(1920*1080)->17inch(1280*1024*150%)->メッセージが飛んでくるこの違いを理解できません。
- 編集済み Brillia 2022年8月29日 3:49
-
21.5inch(1920*1080 DPI=100%) <-> 17inch(800*600 DPI=100%) はDPIが同じなのでメッセージは飛んできません。
21.5inch(1920*1080 DPI=100%) <-> 17inch(1280*1024 DPI=150%) はDPIが異なるのでメッセージが飛んできます。
「テキスト、アプリ、その他の項目サイズを変更する」の値をDPIと呼んでいたのがわかりにくかったですね。下記のような対応付けになります。
DPI 値 スケーリングの割合
96 100%
120 125%
144 150%
192 200%参考:https://docs.microsoft.com/ja-jp/windows/win32/hidpi/wm-dpichanged
- 編集済み kenjinoteMVP 2022年8月29日 8:08
-
私の理解不足なのかもしれませんが、教えてください。
GDI、MM_LOMETRICを使用して直線を5センチ描画しているとします。
メインディスプレイ:21.5inch(1920*1080*100%)=>5センチで描画される
サブディスプレイ:17inchi(1280*1024*100%)=>5.3センチで描画される(5センチで描画したい)これがなぜなのかを調べたところ、下記の結果に行き着いたのですが、間違っていますでしょうか。
またサブディスプレイで描画したときに、
サブディスプレイの解像度で指定した論理値が反映できることが可能なのかをしりたいのです。
メインディスプレイの解像度は、102DPI
サブディスプレイの解像度は、96DPI
102/96=1.0625
50*1.0625=53.125
教えてください。
-
96[dpi]とは「画面上の96[dot]を25.4[mm]に換算します」といっているだけです。これは1[dot]あたり約0.2646[mm]にあたります。
同じ様にMM_LOMETRICでは1[dot]あたり0.1[mm]に換算しますといっているだけです。
モニターの場合には、出力は換算結果のdot数で出力されます。物理長さではありません。
つまり、23インチモニターと27インチモニターでは、横のdotが両方とも1280であっても、1ドットの物理サイズが異なるため、見た目の長さは一致しません。
もちろん旧来からOSはモニターのサイズを認識していないので当然です。
では、この比率は何に出力したなら意味を持つのかですが、これは紙に対する印刷です。
A4等の紙は物理サイズに意味を持つのでプリンターは逆に物理長さに対する印刷のドット数を規定しています。
昔はかなりいい加減でしたが600[dpi]のインクジェットあたりからまぁまぁの値になりました。
もちろんモニターの方も相当いい加減です。
メジャーで測れば様々な大きさの「23インチモニター」が存在することがわかりますし、
そもそも縦横比が色々なので対角のインチ数の意味が薄れています。
画面が曲面のものもあります。ということで、残念ですが、普遍的なプログラム手法で画面上に物理長さを正確に表現することはできません。
- 編集済み 仲澤@失業者 2022年8月29日 8:04