How to use Win32 GetMonitorInfo() in .NET c#?

13,875

Solution 1

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

Solution 2

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.

Solution 3

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.

Share:
13,875
Houman
Author by

Houman

I'm a thinker and a dreamer. Love pets but don't have any. I'm a passionate tech entrepreneur.

Updated on June 14, 2022

Comments

  • Houman
    Houman almost 2 years

    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?

  • Houman
    Houman almost 13 years
    Thanks Gabe, I have forgotten to mention my Application is written in WPF. Theoretically I would have no access to System.Windows.Forms.Screen, unless it would also work with WPF?
  • Gabe
    Gabe almost 13 years
    @Kave: Just add a reference to System.Windows.Forms.dll and you can use it from WPF.
  • CodeNaked
    CodeNaked almost 13 years
    @Gabe - It might be easier, but I'm not sure I'd load a 5MB assembly for one class/method even with any optimizations. Not to mention it's dependencies ;-)
  • Houman
    Houman almost 13 years
    Thanks Gabe, yes I got it now there. But I don't grasp how this should work. Screen test = Screen.FromPoint(new Point(2561, 400)); Shouldn't really give me a screen object as my max X is 2560. Yet I get a screen object with X 1280, Y 0 , Width 1280 and height 1024. I dont see any indication how my point coordinates would be outside the screen. Any idea? Thanks
  • Gabe
    Gabe almost 13 years
    @Kave: I see, you aren't passing in the MONITOR_DEFAULTTONEAREST option. See my edit for how to get around that.
  • user1529413
    user1529413 over 5 years
    "It should have everything you need" It does not have the Screen positions relative to themselves
  • Konstantin S.
    Konstantin S. over 3 years
    You need [Out] if you have a class, not a struct.