37

WPF has the SystemParameters class that exposes a great number of system metrics. On my computer I have noticed that a normal window has a title that is 30 pixels high and a border that is 8 pixels wide. This is on Windows 7 with the Aero theme enabled:

Non-client area - Aero

However, SystemParameters return the following values:

SystemParameters.BorderWidth = 5
SystemParameters.CaptionHeight = 21

Here I have disabled the Aero theme:

Non-client area - classic

Now, SystemParameters return the following values:

SystemParameters.BorderWidth = 1
SystemParameters.CaptionHeight = 18

How do I compute the actual observed values by using SystemParameters?

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
  • 1
    You've done a great job of illustrating the metrics, thanks so much. This should go in MSDN documentation! – Sabuncu Oct 15 '15 at 11:50

5 Answers5

32

For a resizable window you need to use a different set of parameters to compute the size:

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

These sizes will change when you modify the theme.

Martin Liversage
  • 104,481
  • 22
  • 209
  • 256
Tim
  • 14,999
  • 1
  • 45
  • 68
  • Yeah, adding 8 is a hack I've seen before to make these values match the expected values for the Aero theme. But it's hardly a foolproof approach. Window themes are something that Microsoft is apparently quite fond of re-inventing, and things fall apart even under the current system unless you specifically check which theme the user is currently running—Classic, Aero Basic, or Aero. – Cody Gray - on strike May 17 '11 at 14:49
  • I'm not really looking at that as being a hack in this case. The caption is 22 pixels, and the border is 8 pixels on all four sides. that gives you the correct values. This works when you change the theme - it returns the correct values for Classic, Aero Basic and Aero. – Tim May 17 '11 at 14:55
  • You have provided the correct answer but I have taken the liberty to edit it to more clearly show what the answer is by removing the "edit history". – Martin Liversage May 17 '11 at 17:59
  • @Martin Liversage: Sounds good. Now that it's "settled" that makes a lot of sense. Thanks! – Tim May 17 '11 at 18:00
  • Can anyone tell me what event occurs when you change the theme? I want to change max, min of window size – sees Mar 27 '13 at 00:19
  • 1
    It seems that here is the answer: [detect-system-theme-change-in-wpf](http://stackoverflow.com/questions/6360671/detect-system-theme-change-in-wpf) – sees Mar 27 '13 at 00:25
  • these titleheight, verticalBorderWith is not correct after some theme is changed. For example: "Windows classic" to "Windows 7 Basic" when your application is still running. I tried in WM_THEMECHANGED event – sees Mar 27 '13 at 04:53
  • The event SystemEvents.UserPreferenceChanged also does the trick. FYI: [UserPreferenceChanged(in Japaense)](http://dobon.net/vb/dotnet/system/userpreferencechanged.html) – sees Mar 27 '13 at 06:30
  • In my case screen with is 1920px, maximized window ActualWidth is 1936, 8px border with, but SystemParameters.ResizeFrameVerticalBorderWidth returns 4. @sees link worked for me. I'm taking ActualWidth of Windows' content, which is 1920. – Samvel Siradeghyan Mar 04 '14 at 13:48
  • 2
    On Windows 8.1, `ResizeFrameVerticalBorderWidth` gives me 4, but the real border width is 7. – kol Sep 28 '14 at 11:13
  • 2
    @kol `SystemParameters.ResizeFrameVerticalBorderWidth + SystemParameters.FixedFrameVerticalBorderWidth // + SystemParameters.BorderWidth` gives me the right value (I got 8 with java, but im not sure if its 8 or 7, so idk if BorderWidth matters, I think its the inner gray line. – yyny Apr 28 '15 at 19:24
  • @YoYoYonnY: It seems 8 is right, so the above code _with_ "+SystemParameters.BorderWidth" gets the right result on Win7 Aero. If possible, I'll test what happens on Win8.1 and Win10. – PMF May 11 '16 at 12:51
9

I'm pretty sure that the GetSystemMetrics function (which the SystemParameters class calls internally with the appropriate arguments) is returning the correct values for your system, it's just returning the correct values in the case whether the Aero theme is disabled. By turning on Aero, you get beefier borders and taller window captions, all the name of juicy graphical goodness.

If you want to get the correct size of these window elements, regardless of the user's current theme (remember, you can run Windows Vista and beyond with the Classic theme, the Aero Basic theme, or the full Aero theme, all of which are going to have different-sized UI elements), you need to use a different method available in Vista and later.

You need to send the window a WM_GETTITLEBARINFOEX message in order to request extended title bar information. The wParam is unused and should be zero. The lParam contains a pointer to a TITLEBARINFOEX structure that will receive all of the information. The caller is responsible for allocating memory for this structure and setting its cbSize member.

To do all of this from a .NET application, you'll obviously need to do some P/Invoke. Start by defining the constants you need, as well as the TITLEBARINFOEX structure:

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

Then define the SendMessage function accordingly:

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

And finally, you can call all of that mess using something like the following code:

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

EDIT: Now tested and working on my notebook running Windows 7.

Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
  • 1
    Seems to work. Small warning when using this code in WPF, as this code expects windows forms `Rectangle`, not WPF `Rectangle` - took me some time to figure it out, otherwise you're not going to get results. – Erti-Chris Eelmaa Dec 30 '14 at 00:29
  • This work up to windows 8.1, but does not work on windows 10, do you know how to fix? I posted a new question here - https://stackoverflow.com/questions/60806098/compute-total-width-of-title-bar-buttons-for-3rd-party-window-on-windows-10 – Victor Zakharov Mar 22 '20 at 23:34
1

This is a C++/CLI answer, and it is NOT using SystemParameters, but I think this is a better approach to this problem, since it should be correct for any window.

In fact, the other answers are only valid for a resizable window, and one must create different cases for each of the available WindowStyles.

Since for every SystemParameters needed for these calculations there is a documented SM_CX* or SM_CY* value, I thought that, instead of re-inventing the wheel, one could simply use the WinAPI AdjustWindowRectEx function.

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

One can easily convert the above code to C# using some more DllImports, or this can be dropped into a C++/CLI assembly if your project is already using one.

gog
  • 1,220
  • 11
  • 30
  • `AdjustWindowRectEx` is an awesome find. The provided code needs a little help but it put me on the right track. Thanks! – caesay Jun 17 '22 at 06:12
0

For re-sizable window

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;
Manikandan
  • 673
  • 3
  • 12
  • 26
  • `WindowNonClientFrameThickness` already contains `WindowResizeBorderThickness` internally. The only difference is that the former gets the height of the caption as well. – Nicke Manarin Oct 17 '17 at 17:23
0

Refer to the following:

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

I presume you are trying to calculate the size you must make the Applications Window in order to give the right amount of client area to fully show some WPF content?

If so, then just remember that WPF's pixels are at 96dpi, and your display may be running at a different dpi...also as mentioned by other answers the theme affects how big you'd have to size your main Window to get your desired client area.

Alternatively, you might be able to use MinWidth/MinHeight on the child control of the Window.

Colin Smith
  • 12,375
  • 4
  • 39
  • 47
  • He's explicitly asking about the **non-client** area. Most of your answer and the linked post are about the client area. It's pretty silly to determine the NC area just to calculate the dimensions of the client area when dtermining the dimensions of the latter is *much* simpler. – Cody Gray - on strike May 17 '11 at 15:07