Screen.AllScreen is not giving the correct monitor count
Asked Answered
D

4

20

I am doing something like this in my program:

Int32 currentMonitorCount = Screen.AllScreens.Length;

if  (currentMonitorCount < 2)
{
   //Put app in single screen mode.
}
else
{
   //Put app in dual screen mode.
}

It is VERY important my application recognizes how many monitors are currently connected.

However, after I plug/unplug the monitor a couple of times, Screen.AllScreens.Length always returns '2'.

My monitor knows it's not connected (it has entered 'power save' mode), and the control panel knows that it's not connected (it shows only one monitor).

So what am I missing? How do I figure out that there's only one monitor?

Dialect answered 16/2, 2011 at 18:28 Comment(1)
Have you tried System.Windows.Forms.SystemInformation.MonitorCount ? I use it in one of my applications and it's been working out good so far but I have not experimented with unplugging/plugging monitor while my app is running.Archegonium
T
29

I had a look at the source (remember we can do that using the MS Symbol servers). AllScreens uses an unmanaged API to get the screens on the first access, then stores the result in a static variable for later use.

The consequence of this, is that if the number of monitors changes while your program is running; then Screen.AllScreens will not pick up the change.

The easiest way to get around this would probably be to call the unmanaged API directly. (Or you could be evil, and use reflection to set the static screens field to null before asking. Don't do that).

Edit:

If you just need to know the count, check whether you can use System.Windows.Forms.SystemInformation.MonitorCount (as suggested in the comments) before going the P/Invoke route. This calls GetSystemMetrics directly, and it is probably correctly updated.

If you find you need to do it using P/Invoke, here is a complete example that demonstrates the usage of the unmanaged API from C#:

using System;
using System.Runtime.InteropServices;

class Program
{
    public static void Main()
    {
        int monCount = 0;
        Rect r = new Rect();
        MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref Rect prect, int d) => ++monCount > 0;                                       
        if (EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0))
            Console.WriteLine("You have {0} monitors", monCount);
        else
            Console.WriteLine("An error occured while enumerating monitors");

    }
    [DllImport("user32")]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lpRect, MonitorEnumProc callback, int dwData);

    private delegate bool MonitorEnumProc(IntPtr hDesktop, IntPtr hdc, ref Rect pRect, int dwData);

    [StructLayout(LayoutKind.Sequential)]
    private struct Rect
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }
}
Tabaret answered 16/2, 2011 at 18:35 Comment(3)
I'm.... not sure how to read that Delegate/Linq combination... how do I actually get the monitor data out of that function?Kaiak
Both of these solutions work great, but any ideas on how to get a real monitor count when the presentation display mode is set to duplicate? Maybe it's just not exposed, but in display settings, windows knows when a second monitor is plugged in, even in duplicate mode, while both of these solutions result in a count of 1.Scrod
I needed to detect when the number of monitors changes. I use WndProc and look for WM_DISPLAYCHANGE. When I'm there, the System.Windows.Forms.Screen.AllScreens value is hit or miss on accuracy. The EnumDisplayMonitors method is spot on though. Thanks!!!Mattock
H
11

Building on the previous reply by driis, this is how I handled it. I should note that the following code lives in my Program.cs file.

First the links to external resources and data structures:

    [DllImport("user32")]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lpRect, MonitorEnumProc callback, int dwData);

    private delegate bool MonitorEnumProc(IntPtr hDesktop, IntPtr hdc, ref Rect pRect, int dwData);

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

Now create a simple object to contain monitor information:

public class MonitorInfo
{
    public bool IsPrimary = false;
    public Rectangle Bounds = new Rectangle();
}

And a container to hold these objects:

    public static List<MonitorInfo> ActualScreens = new List<MonitorInfo>();

and a method to refresh the container:

    public static void RefreshActualScreens()
    {
        ActualScreens.Clear();
        MonitorEnumProc callback = (IntPtr hDesktop, IntPtr hdc, ref Rect prect, int d) =>
        {
            ActualScreens.Add(new MonitorInfo()
                {
                    Bounds = new Rectangle()
                    {
                        X = prect.left,
                        Y = prect.top,
                        Width = prect.right - prect.left,
                        Height = prect.bottom - prect.top,
                    },
                    IsPrimary = (prect.left == 0) && (prect.top == 0),
                });

            return true;
        };

        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, 0);
    }

Then later on a Form, If I wanted to detect that a display had been added or removed ...

    private const int WM_DISPLAYCHANGE = 0x007e;

    protected override void WndProc(ref Message message)
    {
        base.WndProc(ref message);

        if (message.Msg == WM_DISPLAYCHANGE)
        {
            Program.RefreshActualScreens();
            // do something really interesting here
        }
    }

Might be a few typos in there, but that is the basic idea. Good luck!

Headspring answered 6/10, 2015 at 11:57 Comment(0)
C
1

I had a look at the code of the Screen class ( in here )

See line 120, Screen.AllScreens uses the field Screen.screens for cache. In my solution, I use the reflection api to change the Screen class. I clear Screens.screens before calling Screen.AllScreens.

// Code for clearing private field
typeof(Screen).GetField("screens", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).SetValue(null, null);
Cryology answered 1/1, 2018 at 3:21 Comment(1)
Please try to clarify this answer a bit more.Alienation
S
0

I have My laptop running 1920x1080 32 BitsPerPixel with a 4k 50-inch tv and a 4k 32-inch tv.

Screen.AllScreens.Count - returns 3 for Count
UBound(Screen.AllScreens) - returns 2 for highest Index
Screen.AllScreens(Index) - To reference A specific monitor

Also check this out - Capture the Screen into a Bitmap

Sole answered 22/3 at 8:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.