Screen.AllScreen не дает правильного количества мониторов

Я делаю что-то подобное в своей программе:

Int32 currentMonitorCount = Screen.AllScreens.Length;

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

ОЧЕНЬ важно, что мое приложение распознает, сколько мониторов в настоящее время подключено.

Однако после того, как я несколько раз подключаю/отключаю монитор, Screen.AllScreens.Length всегда возвращает "2".

Мой монитор знает, что он не подключен (он включил режим энергосбережения), и панель управления знает, что она не подключена (отображается только один монитор).

Так что мне не хватает? Как я могу понять, что есть только один монитор?

Ответы

Ответ 1

Я посмотрел на источник (помните, что мы можем это сделать с помощью серверов Symbol MS). AllScreens использует неуправляемый API для получения экранов при первом доступе, а затем сохраняет результат в статической переменной для последующего использования.

Следствием этого является то, что если количество мониторов изменяется во время работы вашей программы; то Screen.AllScreens не получит изменения.

Самый простой способ обойти это, вероятно, можно было бы напрямую вызвать неуправляемый API. (Или вы можете быть злым и использовать отражение, чтобы статическое поле screens было нулевым, прежде чем спрашивать. Не делайте этого).

Изменить:

Если вам просто нужно знать количество, проверьте, можете ли вы использовать System.Windows.Forms.SystemInformation.MonitorCount (как это предложено в комментариях) перед тем, как перейти к маршруту P/Invoke. Это вызывает GetSystemMetrics напрямую и, вероятно, правильно обновляется.

Если вы обнаружите, что вам нужно сделать это с помощью P/Invoke, вот полный пример, демонстрирующий использование неуправляемого API из С#:

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;
    }
}

Ответ 2

Основываясь на предыдущем ответе на driis, я так справился с этим. Следует отметить, что следующий код находится в моем файле Program.cs.

Сначала ссылки на внешние ресурсы и структуры данных:

    [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;
    }

Теперь создайте простой объект, чтобы содержать информацию о мониторе:

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

И контейнер для хранения этих объектов:

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

и метод обновления контейнера:

    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);
    }

Затем позже в форме, если бы я хотел обнаружить, что дисплей был добавлен или удален...

    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
        }
    }

Возможно, там будет несколько опечаток, но это основная идея. Удачи!

Ответ 3

Я посмотрел код класса Screen (в здесь)

См. строку 120, Screen.AllScreens. Используйте поле Screen.screens для temp. И в моем решении, я использую отражение api, чтобы внести некоторые изменения в класс Screen!! Я чистый Screen.screens перед отзывом Screen.AllScreen.

typeof(Screen).GetField("screens", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).SetValue(null, null);
// it is code for clean private field