Getting the location of the Close button in the title bar


Today's Little Program locates the × button in the corner of the window and, just to show that it found it, displays a balloon tip pointing at it.

Let's start with the program from last week, the one that displays a balloon tip, then make these changes:

BOOL GetCloseButtonCenter(HWND hwnd, POINT *ppt)
{
 TITLEBARINFOEX info = { sizeof(info) };
 if (!SendMessage(hwnd, WM_GETTITLEBARINFOEX, 0, (LPARAM)&info))
    return FALSE;
 if (info.rgstate[5] & (STATE_SYSTEM_INVISIBLE |
                            STATE_SYSTEM_OFFSCREEN |
                            STATE_SYSTEM_UNAVAILABLE)) return FALSE;
 ppt->x = info.rgrect[5].left +
             (info.rgrect[5].right - info.rgrect[5].left) / 2;
 ppt->y = info.rgrect[5].top +
             (info.rgrect[5].bottom - info.rgrect[5].top) / 2;
 return TRUE;
}

  case TEXT(' '):
    if (GetCloseButtonCenter(hwnd, &pt)) {
      SendMessage(g_hwndTT, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y));

Instead of positioning the balloon at the cursor position, we put it at the center of the Close button. We use the WM_GET­TITLE­BAR­INFO­EX message to obtain information about the window title bar, specifically checking information about the Close button. After verifying that it is visible and on-screen and enabled, we calculate its center point and return success.

The WM_GET­TITLE­BAR­INFO­EX message is new for Windows Vista. Next time, we'll cook up a method that works on Windows 2000 and Windows XP.

Comments (19)
  1. Joshua says:

    Having played with custom title bars I can guess. I'm assuming this message is emulated if it gets passed to DefWindowProc.

  2. skSdnW says:

    The MSDN page for WM_GETTITLEBARINFOEX does not say anything about a return value :/

  3. Dan Bugglin says:

    @skSdnW no return value, you pass a TITLEBARINFOEX structure by reference as lparam.  The page doesn't outright state this but it does provided the needed info for you to figure this out.

  4. skSdnW says:

    @The MAZZTer: Raymond is checking the return value in the sample code! If it was documented that it returned BOOL you could probably fall back to calling GetTitleBarInfo on older systems, I'm guessing we will find out next time…

  5. @skSdnW: Just a wild guess (literally, having done no actual Win32 coding myself), but perhaps SendMessage will return FALSE if the program runs on versions of Windows that do not support the message constant?  I'm guessing nothing stops this from actually running on older platforms, just from compiling against older headers.

  6. skSdnW says:

    @MNGoldenEagle: Yes it should return FALSE on older versions (and disabled DWM?) but the important missing bit of documentation is if it returns TRUE on success. Since we don't have this piece of information you really need to initialize the items in the state array to STATE_SYSTEM_UNAVAILABLE before sending the message and not check the return value at all. Hopefully Raymond will chime in and clear this up…

  7. Joshua says:

    @Joker_vD: WM_NCPAINT + WM_NCCALCSIZE + WM_NCHITTEST

    blogs.msdn.com/…/10131176.aspx

  8. @skSdnW: Looking just at Raymond's example, it looks like he's already checking for STATE_SYSTEM_UNAVAILABLE and isn't initializing that value beforehand, so my guess is that if the function exists then it always returns true and simply sets the necessary state value if it wasn't able to retrieve the data.  It would be nice if the documentation made this more explicit, though.

  9. Nick says:

    @Nico: Visual Studio's title bar is fully custom. Windows 8 (and 8.1) has minimize-maximize/restore-close buttons in the same positions and sizes as it did on Windows 7 and Vista but Visual Studio 2012 and 2013 have equal-sized buttons (and a custom larger application icon in the left corner, too).

  10. Adam Gross says:

    It's too bad apps that customize these buttons don't always override WM_GETTITLEBARINFOEX to indicate where their buttons are.  I'm looking at you, Google Chrome: code.google.com/…/detail

  11. Joshua says:

    @Adam Gross: What do you expect from a program that runs on XP? The message didn't exist.

  12. Wear says:

    @Nico: Testing with a FindWindow(NULL, TEXT("Start Page – Microsoft Visual Studio")); call it seems to return reasonable results. The tooltip isn't quite where I'd expect it to be but it's not too far off.

  13. Matt says:

    @Joshua: "What do you expect from a program that runs on XP? The message didn't exist."

    I expect it to crash because of a virus because WindowsXP is out of service and anyone still using it gets no guarantees of anything.

  14. Joshua says:

    @Matt: It's the other way around. You are sending the message to a program that was designed for and runs on XP.

  15. Joker_vD says:

    @Joshua: One of our applications, in order to have a fancy title bar, just hid the normal one and then done some custom painting in the client area. And processing some system messages, to make Alt-Space do something, but you can't process *all* title bar related messages, so the experience still was a bit clunky.

  16. Nico says:

    What is the behavior of this on an application with a custom non-client area?  Visual Studio, for example, now paints itself to look like it's on Windows 8 (*sigh*).  Will the tooltip point to the place the close button would be, or will it fail one of the checks (INVISIBLE, for example)?  I've noticed that when a window doesn't respond to messages, Windows will repaint the non-client area with a default titlebar and borders; does that relate to this at all?

  17. Joker_vD says:

    Why can't Chrome handle WM_GETTITLEBARINFOEX? It will be a dead code when run on XP, sure, but it will do useful things on Windows 7+

    @Joshua: Thanks, I will pass your extremely detailed comment to the maintainers of that application — that is, when (and if) it will be maintained. After all, a borderless window with painted-in-client-area "borders" and "title" sorta works and almost nobody complains.

  18. Azarien says:

    @Joker_vD: I don't think it will be dead code.

    If your window has a custom handler for WM_GETTITLEBARINFOEX, and you actually send this message via SendMessage or PostMessage to that window, I think you'll get a response, even if Windows XP itself does not know that message.

    Just thinking. I have no time to test that now.

  19. nobugz says:

    This post isn't really complete without mentioning DwmGetWindowAttribute() with DWMWA_CAPTION_BUTTON_BOUNDS.  It's a bit like Rabi's response to the muon particle, "who ordered that?"

Comments are closed.

Skip to main content