Last time, we tried to create a DisplayInformation
from an HWND
, but it failed with the message “A DispatcherQueue is required for DisplayInformation created from an HWND.”
So I guess we need to make sure the thread has a DispatcherQueue
.
#include <DispatcherQueue.h> #include <windows.graphics.display.interop.h> #include <winrt/Windows.Graphics.Display.h> #include <winrt/Windows.Foundation.h> #include <winrt/Windows.System.h> namespace winrt { using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::System; } namespace ABI { using namespace ABI::Windows::System; } winrt::DispatcherQueueController g_controller{ nullptr }; winrt::DisplayInformation g_info{ nullptr }; OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) noexcept { DispatcherQueueOptions options{ sizeof(options), DQTYPE_THREAD_CURRENT, DQTAT_COM_NONE }; winrt::check_hresult( CreateDispatcherQueueController(options, reinterpret_cast<ABI::IDispatcherQueueController**>( controller.put()))); g_info = wil::capture_interop<winrt::DisplayInformation> (&IDisplayInformationStaticsInterop::GetForWindow, hwnd); return TRUE; } catch (...) { return FALSE; } winrt::fire_and_forget OnDestroy(HWND hwnd) { g_info = nullptr; co_await g_controller.ShutdownQueueAsync(); PostQuitMessage(0); }
We use CreateDispatcherQueueController
to attach a dispatcher queue to our existing message pump. There is a bit of a hassle with the second parameter because we are mixing the C++/WinRT and Win32 ABI versions of the IDispatcherQueueController
interfaces. (Previous discussion.) And since we created the dispatcher queue controller, we also have to shut down the dispatcher queue when we’re done, which we take care of in OnDestroy()
.
Hooray, this code now successfully creates a DispatcherQueueController
that manages a DispatcherQueue
on the current thread, and that removes the obstacle that was preventing GetForWindow
from succeeding.
Now we can hook up the event and start reading the orientation.
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs) noexcept { DispatcherQueueOptions options{ sizeof(options), DQTYPE_THREAD_CURRENT, DQTAT_COM_NONE }; winrt::check_hresult( CreateDispatcherQueueController(options, reinterpret_cast<ABI::IDispatcherQueueController**>( controller.put()))); g_info.OrientationChanged([hwnd](auto&&, auto&&) { InvalidateRect(hwnd, nullptr, TRUE); }); g_info = wil::capture_interop<winrt::DisplayInformation> (&IDisplayInformationStaticsInterop::GetForWindow, hwnd); return TRUE; } catch (...) { return FALSE; } void PaintContent(HWND hwnd, PAINTSTRUCT* pps) noexcept { PCWSTR message; switch (g_info.CurrentOrientation()) { case winrt::DisplayOrientations::Landscape: message = L"Landscape"; break; case winrt::DisplayOrientations::Portrait: message = L"Portrait"; break; case winrt::DisplayOrientations::LandscapeFlipped: message = L"LandscapeFlipped"; break; case winrt::DisplayOrientations::PortraitFlipped: message = L"PortraitFlipped"; break; default: message = L"Unknown"; break; } TextOut(pps->hdc, 0, 0, message, static_cast<int>(wcslen(message))); }
After creating the DisplayInformation
, we register for the orientation change event and force a repaint when that happens.
Our paint handler simply prints the current orientation of the monitor that the window is on.
I won’t bother demonstrating it here, but you can also use GetForMonitor
to get a DisplayInformation
for a specific monitor.
If I’m just tracking the monitor of the window, can I achieve it by listening for WM_DeviceChange, WM_DisplayChange, WM_WindowPosChanging, WM_WindowPosChanged?
Sure. But this question was about detecting display orientation. You can try to infer portrait/landscape by looking at the dimensions, but that won’t distinguish between portrait and portrait-flipped, for example. (dmOrientation works only for printers.)
So, what is the best way to have a desktop application window with client area and some controls in it staying in the same physical place as screen rotates, while icons/text on these controls would follow the screen orientation?
Most of the time, “nothing”. A screen rotation is a special case of resolution changes, so most apps and Windows already keep themselves/them at sane locations. Also, most apps only have to care about its window size, not the screen size/orientation.
I think the fields are dmPelsWidth, dmPelsHeight, dmDisplayOrientation. According to the docs of Windows.Graphics.Display.DisplayOrientations and DEVMODEW, the result of GetcurrentOrientation should be consistent with the following:
<code>
I made a complete example: https://gist.github.com/GeeLaw/11799094e137a4f60590a5b7162ba933
As things turned out, it is quite safe to ignore anything related to UWP and stick to plain Win32.
I don’t think any of those tell you the orientation, though? if they do, jeeze that would be easier than the nonsense in today’ s post.
You make use of ChangeDisplaySettingsEx () and EnumDisplaySettingsEx() family of functions, DEVMODE structure.
The union field dmDisplayOrientation has what you’re looking for.
That has been my strategy, just for the sheer sanity. Whenever I see new UWP functionality my first thought experiment is: “What Win32 APIs are they using for that?”
What’s the reason that DisplayInformation needs a Queue? Is DispatcherQueue some sort of equavalent of message pump and DisplayInformation *thing* uses it to be able to serialize some calls across the threads, because DisplayInformation does its work on some ThreadPool and not on STA? Or am i confusing myself
I notice you talk about “the” monitor the window is on as if there is one and only one. The DisplayInformation documentation, too, mentions “if your app is moved from one monitor to another monitor” as if that’s an unambiguous atomic event such that one moment the window is on one monitor and the next moment suddenly on another.
You read the docs, and missed the line "A DisplayInformation instance does not map to a specific display, but instead tracks display-related information for wherever the application view is placed."
My understanding is, it doesn't try to deal with monitor info, especially multimonitor info if your window is displayed across multiple monitors.
It only deals with the properties that are applied to the window at that time. And only one monitor's properties can be applied to the...