如何在 .NET c# 中使用 Win32 GetMonitorInfo()?

发布于 2024-11-14 05:19:57 字数 2483 浏览 4 评论 0原文

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

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

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

我创建了一个 Helper 类:

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

任何建议我需要做什么解决这个问题?

I have to implement a feature where the last position of the window is saved. When the application starts up this position needs to be obtained and restored.

Now it could be that a second monitor is dismantled. If the last position is on a now non-visible monitor (in other words the saved coordinates are outside the visible coordinates), this case should be caught and the coordinates shall be set to the default rather than last position.

In order to retrieve the information about monitors I need to use Win32. It is not easy for me to make this work.

I have created a 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);

        }
    }

And these are my attempts to create the MonitorInfo and Rect classes:

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

I am using this code like this to obtain the visible monitors:

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

The last method throws an exception when trying to execute

bool result = GetMonitorInfo(mh, ref mi);

Any suggestions what I need to do to fix this?

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

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

发布评论

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

评论(4

早茶月光 2024-11-21 05:19:58

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

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

Your Rectangle2 should use Int32 or just int, not Int64. More information can be found here.

Also it needs to be a struct, not a class. Same goes for your MonitorInfo class (it should be a struct). I'd recommend trying the version from the link above, or compare them with your versions.

宁愿没拥抱 2024-11-21 05:19:58

由于您使用的是 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);

Since you are using the MonitorInfo class, not a struct, you must specify the [Out] attribute and not use ref in order for the marshaler to update your class correctly.

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

You can also go to using structure and ref, then it will look like this:

[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);
不奢求什么 2024-11-21 05:19:57

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

Screen.FromPoint< /a> 是带有 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; 
    }

Rather than calling a native API, you should use System.Windows.Forms.Screen. It should have everything you need, and be much easier to use.

Screen.FromPoint is the managed equivalent of your GetMonitorInfoNow function with the MONITOR_DEFAULTTONEAREST option. I just noticed you aren't using that option, so you may have to write your own or use the correct P/Invoke signatures.

Writing your own should be fairly simple, if you just reference System.Drawing and System.Windows.Forms. Both of these should work:

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

If you prefer to make the Win32 calls yourself, the proper P/Invoke signatures (i.e. what you'd get from decompiling the .Net DLL) for the functions you need to call are:

    [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; 
    }
ぺ禁宫浮华殁 2024-11-21 05:19:57

我发现一个不同的是
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] 的更多信息,请参见 这个

I found one different is
public static extern bool GetMonitorInfo(IntPtr hMonitor, [In,Out] MONITORINFO lpmi) and
public static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi)

In my case, the ref keywork made the function always return false.
But if remove this keyword or usr [In,Out], it work.

More info about ref vs. [In,Out] on This.

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