在Windows 8上,表单显示的尺寸错误 - 如何获取真实尺寸?

7

在Windows 8上,拥有窗体边框样式设置为Sizable的WinForms窗体,DesktopBounds属性会告诉正确的值:

enter image description here

相比之下,当表单边框样式为FixedDialog时,值是错误的:

enter image description here

在Windows XP上,这些值始终是正确的:

enter image description here

enter image description here

我的问题是:

如何获取窗口的实际大小,包括完整的非客户区?

更新1:

看起来这与这个SO问题有关。我会尝试并查看它是否也能解决我的问题。

更新2:

只是为了完整起见,这里是从VMware Windows 7的结果:

enter image description here

enter image description here

更新 3:

最终找到了一种解决方案,涉及使用 DwmGetWindowAttribute 函数DWMWA_EXTENDED_FRAME_BOUNDS。我将在下面发布答案。


6
仔细观察,Windows XP表现出相同的行为。只是窗口边框只有一个像素宽,而不是Windows 8中的五个像素(在XP中,固定大小和可调整大小之间的差异为2,而在Windows 8中为10)。 - Justin Niessner
你为什么认为尺寸有问题?如果你在改变 FormBorderStyle 时查看 Visual Studio 设计器,你会看到它实际上在缩小。 - Moslem Ben Dhaou
@JustinNiessner 是的,我看到了;但是Windows XP版本返回的尺寸实际上反映了真实的窗体大小,而在Windows 8上却不是这样。 - Uwe Keim
1
在Windows 10上,如果在窗口首次绘制到屏幕之前(例如为了正确地将新窗口放置在屏幕上)调用DwmGetWindowAttribute()并使用DWMWA_EXTENDED_FRAME_BOUNDS参数,则会得到与GetWindowRect()相同的结果,即包括不可见的边框。必须等待窗口被渲染后才能获得正确的结果。 - Ian Goldby
2个回答

10
为了回答我的问题,我最终找到了一个解决方案,其中涉及使用DwmGetWindowAttribute函数DWMWA_EXTENDED_FRAME_BOUNDS
这个答案的灵感来自于这个源代码,它呈现了一个在所有系统上似乎都能工作的函数。核心是一个函数:
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);
    }
}

以下是完整的代码:

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

1
我正在使用与此非常相似的代码,并发现在许多高DPI显示器情况下,该API函数返回0并回退到GetWindowRect()调用。 - Rick Strahl
2
该源代码似乎已经移动到这里:https://github.com/ShareX/ShareX/blob/e81176a8398993d3208f9ca83b0422f6e53ef48d/ShareX.HelpersLib/Helpers/CaptureHelpers.cs#L310 - lapo

-3

我认为“错误”并不是恰当的说法...你看到了一些你不理解的值,但这并不总是等同于错误。真正的问题是,你试图通过获取窗口边界来解决什么实际问题?

你尝试过Win32的GetWindowRect方法吗?我想知道那个方法显示了什么。

你可以尝试一个hack,检测操作系统并加以处理。

在C#中确定操作系统:http://support.microsoft.com/kb/304283(该示例没有特别提到Windows 8,但我认为SDK已经更新了)


谢谢。你会如何进行操作系统切换?我无法想象一种计算真实值的方法,即使检查了“SystemInformation”属性。 - Uwe Keim
刚刚尝试了 GetWindowRect,返回的确切值相同。 - Uwe Keim
编辑了关于如何确定操作系统的信息。 - canhazbits

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接