How can I create a Visual Studio 2022-style custom title bar in a C++/Qt Win32 application?

LoveC 81 信誉分
2026-08-18T18:57:38.0066667+00:00

I am developing a traditional Win32 desktop application in C++ with Qt 6.11 and QMainWindow. This is not a UWP application. I would like to create a title bar similar to Visual Studio 2022, with a customizable title-bar height, application icon size and alignment, and title text that can be hidden or centered. I also want to place Qt controls such as a menu bar, buttons, or an edit box in the title-bar area, and possibly add a custom button near the native caption buttons.

The native Windows minimize, maximize, and close buttons should remain unchanged, including their normal hover and pressed effects, system menu, window dragging, resizing, and Snap behavior. I do not want to use a frameless window, replace or owner-draw the native caption buttons, or create a separate top-level window attached to the title bar. I would like to achieve this effect through the native Windows window frame while continuing to use Qt for the application interface.

I am looking for guidance or a complete C++/Qt example showing how this can be implemented correctly in a Win32 application. Thank you to everyone in the community who can provide an explanation or working solution.用户的图像

Windows 开发 | Windows API - Win32
0 个注释 无注释

问题作者接受的答案
Taki Ly (WICLOUD CORPORATION) 5,055 信誉分 Microsoft 外部员工 审查方
2026-08-19T06:50:30.7966667+00:00

Hello @LoveC ,

I put together a small Qt 6 + Win32 prototype on Windows 11 to explore this, so the notes below are based on what I observed in that test rather than a definitive statement.

One point that may be worth checking first: the classic "DWM custom frame" trick (returning 0 from WM_NCCALCSIZE) appears to have worked on Windows 7 Aero, but on Windows 10/11 it seems the system no longer draws the Min/Max/Close buttons once the standard frame is removed. In my test the top-right area came up empty. This suggests that keeping the system-drawn caption buttons AND placing widgets on the title-bar row AND avoiding both owner-draw and a frameless window may not all be possible at once on Win11. It might be relevant that VS2022, Windows Terminal and the browsers all seem to paint their own button glyphs rather than relying on the system.

One approach that seemed to work well for me (and stays close to VS2022):

  1. Keep the native frame and extend the client over the title bar with WM_NCCALCSIZE + DwmExtendFrameIntoClientArea (this appears to preserve shadow, resize and Snap).
  2. Paint just the button glyphs yourself with QPainter.
  3. In WM_NCHITTEST, return HTMINBUTTON / HTMAXBUTTON / HTCLOSE over the buttons. The system then seems to handle the actual minimize/maximize/close and show the Win11 Snap Layout flyout on the Maximize button. Return HTCAPTION for the empty area (drag) and HTLEFT/HTRIGHT/... for the edges (resize).

Core of nativeEvent on your QMainWindow (it helped to call DwmDefWindowProc first):

bool MainWindow::nativeEvent(const QByteArray&, void* message, qintptr* result) {
    MSG* msg = static_cast<MSG*>(message);
    LRESULT lRet = 0;
    if (DwmDefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam, &lRet)) {
        *result = lRet; return true;
    }
    switch (msg->message) {
    case WM_NCCALCSIZE:
        if (msg->wParam == TRUE) { *result = 0; return true; } // extend client over title bar
        break;
    case WM_NCHITTEST: {
        int btn = captionButtonAt(msg->hwnd, msg->lParam); // 0=min,1=max,2=close,-1=none
        if (btn != -1) {
            *result = (btn==0)?HTMINBUTTON : (btn==1)?HTMAXBUTTON : HTCLOSE;
            return true; // system does the action + Win11 Snap flyout
        }
        LRESULT ht = hitTestNCA(msg->hwnd, msg->lParam, gTitleBarHeight); // HTCAPTION / edges
        if (ht != HTNOWHERE) { *result = ht; return true; }
        break;
    }
    }
    return QMainWindow::nativeEvent(QByteArray(), message, result);
}

And extending the frame once the window exists:

MARGINS m = { 0, 0, gTitleBarHeight, 0 };
DwmExtendFrameIntoClientArea(hWnd, &m);
SetWindowPos(hWnd, nullptr, 0,0,0,0, SWP_FRAMECHANGED|SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER);

hitTestNCA is the helper from the DWM custom frame sample (Appendix C). The icon, menu bar, search box and custom button can be normal Qt widgets in the title-bar layout, and it may help to reserve roughly 3 * buttonWidth on the right so they do not overlap the buttons. Two things that caught me out: the borders may need compensating in WM_NCCALCSIZE when maximized so the top is not clipped, and the hit-test point likely needs dividing by devicePixelRatioF() for high-DPI.

In my testing, checking with SendMessage(WM_NCHITTEST) showed the buttons returning HTMINBUTTON/HTMAXBUTTON/HTCLOSE and the empty area returning HTCAPTION, with the Snap flyout appearing as expected, though your setup may differ. I would be glad to look closer at any part of this. If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

Thank you.

此答案是否有帮助?

2 个人认为此答案很有帮助。

1 个其他答案

排序依据: 最早
  1. Taki Ly (WICLOUD CORPORATION) 5,055 信誉分 Microsoft 外部员工 审查方
    2026-08-19T01:56:21.4766667+00:00

    Hi @LoveC ,

    I'm working on this issue and will try to get back to you soon. Thank you for your patience.

    此答案是否有帮助?


你的答案

提问者可以将答案标记为“已接受”,审查方可以将答案标记为“已推荐”,这有助于用户了解答案是否解决了提问者的问题。