WPF: multi monitor programming
Asked Answered
M

1

5

I'm using WPF in C#

I want to begin with multi monitor programming, that mean the app will display on many monitors with different views.

I have searched on internet, and I have got the approach that using Screen.AllScreens[], however I have the following question

There are many ways to connect many monitor to PC

Case 1: In Laptop: 1 Laptop's Screen, 1 Screen connect to VGA port, 1 Screen connect to HDMI port...

Case 2: In Desktop: Many Screens connect to VGA card that support multi output

Case 3: Many Screens connect to Hub HDMI or Hub VGA, and the Hub connect to PC

My question is, Screen.AllScreens[] support for which case?

And there are any other way, that support all of case?

Thank you very much!

Midmost answered 4/12, 2015 at 14:28 Comment(9)
It should show the same as the number of logical screens you have set up in your display settings, regardless of how they are connected.Bathtub
@RonBeyer: Thanks for your reply, you have tried it before?Midmost
Yes, many times, even with a USB monitor.Bathtub
There seems to be a cautionary note on Screen.AllScreens in this answer - it may not report the correct screens if the screens change while your program is running, and after you've called it for the first time.Fateful
@RonBeyer: I will try to use it, many thanks!Midmost
@JamesThorpe: I will test that case, thank you!Midmost
@RonBeyer: In case 3, I hear someone tell that I cannot with the hub, before the hub cannot output with different views. You tested with this case? and which model of Hub that you tested.Midmost
I used a Matrox TripleHead2Go, but like I said, if in the display settings it looks like one monitor, then only one monitor will show up. If the hub just makes one giant monitor out of many monitors, then its logically only a single monitor.Bathtub
@RonBeyer: thank you so much!Midmost
O
6

Screens.AllScreens[] is a WinForms method, if I'm not mistaken, but yes this supports all of your cases (as far as I know). If I remember correctly, that WinForms method initialized the static property on application startup and it wouldn't update if the screens were changed during runtime.

As you are dealing with WPF, I would avoid WinForms alltogether. Instead write your own Screens wrapper that p/invokes the Win32 API. Also, hook up an event handler to notify you when display settings change, for example:

Microsoft.Win32.SystemEvents.DisplaySettingsChanged += new EventHandler(this.SystemEvents_DisplaySettingsChanged);

This is the wrapper I use:

/// <summary>
/// This class deals with monitors.
/// </summary>
internal static class Monitors
{
    private static List<Monitors.Screen> Screens = null;

    internal static List<Monitors.Screen> GetScreens()
    {
        Monitors.Screens = new List<Monitors.Screen>();

        var handler = new NativeMethods.DisplayDevicesMethods.EnumMonitorsDelegate(Monitors.MonitorEnumProc);
        NativeMethods.DisplayDevicesMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, handler, IntPtr.Zero); // should be sequential

        return Monitors.Screens;
    }

    private static bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, NativeMethods.DisplayDevicesMethods.RECT rect, IntPtr dwData)
    {
        NativeMethods.DisplayDevicesMethods.MONITORINFO mi = new NativeMethods.DisplayDevicesMethods.MONITORINFO();

        if (NativeMethods.DisplayDevicesMethods.GetMonitorInfo(hMonitor, mi))
        {
            Monitors.Screens.Add(new Monitors.Screen(
                (mi.dwFlags & 1) == 1, // 1 = primary monitor
                mi.rcMonitor.Left, 
                mi.rcMonitor.Top, 
                Math.Abs(mi.rcMonitor.Right - mi.rcMonitor.Left), 
                Math.Abs(mi.rcMonitor.Bottom - mi.rcMonitor.Top)));
        }

        return true;
    }

    /// <summary>
    /// Represents a display device on a single system.
    /// </summary>
    internal sealed class Screen
    {
        /// <summary>
        /// Initializes a new instance of the Screen class.
        /// </summary>
        /// <param name="primary">A value indicating whether the display is the primary screen.</param>
        /// <param name="x">The display's top corner X value.</param>
        /// <param name="y">The display's top corner Y value.</param>
        /// <param name="w">The width of the display.</param>
        /// <param name="h">The height of the display.</param>
        internal Screen(bool primary, int x, int y, int w, int h)
        {
            this.IsPrimary = primary;
            this.TopX = x;
            this.TopY = y;
            this.Width = w;
            this.Height = h;
        }

        /// <summary>
        /// Gets a value indicating whether the display device is the primary monitor.
        /// </summary>
        internal bool IsPrimary { get; private set; }

        /// <summary>
        /// Gets the display's top corner X value.
        /// </summary>
        internal int TopX { get; private set; }

        /// <summary>
        /// Gets the display's top corner Y value.
        /// </summary>
        internal int TopY { get; private set; }

        /// <summary>
        /// Gets the width of the display.
        /// </summary>
        internal int Width { get; private set; }

        /// <summary>
        /// Gets the height of the display.
        /// </summary>
        internal int Height { get; private set; }
    }
}

internal static class NativeMethods
{
    /// <summary>
    /// Methods for retrieving display devices.
    /// </summary>
    internal static class DisplayDevicesMethods
    {
        internal delegate bool EnumMonitorsDelegate(IntPtr hMonitor, IntPtr hdcMonitor, NativeMethods.DisplayDevicesMethods.RECT rect, IntPtr dwData);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, EnumMonitorsDelegate lpfnEnum, IntPtr dwData);

        /// <summary>
        /// Retrieves information about a display monitor.
        /// </summary>
        /// <param name="hmonitor">A handle to the display monitor of interest.</param>
        /// <param name="info">A pointer to a MONITORINFO or MONITORINFOEX structure that receives information about the specified display monitor.</param>
        /// <returns>If the function succeeds, the return value is nonzero.</returns>
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        [return: MarshalAs(UnmanagedType.Bool)]
        internal static extern bool GetMonitorInfo(IntPtr hmonitor, [In, Out] NativeMethods.DisplayDevicesMethods.MONITORINFO info);

        /// <summary>
        /// The RECT structure defines the coordinates of the upper-left and lower-right corners of a rectangle.
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        internal struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        /// <summary>
        /// The MONITORINFO structure contains information about a display monitor.
        /// </summary>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
        internal class MONITORINFO
        {
            internal int cbSize = Marshal.SizeOf(typeof(NativeMethods.DisplayDevicesMethods.MONITORINFO));
            internal NativeMethods.DisplayDevicesMethods.RECT rcMonitor = new NativeMethods.DisplayDevicesMethods.RECT();
            internal NativeMethods.DisplayDevicesMethods.RECT rcWork = new NativeMethods.DisplayDevicesMethods.RECT();
            internal int dwFlags;
        }
    }
}
Oswaldooswalt answered 28/2, 2016 at 10:44 Comment(1)
Thanks for this. Works nicelyDynamiter

© 2022 - 2024 — McMap. All rights reserved.