Screen.AllScreen 未给出正确的显示器计数

发布于 2024-10-17 11:48:10 字数 407 浏览 6 评论 0原文

我在我的程序中做了这样的事情:

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

我的显示器知道它没有连接(它已进入“省电”模式),并且控制面板知道它没有连接(它只显示一台显示器)。

那么我错过了什么? 如何知道只有一台显示器?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

何以笙箫默 2024-10-24 11:48:10

我查看了源代码(记住我们可以使用 MS Symbol 服务器来做到这一点)。 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;
    }
}

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;
    }
}
星星的軌跡 2024-10-24 11:48:10

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

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

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!

茶花眉 2024-10-24 11:48:10

我查看了 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);

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);
我一直都在从未离去 2024-10-24 11:48:10

我的笔记本电脑运行 1920x1080 32 BitsPerPixel,配有 4k 50 英寸电视和 4k 32 英寸电视。

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

另请查看 - 将屏幕捕获为位图

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文