Using the Display­Information class from a desktop Win32 application, part 2

Raymond Chen

Last time, we tried to create a Display­Information 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 Dispatcher­Queue.

#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 Create­Dispatcher­Queue­Controller 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 IDispatcher­Queue­Controller 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 Dispatcher­Queue­Controller that manages a Dispatcher­Queue on the current thread, and that removes the obstacle that was preventing Get­For­Window 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 Display­Information, 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 Get­For­Monitor to get a Display­Information for a specific monitor.

 

13 comments

Discussion is closed. Login to edit/delete existing comments.

  • Yuri Khan 0

    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.

    • Chris Iverson 1

      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 window at one time, even if the window shows up on multiple monitors.

      You can prove this yourself with an easy example: a DPI-aware application and two monitors with different DPIs. If you position the DPI-aware window across the boundary between those two monitors, it won’t apply both DPIs. Instead, the DPI that will be applied to the window seems to be the DPI of the monitor that has the largest amount of the window on it. (In other words, the monitor that would be chosen by the MonitorFromWindow() API call.)

      “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”

      From the point of view of the above behavior(like the MonitorFromWindow() API call), there is such a moment: when the largest amount of window changes from one monitor to another. Using that DPI example, you can see this yourself by constantly moving the DPI-aware window around over the border between monitors. As the majority of the window area changes from one monitor to another, you will see the window change size, as the new DPI takes effect. And you will see that change back and forth as you move the window across back and forth.

  • Sigge Mannen 0

    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

  • GL 0

    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?

    • Paulo Pinto 3

      As things turned out, it is quite safe to ignore anything related to UWP and stick to plain Win32.

      • Joe Beans 2

        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?”

      • alan robinson 0

        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.

        • Paulo Pinto 1

          You make use of ChangeDisplaySettingsEx () and EnumDisplaySettingsEx() family of functions, DEVMODE structure.

          The union field dmDisplayOrientation has what you’re looking for.

    • Raymond ChenMicrosoft employee 0

      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.)

      • GL 1

        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:

        DEVMODEW dm { .dmSize = sizeof(DEVMODEW) };
        if (!EnumDisplaySettingsExW(
          nullptr,
          ENUM_CURRENT_SETTINGS,
          &dm,
          EDS_RAWMODE | EDS_ROTATEDMODE)
        ) throw "EnumDisplaySettngsEx call";
        if ((dm.dmFields & (DM_PELSWIDTH | DM_PELSHEIGHT))
          != (DM_PELSWIDTH | DM_PELSHEIGHT)
        ) throw "EnumDisplaySettngsEx fields";
        dm.dmDisplayOrientation &= (
          (dm.dmFields & DM_DISPLAYORIENTATION) == DM_DISPLAYORIENTATION
          ? 3 : 0
        ); // ^^^ I am not sure if this guard is necessary.
        
        // Infer the orientation at rotation degree 0.
        auto const initial = (
          // dmPelsWidth <=> dmPelsHeight  is landscape/portrait after rotation.
          // dmDisplayOrientation          is rotation / (-pi/2).
          ((dm.dmPelsWidth > dm.dmPelsHeight) ^ (dm.dmDisplayOrientation & 1))
          ? 1 // Landscape in DisplayOrientations
          : ((dm.dmPelsWidth < dm.dmPelsHeight) ^ (dm.dmDisplayOrientation & 1))
          ? 2 // Portrait
          : 3 // Landscape | Portrait
          /* The documentation does not specify what happens
          ** when the resolution is square.
          ** Since DisplayOrientations is a [Flags] enumeration,
          ** the sane result is a bitwise combination of both.
          */
        );
        // Compute the orientation at current rotation.
        // The significance of bits in DisplayOrientation
        // is modulo 4 (plus 1 for every 90 degree clockwise),
        // consistent with the numeric value of dmDisplayOrientation.
        auto const current = ((
          (initial << dm.dmDisplayOrientation) |
          (initial >> (4 - dm.dmDisplayOrientation))
        ) & 15u);
        
      • Кирилл Ярин 0

        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?

        • GL 0

          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.

Feedback usabilla icon