【问题标题】:How do I compute the non-client window size in WPF?如何计算 WPF 中的非客户端窗口大小?
【发布时间】:2011-08-27 06:49:31
【问题描述】:

WPF 有SystemParameters class,它公开了大量的系统指标。在我的电脑上,我注意到一个普通的窗口有一个 30 像素高的标题和一个 8 像素宽的边框。这是在启用 Aero 主题的 Windows 7 上:

但是,SystemParameters 返回以下值:

SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21

在这里我禁用了 Aero 主题:

现在,SystemParameters 返回以下值:

SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18

如何使用SystemParameters 计算实际观察值?

【问题讨论】:

  • 您在说明指标方面做得很好,非常感谢。这应该出现在 MSDN 文档中!

标签: c# .net wpf windows winapi


【解决方案1】:

对于可调整大小的窗口,您需要使用一组不同的参数来计算大小:

var titleHeight = SystemParameters.WindowCaptionHeight
  + SystemParameters.ResizeFrameHorizontalBorderHeight;
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;

当您修改主题时,这些尺寸会发生变化。

【讨论】:

  • 是的,添加 8 是我以前见过的一种技巧,可以使这些值与 Aero 主题的预期值相匹配。但这并不是一个万无一失的方法。窗口主题显然是微软非常喜欢重新发明的东西,即使在当前系统下,事情也会崩溃,除非你特别检查用户当前正在运行哪个主题——Classic、Aero Basic 或 Aero。
  • 在这种情况下,我并没有真正将其视为黑客行为。标题为 22 像素,四边的边框为 8 像素。为您提供正确的值。这在您更改主题时有效 - 它返回 Classic、Aero Basic 和 Aero 的正确值。
  • 看来这里是答案:detect-system-theme-change-in-wpf
  • 在 Windows 8.1 上,ResizeFrameVerticalBorderWidth 给我 4,但实际边框宽度是 7。
  • @kol SystemParameters.ResizeFrameVerticalBorderWidth + SystemParameters.FixedFrameVerticalBorderWidth // + SystemParameters.BorderWidth 给了我正确的值(我用 java 得到 8,但我不确定它是 8 还是 7,所以如果 BorderWidth 很重要,我认为它是内部灰线。
【解决方案2】:

我很确定 GetSystemMetrics functionSystemParameters 类在内部使用适当的参数调用)为您的系统返回正确的值,它只是返回正确的值Aero 主题是否被禁用的情况。启用 Aero 后,您会获得更强大的边框和更高的窗口标题,这些都是多汁的图形优点。

如果您想获得这些窗口元素的正确大小,而不管用户当前的主题是什么(请记住,您可以使用 Classic 主题、Aero Basic 主题或完整 Aero 主题运行 Windows Vista 及更高版本,所有将有不同大小的 UI 元素),您需要使用 Vista 及更高版本中可用的不同方法。

您需要向窗口发送WM_GETTITLEBARINFOEX message 以请求扩展标题栏信息。 wParam 未使用,应为零。 lParam 包含一个指向TITLEBARINFOEX structure 的指针,它将接收所有信息。调用者负责为此结构分配内存并设置其cbSize 成员。

要从 .NET 应用程序执行所有这些操作,您显然需要执行一些 P/Invoke。首先定义您需要的常量以及TITLEBARINFOEX 结构:

internal const int WM_GETTITLEBARINFOEX = 0x033F;
internal const int CCHILDREN_TITLEBAR = 5;

[StructLayout(LayoutKind.Sequential)]
internal struct TITLEBARINFOEX
{
    public int cbSize;
    public Rectangle rcTitleBar;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
    public int[] rgstate;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = CCHILDREN_TITLEBAR + 1)]
    public Rectangle[] rgrect;
}

然后相应地定义SendMessage函数:

[DllImport("user32.dll", CharSet = CharSet.Auto)]
internal static extern IntPtr SendMessage(
                                          IntPtr hWnd,
                                          int uMsg,
                                          IntPtr wParam,
                                          ref TITLEBARINFOEX lParam);

最后,您可以使用类似以下代码的方式调用所有这些混乱:

internal static TITLEBARINFOEX GetTitleBarInfoEx(IntPtr hWnd)
{
    // Create and initialize the structure
    TITLEBARINFOEX tbi = new TITLEBARINFOEX();
    tbi.cbSize = Marshal.SizeOf(typeof(TITLEBARINFOEX));

    // Send the WM_GETTITLEBARINFOEX message
    SendMessage(hWnd, WM_GETTITLEBARINFOEX, IntPtr.Zero, ref tbi);

    // Return the filled-in structure
    return tbi;
}

编辑:现在在我运行 Windows 7 的笔记本上进行了测试和工作。

【讨论】:

  • 似乎有效。在 WPF 中使用此代码时的小警告,因为此代码需要 Windows 窗体 Rectangle,而不是 WPF Rectangle - 我花了一些时间才弄清楚,否则你不会得到结果。
  • 这适用于windows 8.1,但不适用于windows 10,你知道如何解决吗?我在这里发布了一个新问题 - stackoverflow.com/questions/60806098/…
【解决方案3】:

请参阅以下内容:

http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2009/09/20/wpf-quick-tip-how-to-get-wpf-window-client-area-size.aspx

我想您正在尝试计算您必须制作应用程序窗口的大小,以便提供适量的客户区来完全显示某些 WPF 内容?

如果是这样,那么请记住 WPF 的像素为 96dpi,并且您的显示器可能以不同的 dpi 运行...正如其他答案所提到的,主题会影响您必须将主窗口的大小调整到多大获取您想要的客户区域。

或者,您可以在 Window 的子控件上使用 MinWidth/MinHeight。

【讨论】:

  • 他明确询问非客户区域。您的大部分答案和链接的帖子都是关于客户区的。确定 NC 区域只是为了计算客户区域的尺寸,而确定后者的尺寸要简单得多,这是非常愚蠢的。
【解决方案4】:

这是一个 C++/CLI 答案,它没有使用 SystemParameters,但我认为这是解决此问题的更好方法,因为它应该适用于任何窗口。

实际上,其他答案仅对可调整大小的窗口有效,并且必须为每个可用的WindowStyles 创建不同的案例。

由于这些计算所需的每个 SystemParameters 都有一个记录在案的 SM_CX* 或 SM_CY* 值,我认为与其重新发明轮子,不如简单地使用 WinAPI AdjustWindowRectEx 函数。

bool SetWindowClientArea(System::Windows::Window^ win, int width, int height) {
    System::Windows::Interop::WindowInteropHelper^ wi = gcnew System::Windows::Interop::WindowInteropHelper(win);
    wi->EnsureHandle();
    HWND win_HWND = (HWND)(wi->Handle.ToPointer());

    LONG winStyle = GetWindowLong(win_HWND, GWL_STYLE);
    LONG winExStyle = GetWindowLong(win_HWND, GWL_EXSTYLE);
    RECT r = { 0 };
    r.right = width;
    r.bottom = height;
    BOOL bres = AdjustWindowRectEx(&r, winStyle, FALSE, winExStyle);
    if (bres) {
        Double w = r.right - r.left;
        Double h = r.bottom - r.top;
        win->Width = w;
        win->Height = h;
    }

    return bres;
}

使用更多DllImports 可以轻松地将上述代码转换为C#,或者如果您的项目已经在使用它,则可以将其放入C++/CLI 程序集中。

【讨论】:

    【解决方案5】:

    用于可调整大小的窗口

    NON_CLIENT_AREA_HEIGHT = SystemParameters.WindowNonClientFrameThickness.Top +
                    SystemParameters.WindowNonClientFrameThickness.Bottom +
                    SystemParameters.WindowResizeBorderThickness.Top +
                    SystemParameters.WindowResizeBorderThickness.Bottom;
    
                NON_CLIENT_AREA_WIDTH = SystemParameters.WindowNonClientFrameThickness.Left +
                    SystemParameters.WindowNonClientFrameThickness.Right +
                    SystemParameters.WindowResizeBorderThickness.Left +
                    SystemParameters.WindowResizeBorderThickness.Right;
    

    【讨论】:

    • WindowNonClientFrameThickness 内部已包含 WindowResizeBorderThickness。唯一的区别是前者也获得了标题的高度。
    猜你喜欢
    • 2011-06-02
    • 2010-10-14
    • 2019-03-23
    • 2011-01-09
    • 2014-12-07
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 1970-01-01
    相关资源
    最近更新 更多