Click here to Skip to main content
15,893,266 members
Articles / Programming Languages / C#
Tip/Trick

Getting Display Information in Windows UI 3

Rate me:
Please Sign up or sign in to vote.
0.00/5 (No votes)
3 May 2023CPOL2 min read 7.5K   1   3
How to obtain information on the active display in a Windows UI 3 app
I rarely like the size of the application windows for my Win UI 3 apps. Adjusting the size is relatively straightforward with the latest Win Apps SDK, but getting the size of the target display is not at all obvious. This tip shows how to get it, and provides an example of using it.

Introduction

So far as I can tell, getting the size of the currently active display within a Windows App SDK (aka Win UI 3) application isn't intuitively obvious. This tip shows one way of obtaining the information, and how you might use it.

Retrieving and Using Display Information

Here's a method from an app I wrote which sets the initial size of the app's window:

C#
private async Task SizeWindow( AppWindow appWindow )
{
    var displayList = await DeviceInformation.FindAllAsync
                      ( DisplayMonitor.GetDeviceSelector() );

    if( !displayList.Any() )
        return;

    var monitorInfo = await DisplayMonitor.FromInterfaceIdAsync( displayList[ 0 ].Id );

    var winSize = new SizeInt32();

    if( monitorInfo == null )
    {
        winSize.Width = 800;
        winSize.Height = 1200;
    }
    else
    {
        winSize.Height = monitorInfo.NativeResolutionInRawPixels.Height;
        winSize.Width = monitorInfo.NativeResolutionInRawPixels.Width;

        var widthInInches = Convert.ToInt32( 8 * monitorInfo.RawDpiX ); 
        var heightInInches = Convert.ToInt32( 12 * monitorInfo.RawDpiY );

        winSize.Height = winSize.Height > heightInInches? 
                         heightInInches: winSize.Height;
        winSize.Width = winSize.Width > widthInInches ? widthInInches: winSize.Width;
    }

    appWindow.Resize( winSize );
}

I won't go through every line in the method because some of it is "belt and suspenders" logic to keep things from blowing up in unusual circumstances. But the basic logic is simple and starts by calling DeviceInformation.FindAllAsync using a value you get by calling DisplayMonitor.GetDeviceSelector.

DeviceInformation.FindAllAsync, if called without an argument, appears to return information on every single device known to Windows (when I called it that way the first time, I got more than 650 items returned).

Fortunately, the call to DisplayMonitor.GetDeviceSelector filters it down to just the display devices (on my system, which only has one display, the returned list contained only a single item).

To get the information about a display, you pass the Id parameter of a device information object to DisplayMonitor.FromInterfaceIdAsync.

Once that returns, the rest of the logic simply involves using the information -- assuming it was returned -- to size the application window. If the monitor info wasn't returned, I default to an 800x1200 window.

But if it was returned, I set the window to full screen and then limit it to be no more than 8 inches wide and 12 inches tall.

Once that's done, you just pass the size information to the AppWindow Resize method.

Calling the Method

I like to size the application window in its constructor. Unfortunately, because the methods you need to call to get display information all appear to be asynchronous, I can't just call the SizeWindow method shown above. I also need to determine the AppWindow instance to pass to SizeWindow.

Here's how I did that in the main window's constructor:

C#
this.InitializeComponent();

var hWnd = WindowNative.GetWindowHandle( this );
var windowId = Win32Interop.GetWindowIdFromWindow( hWnd );
var appWindow = AppWindow.GetFromWindowId( windowId );

Task.Run( async () => await SizeWindow( appWindow ) );

There may be a more direct method for getting the AppWindow instance because the WinUI 3/Windows App SDK is evolving rapidly. Be sure to stay on top of the changes!

Points of Interest

I was surprised that I couldn't find an easily accessible way to get the display size information. I spent well over an hour researching online before I stumbled across what I needed to put together what I've included here. Granted, that might be due to me not using the right search terms (is it a monitor or a display? and please stop giving me references to AppWindow.Resize because I already know about it :)). If anyone knows a more direct way, I'm all ears!

History

  • 3rd May, 2023: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Jump for Joy Software
United States United States
Some people like to do crossword puzzles to hone their problem-solving skills. Me, I like to write software for the same reason.

A few years back I passed my 50th anniversary of programming. I believe that means it's officially more than a hobby or pastime. In fact, it may qualify as an addiction Smile | :) .

I mostly work in C# and Windows. But I also play around with Linux (mostly Debian on Raspberry Pis) and Python.

Comments and Discussions

 
QuestionAn alternative method Pin
pdoxtader5-Apr-24 8:16
professionalpdoxtader5-Apr-24 8:16 
Hi Mark,
After starting with the code you posted here, and going down the multiple monitor rabbit hole to figure out how best to accomplish this in multi-monitor systems, I discovered that the best way to get your screen bounds in WinUI3 C# is probably the following code:
C#
var displayArea = DisplayArea.GetFromWindowId(this.AppWindow.Id, DisplayAreaFallback.Primary);

This single line of code returns a DisplayArea object, which is the representation of the monitor upon which your app is displayed. It includes the RectInt32 containing the screen dimensions and position in .OuterBounds, and the available display area without the taskbar in .WorkArea.

The RectInt32 returns values that can be used to differentiate between monitors even if their resolutions are identical, because each display is positioned relative to the rest. This can be seen by looking at the X and Y values of .OuterBounds.

Unfortunately, DisplayArea.FindAll() has a bug that remains unaddressed by Microsoft (for years now), so at the moment the only way to get info about the monitors in your system (using WinUI3) that contains the RectInt32 we really need to be able to differentiate between monitors is to use the following Monitor class that I found on StackOverflow;


C#
public sealed class Monitor
{
    private Monitor(IntPtr handle)
    {
        Handle = handle;
        var mi = new MONITORINFOEX();
        mi.cbSize = Marshal.SizeOf(mi);
        if (!GetMonitorInfo(handle, ref mi))
            throw new Win32Exception(Marshal.GetLastWin32Error());

        DeviceName  = mi.szDevice.ToString();
        Bounds      = new RectInt32(mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right - mi.rcMonitor.left, mi.rcMonitor.bottom - mi.rcMonitor.top);
        WorkingArea = new RectInt32(mi.rcWork.left, mi.rcWork.top, mi.rcWork.right - mi.rcWork.left, mi.rcWork.bottom - mi.rcWork.top);
        IsPrimary   = mi.dwFlags.HasFlag(MONITORINFOF.MONITORINFOF_PRIMARY);
    }

    public IntPtr Handle { get; }
    public bool IsPrimary { get; }
    public RectInt32 WorkingArea { get; }
    public RectInt32 Bounds { get; }
    public string DeviceName { get; }

    public static IEnumerable All
    {
        get
        {
            var all = new List();
            EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, (m, h, rc, p) =>
            {
                all.Add(new Monitor(m));
                return true;
            }, IntPtr.Zero);
            return all;
        }
    }

    public override string ToString() => DeviceName;
    public static IntPtr GetNearestFromWindow(IntPtr hwnd) => MonitorFromWindow(hwnd, MFW.MONITOR_DEFAULTTONEAREST);
    public static IntPtr GetDesktopMonitorHandle() => GetNearestFromWindow(GetDesktopWindow());
    public static IntPtr GetShellMonitorHandle() => GetNearestFromWindow(GetShellWindow());
    public static Monitor FromWindow(IntPtr hwnd, MFW flags = MFW.MONITOR_DEFAULTTONULL)
    {
        var h = MonitorFromWindow(hwnd, flags);
        return h != IntPtr.Zero ? new Monitor(h) : null;
    }

    [Flags]
    public enum MFW
    {
        MONITOR_DEFAULTTONULL = 0x00000000,
        MONITOR_DEFAULTTOPRIMARY = 0x00000001,
        MONITOR_DEFAULTTONEAREST = 0x00000002,
    }

    [Flags]
    public enum MONITORINFOF
    {
        MONITORINFOF_NONE = 0x00000000,
        MONITORINFOF_PRIMARY = 0x00000001,
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct MONITORINFOEX
    {
        public int cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public MONITORINFOF dwFlags;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szDevice;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

    private delegate bool MonitorEnumProc(IntPtr monitor, IntPtr hdc, IntPtr lprcMonitor, IntPtr lParam);

    [DllImport("user32")]
    private static extern IntPtr GetDesktopWindow();

    [DllImport("user32")]
    private static extern IntPtr GetShellWindow();

    [DllImport("user32")]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);

    [DllImport("user32")]
    private static extern IntPtr MonitorFromWindow(IntPtr hwnd, MFW flags);

    [DllImport("user32", CharSet = CharSet.Unicode)]
    private static extern bool GetMonitorInfo(IntPtr hmonitor, ref MONITORINFOEX info);
}

Thanks for posting your original code - I did learn a lot from it, and during this trip down the rabbit hole.
  • Pete

QuestionDeviceInformation not in WinAppSDK Pin
bobbyn3149-Aug-23 23:18
bobbyn3149-Aug-23 23:18 
AnswerRe: DeviceInformation not in WinAppSDK Pin
Mark Olbert11-Aug-23 11:16
Mark Olbert11-Aug-23 11:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.