如何在WPF中计算非客户端窗口大小?

时间:2011-05-17 14:06:57

标签: c# .net wpf windows winapi

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

Non-client area - Aero

但是,SystemParameters会返回以下值:

SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21

我在这里禁用了Aero主题:

Non-client area - classic

现在,SystemParameters返回以下值:

SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18

如何使用SystemParameters

计算实际观测值

5 个答案:

答案 0 :(得分:32)

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

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

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

答案 1 :(得分:9)

我很确定GetSystemMetrics functionSystemParameters类在内部使用适当的参数调用)为系统返回正确的值,它只是返回是否禁用Aero主题的正确值。通过打开Aero,您可以获得更强大的边框和更高的窗口标题,这些都是多汁的图形效果。

如果您想获得这些窗口元素的正确大小,无论用户当前的主题如何(请记住,您可以使用经典主题,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。

答案 2 :(得分:0)

请参阅以下内容:

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。

答案 3 :(得分:0)

对于可重新调整大小的窗口

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;

答案 4 :(得分:0)

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

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

由于这些计算所需的每个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;
}

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