【问题标题】:Screen.AllScreen is not giving the correct monitor countScreen.AllScreen 未提供正确的监视器计数
【发布时间】:2011-06-28 13:54:19
【问题描述】:

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

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

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

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

【问题讨论】:

  • 你试过 System.Windows.Forms.SystemInformation.MonitorCount 吗?我在我的一个应用程序中使用它,到目前为止效果很好,但我没有尝试在我的应用程序运行时拔下/插入监视器。

标签: c# .net winforms windows-7 multiple-monitors


【解决方案1】:

我查看了源代码(请记住,我们可以使用 MS Symbol 服务器做到这一点)。 AllScreens 使用非托管 API 在第一次访问时获取屏幕,然后将结果存储在静态变量中以供以后使用。

这样做的结果是,如果程序运行时监视器的数量发生了变化;那么Screen.AllScreens 将不会接受更改。

解决此问题的最简单方法可能是直接致电unmanaged API。 (或者你可能是邪恶的,在询问之前使用反射将静态 screens 字段设置为 null。不要那样做)。

编辑:

如果您只需要知道计数,请在执行 P/Invoke 路由之前检查您是否可以使用 System.Windows.Forms.SystemInformation.MonitorCount(如 cmets 中的建议)。这个直接调用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 组合...如何从该函数中实际获取监视器数据?
  • 这两种解决方案都很好用,但是当演示显示模式设置为复制时,如何获得真正的显示器计数有什么想法吗?也许它只是没有暴露,但在显示设置中,Windows 知道何时插入第二台显示器,即使在复制模式下也是如此,而这两种解决方案都会导致计数为 1。
  • 我需要检测监视器数量何时发生变化。我使用 WndProc 并寻找 WM_DISPLAYCHANGE。当我在那里时, System.Windows.Forms.Screen.AllScreens 值会在准确性方面受到影响。不过 EnumDisplayMonitors 方法很实用。谢谢!!!
【解决方案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 类的代码(在 here 中)

    参见第 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);
    

    【讨论】:

    • 请尝试进一步澄清这个答案。
    猜你喜欢
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-09
    • 2014-01-01
    • 1970-01-01
    相关资源
    最近更新 更多