Screen.AllScreen不能正确显示监视器数量

20

我在我的程序中做了类似以下的操作:

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'。

我的显示器知道它没有连接(它已进入“省电模式”),控制面板也知道它没有连接(仅显示一个监视器)。

那么我错过了什么?我如何确定只有一个显示器?


2
你试过System.Windows.Forms.SystemInformation.MonitorCount吗?我在我的一个应用程序中使用它,到目前为止它一直表现良好,但我还没有尝试在我的应用程序运行时拔掉/插入显示器。 - Bala R
3个回答

29
我查看了源代码(记住,我们可以使用MS符号服务器来做到这一点)。 AllScreens使用未托管的API在第一次访问时获取屏幕,然后将结果存储在静态变量中以供以后使用。
这样做的后果是,如果您的程序在运行时更改了显示器数量,则Screen.AllScreens将无法捕捉到更改。
最简单的解决方法可能是直接调用未托管API。 (或者你可以狠心一点,在请求之前使用反射将静态的screens字段设置为null。别这么做)。
编辑:
如果您只需要知道数量,请在走P / Invoke路线之前检查是否可以使用System.Windows.Forms.SystemInformation.MonitorCount(如评论中建议的)来检查。这直接调用GetSystemMetrics,并且它可能已经正确更新。
如果您发现需要使用P / Invoke进行操作,以下是一个完整的示例,演示了从C#使用未托管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;
    }
}

我...不太确定如何阅读那个Delegate/Linq组合...我该如何从那个函数中获取监视器数据? - Nyerguds
2
这两种解决方案都非常好,但是有没有想法在演示显示模式设置为复制时如何获得真正的监视器计数?也许它只是没有暴露出来,但在显示设置中,Windows 知道第二个监视器何时被插入,即使在复制模式下,而这两种解决方案的结果都是 1。 - samuelesque
我需要检测显示器数量变化的时候。我使用WndProc并查找WM_DISPLAYCHANGE。当我在那里时,System.Windows.Forms.Screen.AllScreens值的准确性有时会失误。但是EnumDisplayMonitors方法非常准确。谢谢! - Matt Becker

11

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

可能有一些拼写错误,但这就是基本思想。祝你好运!


1
我查看了Screen类的代码(在这里)。
请参见第120行,Screen.AllScreens使用屏幕缓存字段Screen.screens。 在我的解决方案中,我使用反射API更改Screen类。 在调用Screen.AllScreens之前,我会清除Screens.screens。
// Code for clearing private field
typeof(Screen).GetField("screens", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic).SetValue(null, null);

请尝试更明确地阐述这个答案。 - Nae

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接