8

I was trying to get the height of the title bar of a specific window on Windows. You can replicate it with Notepad. I'm using C++ and none of the codes I found online yielded the correct result. Using e.g. Screenpresso I measured 31 pixels for my window bar height.

The functions I tried are the following:

TitleBarHeight.h:

#pragma once

#include <windows.h>

inline int get_title_bar_thickness_1(const HWND window_handle)
{
    RECT window_rectangle, client_rectangle;
    GetWindowRect(window_handle, &window_rectangle);
    GetClientRect(window_handle, &client_rectangle);
    return window_rectangle.bottom - window_rectangle.top -
        (client_rectangle.bottom - client_rectangle.top);
}

inline int get_title_bar_thickness_2(const HWND window_handle)
{
    RECT window_rectangle, client_rectangle;
    GetWindowRect(window_handle, &window_rectangle);
    GetClientRect(window_handle, &client_rectangle);
    return (window_rectangle.right - window_rectangle.left - client_rectangle.right) / 2;
}

Results:

auto window_handle = FindWindow("Notepad", nullptr);
auto a = get_title_bar_thickness_1(window_handle); // 59
auto b = get_title_bar_thickness_2(window_handle); // 8
auto c = GetSystemMetrics(SM_CXSIZEFRAME); // 4
auto d = GetSystemMetrics(SM_CYCAPTION); // 23

Getting the system metrics with GetSystemMetrics() does not work because windows can have different title bar heights obviously and there is no argument for the window handle.

How can I really get the result of 31?

Donald Duck
  • 8,409
  • 22
  • 75
  • 99
BullyWiiPlaza
  • 17,329
  • 10
  • 113
  • 185
  • 2
    Not sure how you measured 31, but the value obtained from (1) seems to be correct. also there is `SM_CYCAPTION` query value for `GetSystemMetrics` to retrieve caption size – user7860670 Jun 11 '19 at 18:41
  • 1
    What exactly do you mean by height? Top window edge down to client or just the blue part in classic style? Any client vs window calculation is going to include the borders as well. – Anders Jun 11 '19 at 19:12
  • 1
    I'm going to add a nonsensical b+d = 31... Coincidence, I think not! – CurtisJC Jun 11 '19 at 19:12
  • 31 is also the height on my OS (Windows 10) and I get 30 with **DWMWA_CAPTION_BUTTON_BOUNDS** – Castorix Jun 11 '19 at 19:24
  • There is a lot of appcompat involved with the frame size. Looks like your monitor is operating at 120 dpi (aka 125%), declare your app to be dpiAware so it can stop lying to you. Programmatically with SetProcessDPIAware(). Should bump up the SM_CYCAPTION to 29, where you are going to get the extra 2 from is unclear. Do beware than on Win10 most of the border is transparent, visible by hovering the mouse near it. – Hans Passant Jun 11 '19 at 20:05

4 Answers4

4

Send a message WM_GETTITLEBARINFOEX to the window, and you will get the bounding rectangle of the title bar.

TITLEBARINFOEX * ptinfo = (TITLEBARINFOEX *)malloc(sizeof(TITLEBARINFOEX));
ptinfo->cbSize = sizeof(TITLEBARINFOEX);
SendMessage(hWnd, WM_GETTITLEBARINFOEX,0, (LPARAM)ptinfo);
int height = ptinfo->rcTitleBar.bottom- ptinfo->rcTitleBar.top;
int width = ptinfo->rcTitleBar.right - ptinfo->rcTitleBar.left;
free(ptinfo);
Drake Wu
  • 6,927
  • 1
  • 7
  • 30
  • No, it also doesn't work on Windows 10 (where DWM is used) The closest result is DWMWA_CAPTION_BUTTON_BOUNDS + 1 for the border (30 + 1) – Castorix Jun 12 '19 at 07:47
  • This returns `25` as height and `128` as width which is incorrect as well – BullyWiiPlaza Jun 12 '19 at 12:19
  • @BullyWiiPlaza Does the measurement of 31 include the menu bar? – Drake Wu Jun 13 '19 at 03:20
  • @DrakeWu-MSFT It excludes the menu bar – BullyWiiPlaza Jun 13 '19 at 09:04
  • Is your process high DPI aware? From 25 to 31 sounds a lot like a 125% scaling factor. – Adrian McCarthy Jun 13 '19 at 23:00
  • 1
    I think this is the best way, including Windows 10, but the WM_GETTITLEBARINFOEX call must be sent (again) after some message is received. From my tests, good candidates are WM_SHOWWINDOW, WM_NCPAINT, WM_ERASEBKGND or WM_DWMNCRENDERINGCHANGED. Not sure what's the good one... I'd say WM_NCPAINT – Simon Mourier Jun 13 '20 at 08:43
4

Assuming that you don't have menu bar, you can map points from client coordinate system to screen one

RECT wrect;
GetWindowRect( hwnd, &wrect );
RECT crect;
GetClientRect( hwnd, &crect );
POINT lefttop = { crect.left, crect.top }; // Practicaly both are 0
ClientToScreen( hwnd, &lefttop );
POINT rightbottom = { crect.right, crect.bottom };
ClientToScreen( hwnd, &rightbottom );

int left_border = lefttop.x - wrect.left; // Windows 10: includes transparent part
int right_border = wrect.right - rightbottom.x; // As above
int bottom_border = wrect.bottom - rightbottom.y; // As above
int top_border_with_title_bar = lefttop.y - wrect.top; // There is no transparent part

Got 8, 8, 8 and 31 pixels (96DPI aka 100% scaling setting)

You should also take into account DPI awareness mode. Especially GetSystemMetrics is tricky because it remembers state for System DPI when your application was launched.

Daniel Sęk
  • 2,504
  • 1
  • 8
  • 17
2

First, make sure your application is high DPI aware so that the system doesn't lie to you.

Options:

  1. Trust GetSystemMetrics. Nearly any top-level window that actually has a different caption size is doing custom non-client area management which is going to make it (nearly) impossible. The obvious exception is a tool window (WS_EX_TOOLWINDOW) which probably has a SM_CYSMCAPTION height if the WS_CAPTION style is also set.

  2. Get the target window rect and the target window's style. Use AdjustWindowRectEx to determine the size differences with the WS_CAPTION style toggled. I'm not sure if this will work because there may be some interaction between on whether you can have a caption without some kind of border.

  3. Get the target window rect and send WM_HITTEST messages for coordinates that move down the window. Count how many of those get HT_CAPTION in return. Bonus points if you do this with a binary search rather than a linear search. This is probably the hardest and the most reliable way to do it, assuming the window has a rectangular caption area.

Adrian McCarthy
  • 45,555
  • 16
  • 123
  • 175
1

If I've understood correctly, it looks like you want to take the border size of the window (which we should be able to gather from the width as there is no title bar) and subtract it from the the verticle size minus the client window...

inline int get_title_bar_thickness(const HWND window_handle)
{
    RECT window_rectangle, client_rectangle;
    int height, width;
    GetWindowRect(window_handle, &window_rectangle);
    GetClientRect(window_handle, &client_rectangle);
    height = (window_rectangle.bottom - window_rectangle.top) -
    (client_rectangle.bottom - client_rectangle.top);
    width = (window_rectangle.right - window_rectangle.left) - 
    (client_rectangle.right - client_rectangle.left);
    return height - (width/2);
}
CurtisJC
  • 680
  • 3
  • 9
  • 23
  • This probably a good solution but top/bottom vs left/right border sizes could be different (in theory at least). – Anders Jun 11 '19 at 19:13
  • `GetWindowRect(hWnd,&rect_window); DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS, &rect_bounds, sizeof(RECT));` You can compare with the 2 rects to get the exact bound value.https://stackoverflow.com/questions/34139450/getwindowrect-returns-a-size-including-invisible-borders – Drake Wu Jun 13 '19 at 03:25
  • Don't you need a ClientToScreen or ScreenToClient call to translate between client coords (where top-left is at 0,0) and global windows coords. – selbie Jun 16 '19 at 16:40