Language: English | 简体中文
Project README: English | 简体中文
This tutorial shows how to use QWindowKit in Qt Widgets and Qt Quick applications to build a custom title bar while keeping native behaviors such as moving, resizing, system menus, and Windows Snap Layout.
QWindowKit provides three modules:
QWindowKit::Core: shared platform window infrastructure.QWindowKit::Widgets: integration forQWidgetandQMainWindow.QWindowKit::Quick: integration forQQuickWindowand QMLWindow.
- Qt 5.12 or later. For real applications, Qt 5.15.2+ or Qt 6.6.2+ is recommended.
- A C++17 compiler.
- CMake 3.19 or later.
- Windows, macOS, or Linux.
Build from source:
git clone --recursive https://github.com/stdware/qwindowkit
cd qwindowkit
cmake -S . -B build ^
-DCMAKE_PREFIX_PATH=C:/Qt/6.7.3/msvc2019_64 ^
-DCMAKE_INSTALL_PREFIX=C:/Libraries/QWindowKit ^
-DQWINDOWKIT_BUILD_WIDGETS=ON ^
-DQWINDOWKIT_BUILD_QUICK=ON ^
-DQWINDOWKIT_BUILD_EXAMPLES=OFF
cmake --build build --config Release
cmake --install build --config ReleaseOn Linux or macOS:
cmake -S . -B build \
-DCMAKE_PREFIX_PATH=/path/to/Qt \
-DCMAKE_INSTALL_PREFIX=/opt/qwindowkit \
-DQWINDOWKIT_BUILD_WIDGETS=ON \
-DQWINDOWKIT_BUILD_QUICK=ON
cmake --build build --config Release
cmake --install build --config ReleaseCommon options:
-DQWINDOWKIT_BUILD_WIDGETS=ON
-DQWINDOWKIT_BUILD_QUICK=ON
-DQWINDOWKIT_BUILD_EXAMPLES=ON
-DQWINDOWKIT_BUILD_STATIC=OFF
-DQWINDOWKIT_FORCE_QT_WINDOW_CONTEXT=OFF
-DQWINDOWKIT_ENABLE_WINDOWS_SYSTEM_BORDERS=ONWidgets application:
cmake_minimum_required(VERSION 3.19)
project(MyWidgetsApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets)
find_package(QWindowKit REQUIRED COMPONENTS Widgets)
qt_add_executable(MyWidgetsApp
main.cpp
mainwindow.cpp
mainwindow.h
)
target_link_libraries(MyWidgetsApp PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
QWindowKit::Widgets
)Quick application:
cmake_minimum_required(VERSION 3.19)
project(MyQuickApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Core Gui Quick Qml)
find_package(QWindowKit REQUIRED COMPONENTS Quick)
qt_add_executable(MyQuickApp
main.cpp
)
qt_add_qml_module(MyQuickApp
URI MyQuickApp
VERSION 1.0
QML_FILES Main.qml
)
target_link_libraries(MyQuickApp PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Quick
Qt6::Qml
QWindowKit::Quick
)Configure your downstream project with the installed package path:
cmake -S . -B build -DQWindowKit_DIR=C:/Libraries/QWindowKit/lib/cmake/QWindowKitWidgets applications must set Qt::AA_DontCreateNativeWidgetSiblings before constructing QApplication.
main.cpp:
#include <QtCore/QCoreApplication>
#include <QtWidgets/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QApplication app(argc, argv);
MainWindow window;
window.resize(900, 600);
window.show();
return app.exec();
}mainwindow.h:
#pragma once
#include <QtWidgets/QMainWindow>
namespace QWK {
class WidgetWindowAgent;
}
class QWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override;
private:
void setupWindowAgent();
QWK::WidgetWindowAgent *m_windowAgent = nullptr;
};mainwindow.cpp:
#include "mainwindow.h"
#include <QtWidgets/QHBoxLayout>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QWidget>
#include <QWKWidgets/widgetwindowagent.h>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
setupWindowAgent();
auto *content = new QLabel(tr("Application content"), this);
content->setAlignment(Qt::AlignCenter);
setCentralWidget(content);
}
MainWindow::~MainWindow() = default;
void MainWindow::setupWindowAgent()
{
m_windowAgent = new QWK::WidgetWindowAgent(this);
m_windowAgent->setup(this);
auto *titleBar = new QWidget(this);
titleBar->setFixedHeight(40);
auto *iconButton = new QPushButton(QStringLiteral("App"), this);
auto *menuBar = new QMenuBar(this);
menuBar->addMenu(tr("&File"))->addAction(tr("E&xit"), this, &QWidget::close);
auto *titleLabel = new QLabel(windowTitle(), this);
titleLabel->setAlignment(Qt::AlignCenter);
auto *minButton = new QPushButton(QStringLiteral("-"), this);
auto *maxButton = new QPushButton(QStringLiteral("□"), this);
auto *closeButton = new QPushButton(QStringLiteral("x"), this);
auto *layout = new QHBoxLayout(titleBar);
layout->setContentsMargins(8, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(iconButton);
layout->addWidget(menuBar);
layout->addWidget(titleLabel, 1);
layout->addWidget(minButton);
layout->addWidget(maxButton);
layout->addWidget(closeButton);
setMenuWidget(titleBar);
m_windowAgent->setTitleBar(titleBar);
m_windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton);
m_windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton);
m_windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);
m_windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);
m_windowAgent->setHitTestVisible(menuBar, true);
connect(minButton, &QPushButton::clicked, this, &QWidget::showMinimized);
connect(maxButton, &QPushButton::clicked, this, [this] {
isMaximized() ? showNormal() : showMaximized();
});
connect(closeButton, &QPushButton::clicked, this, &QWidget::close);
}Key points:
- Each top-level window needs its own
WidgetWindowAgent. - Call
setup(this)as early as possible, especially before applying size constraints. setTitleBar()marks the draggable title bar area.- Controls inside the title bar that need mouse input must be registered with
setHitTestVisible(widget, true). - System button hints do not connect click behavior. You still connect the buttons yourself.
Menus should be hit-test visible:
auto *menuBar = new QMenuBar(this);
menuBar->addMenu(tr("&File"));
windowAgent->setHitTestVisible(menuBar, true);Use the same rule for search boxes, tab bars, and tool buttons:
windowAgent->setHitTestVisible(searchEdit, true);
windowAgent->setHitTestVisible(tabBar, true);
windowAgent->setHitTestVisible(settingsButton, true);Without this, QWindowKit treats those areas as draggable title bar space, so the controls will not receive normal mouse interaction.
Connect button behavior explicitly:
connect(minimizeButton, &QPushButton::clicked, window, &QWidget::showMinimized);
connect(maximizeButton, &QPushButton::clicked, window, [window] {
if (window->isMaximized()) {
window->showNormal();
} else {
window->showMaximized();
}
});
connect(closeButton, &QPushButton::clicked, window, &QWidget::close);Disable maximization:
setWindowFlag(Qt::WindowMaximizeButtonHint, false);Create a fixed-size window:
setFixedSize(640, 420);main.cpp:
#include <QtGui/QGuiApplication>
#include <QtQml/QQmlApplicationEngine>
#include <QtQuick/QQuickWindow>
#include <QWKQuick/qwkquickglobal.h>
int main(int argc, char *argv[])
{
QQuickWindow::setDefaultAlphaBuffer(true);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QWK::registerTypes(&engine);
engine.load(QUrl(QStringLiteral("qrc:/Main.qml")));
if (engine.rootObjects().isEmpty()) {
return 1;
}
return app.exec();
}QQuickWindow::setDefaultAlphaBuffer(true) is usually required for transparency, blur, Mica, and glass effects. Call it before creating Quick windows.
Main.qml:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import QWindowKit 1.0
Window {
id: window
width: 900
height: 600
visible: false
title: "QWindowKit Quick Window"
WindowAgent {
id: windowAgent
}
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: titleBar
Layout.fillWidth: true
height: 42
color: "#242936"
RowLayout {
anchors.fill: parent
spacing: 0
Text {
text: window.title
color: "white"
elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter
Layout.leftMargin: 12
Layout.fillWidth: true
}
Button {
id: minButton
text: "-"
onClicked: window.showMinimized()
}
Button {
id: maxButton
text: "□"
onClicked: {
if (window.visibility === Window.Maximized)
window.showNormal()
else
window.showMaximized()
}
}
Button {
id: closeButton
text: "x"
onClicked: window.close()
}
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "#f5f6f8"
Text {
anchors.centerIn: parent
text: "Application content"
}
}
}
Component.onCompleted: {
windowAgent.setup(window)
windowAgent.setTitleBar(titleBar)
windowAgent.setSystemButton(WindowAgent.Minimize, minButton)
windowAgent.setSystemButton(WindowAgent.Maximize, maxButton)
windowAgent.setSystemButton(WindowAgent.Close, closeButton)
window.visible = true
}
}If a control in the title bar needs mouse input, mark it as hit-test visible:
Button {
id: menuButton
text: "Menu"
}
Component.onCompleted: {
windowAgent.setHitTestVisible(menuButton, true)
}Windows supports several platform attributes. Set them after setup() succeeds.
windowAgent->setWindowAttribute(QStringLiteral("dark-mode"), true);
windowAgent->setWindowAttribute(QStringLiteral("mica"), true);
windowAgent->setWindowAttribute(QStringLiteral("mica-alt"), false);
windowAgent->setWindowAttribute(QStringLiteral("acrylic-material"), false);
windowAgent->setWindowAttribute(QStringLiteral("dwm-blur"), false);Set the Windows 11 border color:
windowAgent->setWindowAttribute(QStringLiteral("dwm-border-color"), QColor("#3367d6"));Disable the system menu:
windowAgent->setWindowAttribute(QStringLiteral("no-system-menu"), true);Show the system menu:
windowAgent->showSystemMenu(QCursor::pos());For Qt Quick on Windows 10/11 with D3D11 or D3D12, this workaround may help with a top white line. Keep it tied to the documented backend and Qt version constraints:
#if defined(Q_OS_WIN) && QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
qputenv("QSG_RHI_BACKEND", "d3d11");
qputenv("QT_QPA_DISABLE_REDIRECTION_SURFACE", "1");
#endifDo not enable QT_QPA_DISABLE_REDIRECTION_SURFACE with OpenGL or Vulkan.
On macOS, prefer native system buttons unless you have a strong reason to draw your own.
Show native system buttons:
windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false);Hide native system buttons:
windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), true);Enable blur:
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("dark"));
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("light"));
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("none"));On macOS 26 and later, Liquid Glass can be enabled when available:
windowAgent->setWindowAttribute(QStringLiteral("glass-effect"), QStringLiteral("regular"));
windowAgent->setWindowAttribute(QStringLiteral("glass-corner-radius"), 24.0);
windowAgent->setWindowAttribute(QStringLiteral("glass-tint-color"), QColor(255, 255, 255, 46));If the platform does not support an attribute, setWindowAttribute() returns false.
On Linux, QWindowKit can use the Qt fallback window context and provides partial system menu support on Qt 6 Wayland/X11. Behavior varies by desktop environment and window manager.
For compatibility-first applications, consider:
-DQWINDOWKIT_FORCE_QT_WINDOW_CONTEXT=ONMark the control or its parent container as hit-test visible:
windowAgent->setHitTestVisible(button, true);System buttons do not need an extra hit-test-visible call, but they do need a role:
windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);Windows 11 Snap Layout depends on the maximize button role:
windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);The window must also keep the maximize capability:
setWindowFlag(Qt::WindowMaximizeButtonHint, true);Call it early in the window constructor, before complex child widgets and size constraints when possible:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_agent = new QWK::WidgetWindowAgent(this);
m_agent->setup(this);
setMinimumSize(640, 480);
setupUi();
}In QML, call it in Component.onCompleted:
Component.onCompleted: {
windowAgent.setup(window)
windowAgent.setTitleBar(titleBar)
window.visible = true
}Do not rely on hot switching. Once QWindowKit has taken over the window frame, recreate the window if you need a different framing mode.
No. Install WidgetWindowAgent only on top-level windows.
Widgets:
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QApplication app(argc, argv);Quick:
QQuickWindow::setDefaultAlphaBuffer(true);
QGuiApplication app(argc, argv);
QWK::registerTypes(&engine);Do not copy debug environment variables from the examples into production code unless you explicitly need them.