7

Having a WinForms form with form border style set to Sizable on Windows 8, the DesktopBounds property tells the correct values:

enter image description here

In contrast, when having a form border style of FixedDialog, the values are wrong:

enter image description here

On Windows XP, the values are always correct:

enter image description here

enter image description here

My question is:

How to get the real size of a Window including the complete non-client area?

Update 1:

Seems that it is related to this SO question. I'll try and see whether this would solve my issue here, too.

Update 2:

Just for completeness, here are the results from a VMware Windows 7:

enter image description here

enter image description here

Update 3:

Finally found a solution which involves using the DwmGetWindowAttribute function together with the DWMWA_EXTENDED_FRAME_BOUNDS value. I'll post an answer below.

Community
  • 1
  • 1
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • 6
    If you look closely, Windows XP exhibits the same behavior. It's just that the Windows borders are a single pixel wide rather than the five pixels in Windows 8 (the difference in XP between Fixed and Sizable is 2 whereas it is 10 in Windows 8). – Justin Niessner May 10 '13 at 14:46
  • what gives you the assumption the size is wrong? if you have a look at the Visual Studio designer while you change the `FormBorderStyle` you will actually see it shrink – Moslem Ben Dhaou May 10 '13 at 14:47
  • @JustinNiessner Yes, I do see that; still the dimensions returned by the Windows XP version actually reflect the real form size, whereas on Windows 8, they do not. – Uwe Keim May 10 '13 at 14:48
  • 1
    On Windows 10, if you call `DwmGetWindowAttribute()` with `DWMWA_EXTENDED_FRAME_BOUNDS` before the window has first been drawn to the screen (e.g. in order to place a new window correctly on the screen) then you get the same result as `GetWindowRect()` - i.e. including the invisible border. You have to wait until the window has been rendered before it will give the correct result. – Ian Goldby Oct 31 '16 at 15:32

2 Answers2

10

To answer my own question, I finally found a solution which involves using the DwmGetWindowAttribute function together with the DWMWA_EXTENDED_FRAME_BOUNDS value

The answer was inspired by this source code which presents a function that seems to work on all system. The core is a function:

public static Rectangle GetWindowRectangle(IntPtr handle)
{
    if (Environment.OSVersion.Version.Major < 6)
    {
        return GetWindowRect(handle);
    }
    else
    {
        Rectangle rectangle;
        return DWMWA_EXTENDED_FRAME_BOUNDS(handle, out rectangle) 
                   ? rectangle 
                   : GetWindowRect(handle);
    }
}

Full code is provided below:

public static class WindowHelper
{
    // https://code.google.com/p/zscreen/source/browse/trunk/ZScreenLib/Global/GraphicsCore.cs?r=1349

    /// <summary>
    /// Get real window size, no matter whether Win XP, Win Vista, 7 or 8.
    /// </summary>
    public static Rectangle GetWindowRectangle(IntPtr handle)
    {
        if (Environment.OSVersion.Version.Major < 6)
        {
            return GetWindowRect(handle);
        }
        else
        {
            Rectangle rectangle;
            return DWMWA_EXTENDED_FRAME_BOUNDS(handle, out rectangle) ? rectangle : GetWindowRect(handle);
        }
    }

    [DllImport(@"dwmapi.dll")]
    private static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out Rect pvAttribute, int cbAttribute);

    private enum Dwmwindowattribute
    {
        DwmwaExtendedFrameBounds = 9
    }

    [Serializable, StructLayout(LayoutKind.Sequential)]
    private struct Rect
    {
        // ReSharper disable MemberCanBePrivate.Local
        // ReSharper disable FieldCanBeMadeReadOnly.Local
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
        // ReSharper restore FieldCanBeMadeReadOnly.Local
        // ReSharper restore MemberCanBePrivate.Local

        public Rectangle ToRectangle()
        {
            return Rectangle.FromLTRB(Left, Top, Right, Bottom);
        }
    }

    private static bool DWMWA_EXTENDED_FRAME_BOUNDS(IntPtr handle, out Rectangle rectangle)
    {
        Rect rect;
        var result = DwmGetWindowAttribute(handle, (int)Dwmwindowattribute.DwmwaExtendedFrameBounds,
            out rect, Marshal.SizeOf(typeof(Rect)));
        rectangle = rect.ToRectangle();
        return result >= 0;
    }

    [DllImport(@"user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);

    private static Rectangle GetWindowRect(IntPtr handle)
    {
        Rect rect;
        GetWindowRect(handle, out rect);
        return rect.ToRectangle();
    }
}
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
  • 1
    I'm using code very similar to this and finding that in a number of situations with high DPI displays the API function just returns 0 and falls back to the GetWindowRect() call instead. – Rick Strahl Jul 30 '16 at 11:38
  • 2
    That source seems to have moved here: https://github.com/ShareX/ShareX/blob/e81176a8398993d3208f9ca83b0422f6e53ef48d/ShareX.HelpersLib/Helpers/CaptureHelpers.cs#L310 – lapo Apr 28 '18 at 23:46
-3

I don't think "wrong" is quite the right way to put it.. you are seeing values you don't understand, but that's not always the same as wrong. The real question is what is the actual problem you are trying to solve by getting the window bounds?

Have you tried the Win32 GetWindowRect method? I wonder what that shows.

One hack you could try would be to detect the OS and account for these.

To determine the OS in C#: http://support.microsoft.com/kb/304283 (that sample doesn't mention Windows 8 specifically, but I assume the SDK has been updated for it)

canhazbits
  • 1,664
  • 1
  • 14
  • 19