【问题标题】:How to use Win32 GetMonitorInfo() in .NET c#?如何在 .NET c# 中使用 Win32 GetMonitorInfo()?
【发布时间】:2011-09-10 20:48:15
【问题描述】:

我必须实现一个保存窗口最后位置的功能。当应用程序启动时,需要获取并恢复该位置。

现在可能是第二台显示器被拆除了。如果最后一个位置在一个现在不可见的监视器上(换句话说,保存的坐标在可见坐标之外),则应捕获这种情况,并将坐标设置为默认位置而不是最后一个位置。

为了检索有关监视器的信息,我需要使用 Win32。完成这项工作对我来说并不容易。

我创建了一个 Helper CLass:

public static class DisplayHelper
    {
        private const int MONITOR_DEFAULTTONEAREST = 2;

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        public static extern int GetSystemMetrics(int nIndex);

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern UInt32 MonitorFromPoint(Point pt, UInt32 dwFlags);

        [DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
        private static extern bool GetMonitorInfo(UInt32 monitorHandle, ref MonitorInfo mInfo);


        public static void GetMonitorInfoNow(MonitorInfo mi, Point pt)
        {
            UInt32 mh = MonitorFromPoint(pt, 0);
            mi.cbSize = (UInt32)System.Runtime.InteropServices.Marshal.SizeOf(typeof(MonitorInfo));
            mi.dwFlags = 0;
            bool result = GetMonitorInfo(mh, ref mi);

        }
    }

这些是我创建 MonitorInfo 和 Rect 类的尝试:

[StructLayout(LayoutKind.Sequential)]
    public class MonitorInfo
    {
        public UInt32 cbSize;
        public Rectangle2 rcMonitor;
        public Rectangle2 rcWork;
        public UInt32 dwFlags;

        public MonitorInfo()
        {
            rcMonitor = new Rectangle2();
            rcWork = new Rectangle2();

            cbSize = (UInt32)System.Runtime.InteropServices.Marshal.SizeOf(typeof(MonitorInfo));
            dwFlags = 0;
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public class Rectangle2
    {
        public UInt64 left;
        public UInt64 top;
        public UInt64 right;
        public UInt64 bottom;

        public Rectangle2()
        {
            left = 0;
            top = 0;
            right = 0;
            bottom = 0;
        }
    }

我正在使用这样的代码来获取可见的监视器:

//80 means it counts only visible display monitors.
int lcdNr = DisplayHelper.GetSystemMetrics(80);
var point = new System.Drawing.Point((int) workSpaceWindow.Left, (int) workSpaceWindow.Top);
MonitorInfo monitorInfo = new MonitorInfo();
DisplayHelper.GetMonitorInfoNow(monitorInfo, point);

最后一个方法在尝试执行时抛出异常

bool result = GetMonitorInfo(mh, ref mi);

有什么建议我需要做些什么来解决这个问题?

【问题讨论】:

    标签: c# .net winapi multiple-monitors user32


    【解决方案1】:

    您应该使用System.Windows.Forms.Screen,而不是调用本机 API。它应该有你需要的一切,而且更容易使用。

    Screen.FromPoint 是带有MONITOR_DEFAULTTONEAREST 选项的GetMonitorInfoNow 函数的托管等效项。我刚刚注意到您没有使用该选项,因此您可能必须自己编写或使用正确的 P/Invoke 签名。

    如果您只引用System.DrawingSystem.Windows.Forms,编写自己的代码应该相当简单。这两个都应该工作:

    static Screen ScreenFromPoint1(Point p)
    {
        System.Drawing.Point pt = new System.Drawing.Point((int)p.X, (int)p.Y);
        return Screen.AllScreens
                        .Where(scr => scr.Bounds.Contains(pt))
                        .FirstOrDefault();
    }
    
    static Screen ScreenFromPoint2(Point p)
    {
        System.Drawing.Point pt = new System.Drawing.Point((int)p.X, (int)p.Y);
        var scr = Screen.FromPoint(pt);
        return scr.Bounds.Contains(pt) ? scr : null;
    }
    

    如果您更喜欢自己调用 Win32,则需要调用的函数的正确 P/Invoke 签名(即反编译 .Net DLL 得到的)是:

        [DllImport("User32.dll", CharSet=CharSet.Auto)] 
        public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out]MONITORINFOEX info);
        [DllImport("User32.dll", ExactSpelling=true)]
        public static extern IntPtr MonitorFromPoint(POINTSTRUCT pt, int flags);
    
        [StructLayout(LayoutKind.Sequential,CharSet=CharSet.Auto, Pack=4)]
        public class MONITORINFOEX { 
            public int     cbSize = Marshal.SizeOf(typeof(MONITORINFOEX));
            public RECT    rcMonitor = new RECT(); 
            public RECT    rcWork = new RECT(); 
            public int     dwFlags = 0;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)] 
            public char[]  szDevice = new char[32];
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct POINTSTRUCT { 
            public int x;
            public int y;
            public POINTSTRUCT(int x, int y) {
              this.x = x; 
              this.y = y;
            } 
        } 
    
        [StructLayout(LayoutKind.Sequential)] 
        public struct RECT {
            public int left; 
            public int top; 
            public int right;
            public int bottom; 
        }
    

    【讨论】:

    • 感谢 Gabe,我忘了提及我的应用程序是用 WPF 编写的。理论上我无法访问 System.Windows.Forms.Screen,除非它也可以与 WPF 一起使用?
    • @Kave:只需添加对 System.Windows.Forms.dll 的引用,您就可以从 WPF 中使用它。
    • @Gabe - 这可能更容易,但我不确定我是否会为一个类/方法加载一个 5MB 的程序集,即使有任何优化。更不用说它的依赖了;-)
    • 谢谢 Gabe,是的,我现在知道了。但我不明白这应该如何工作。屏幕测试 = Screen.FromPoint(new Point(2561, 400));不应该真的给我一个屏幕对象,因为我的最大 X 是 2560。但是我得到了一个 X 1280、Y 0、宽度 1280 和高度 1024 的屏幕对象。我没有看到任何迹象表明我的点坐标将如何在屏幕之外。任何想法?谢谢
    • @Kave:我明白了,你没有传入MONITOR_DEFAULTTONEAREST 选项。请参阅我的编辑以了解如何解决这个问题。
    【解决方案2】:

    我发现了一个不同的是
    public static extern bool GetMonitorInfo(IntPtr hMonitor, [In,Out] MONITORINFO lpmi)
    public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi)

    在我的例子中,ref 键使函数总是返回 false。
    但是如果去掉这个关键字或者 usr [In,Out] 就可以了。

    更多关于 ref 与 [In,Out] 的信息,请访问 This

    【讨论】:

    • 如果你有一个类而不是结构,你需要 [Out]。
    【解决方案3】:

    您的 Rectangle2 应该使用 Int32 或仅使用 int,而不是 Int64。更多信息可以在here找到。

    它还需要是一个结构,而不是一个类。您的 MonitorInfo 类也是如此(它应该是一个结构)。我建议您尝试上面链接中的版本,或者将它们与您的版本进行比较。

    【讨论】:

      【解决方案4】:

      由于您使用的是 MonitorInfo 类,而不是结构,因此您必须指定 [Out] 属性并且不使用 ref 以便编组器正确更新您的类。

      [DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]
      internal static extern bool GetMonitorInfo(
          IntPtr hmonitor, 
          [In, Out] MonitorInfo info);
      

      你也可以去使用结构体和ref,然后会是这样的:

      [DllImport("user32", CharSet = CharSet.Auto, SetLastError = true)]
      internal static extern bool GetMonitorInfo(
          IntPtr hmonitor, 
          ref MonitorInfoEx info);
      
      [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
      internal struct MonitorInfoEx
      {
         public uint cbSize;
         public RECT rcMonitor;
         public RECT rcWork;
         public uint dwFlags;
         [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
         public char[] szDevice;
      }
      
      var info = new MonitorInfoEx
      {
          cbSize = (uint)Marshal.SizeOf(typeof(MonitorInfoEx)),
      };
      GetMonitorInfo(hDesktop, ref info);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-26
        相关资源
        最近更新 更多