语言: 简体中文 | English
本文面向希望在 Qt Widgets 或 Qt Quick 应用中自定义系统标题栏、实现无边框窗口、保留拖拽/缩放/系统菜单等原生行为的用户。
QWindowKit 分为三个模块:
QWindowKit::Core:公共基础和平台窗口上下文。QWindowKit::Widgets:用于QWidget/QMainWindow。QWindowKit::Quick:用于QQuickWindow/QMLWindow。
- Qt 5.12 或更高版本。实际项目建议 Qt 5.15.2+ 或 Qt 6.6.2+。
- C++17 编译器。
- CMake 3.19 或更高版本。
- Windows、macOS 或 Linux。
从源码构建并安装:
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 ReleaseLinux/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 Release常用构建选项:
-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 应用:
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 应用:
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
)配置下游项目时指定安装路径:
cmake -S . -B build -DQWindowKit_DIR=C:/Libraries/QWindowKit/lib/cmake/QWindowKitWidgets 应用必须在创建 QApplication 前设置 Qt::AA_DontCreateNativeWidgetSiblings。
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 QPushButton;
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/QVBoxLayout>
#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->setObjectName(QStringLiteral("TitleBar"));
titleBar->setFixedHeight(40);
auto *iconButton = new QPushButton(this);
iconButton->setText(QStringLiteral("App"));
auto *titleLabel = new QLabel(windowTitle(), this);
titleLabel->setAlignment(Qt::AlignCenter);
auto *menuBar = new QMenuBar(this);
auto *fileMenu = menuBar->addMenu(tr("&File"));
fileMenu->addAction(tr("E&xit"), this, &QWidget::close);
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);
}要点:
- 每个顶层窗口都需要自己的
WidgetWindowAgent。 setup(this)越早调用越好,尤其是窗口有最小/最大尺寸约束时。setTitleBar()指定可拖拽标题栏区域。- 标题栏内需要接收鼠标事件的控件必须调用
setHitTestVisible(widget, true)。 - 系统按钮提示只告诉 QWindowKit 这些控件的角色;按钮点击行为仍需要自己连接。
菜单栏应设置为 hit-test visible:
auto *menuBar = new QMenuBar(this);
menuBar->addMenu(tr("&File"));
windowAgent->setHitTestVisible(menuBar, true);搜索框、标签页、工具按钮也一样:
windowAgent->setHitTestVisible(searchEdit, true);
windowAgent->setHitTestVisible(tabBar, true);
windowAgent->setHitTestVisible(settingsButton, true);如果不这样做,这些控件所在区域会被当成拖拽区域,用户无法正常点击控件。
QWindowKit 不会自动连接按钮行为。建议显式连接:
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);禁用最大化:
setWindowFlag(Qt::WindowMaximizeButtonHint, false);固定大小窗口:
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) 对 Windows/macOS 的透明、模糊、Mica、玻璃等效果通常是必要的,应该在创建 Quick 窗口前调用。
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: window.visibility === Window.Maximized ? "□" : "□"
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
}
}如果标题栏内有可点击控件,例如菜单按钮或搜索框,需要设置 hit-test visible:
Button {
id: menuButton
text: "Menu"
}
Component.onCompleted: {
windowAgent.setHitTestVisible(menuButton, true)
}Windows 支持一些平台属性。使用前应先完成 setup()。
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);设置 Windows 11 边框颜色:
windowAgent->setWindowAttribute(QStringLiteral("dwm-border-color"), QColor("#3367d6"));关闭系统菜单:
windowAgent->setWindowAttribute(QStringLiteral("no-system-menu"), true);显示系统菜单:
windowAgent->showSystemMenu(QCursor::pos());Qt Quick 在 Windows 10/11 使用 D3D11/D3D12 时,如果遇到顶部白线,可按 README 的限制条件设置:
#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");
#endif不要在 OpenGL 或 Vulkan 后端下启用 QT_QPA_DISABLE_REDIRECTION_SURFACE。
默认建议使用 macOS 系统按钮,不必设置自绘关闭/最小化/缩放按钮。
显示系统按钮:
windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), false);隐藏系统按钮:
windowAgent->setWindowAttribute(QStringLiteral("no-system-buttons"), true);启用模糊:
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("dark"));
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("light"));
windowAgent->setWindowAttribute(QStringLiteral("blur-effect"), QStringLiteral("none"));macOS 26 及更高版本可尝试 Liquid Glass:
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));如果平台不支持,setWindowAttribute() 会返回 false。
Linux 下 QWindowKit 优先使用 Qt 窗口上下文,并在 Qt 6 的 Wayland/X11 环境中提供部分系统菜单支持。不同桌面环境和窗口管理器行为差异较大,建议:
-DQWINDOWKIT_FORCE_QT_WINDOW_CONTEXT=ON在兼容性优先的应用中可以强制使用 Qt fallback 实现。
把按钮或其父容器设置为 hit-test visible:
windowAgent->setHitTestVisible(button, true);系统按钮不需要额外设置 hit-test visible,但需要设置 system button 角色:
windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);Windows 11 的 Snap Layout 依赖最大化按钮的 system button 提示:
windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);同时窗口必须保留最大化能力:
setWindowFlag(Qt::WindowMaximizeButtonHint, true);尽量在窗口构造函数前半段调用,早于尺寸约束和复杂子控件初始化:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_agent = new QWK::WidgetWindowAgent(this);
m_agent->setup(this);
setMinimumSize(640, 480);
setupUi();
}Quick 中通常在 Component.onCompleted 调用:
Component.onCompleted: {
windowAgent.setup(window)
windowAgent.setTitleBar(titleBar)
window.visible = true
}不建议。QWindowKit 接管窗口后,不支持无损热切换回系统标题栏。需要销毁并重建窗口。
不建议。WidgetWindowAgent 只应安装在顶层窗口上。
Widgets:
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QApplication app(argc, argv);Quick:
QQuickWindow::setDefaultAlphaBuffer(true);
QGuiApplication app(argc, argv);
QWK::registerTypes(&engine);不要把示例中的调试环境变量直接复制到生产应用,除非你明确需要它们。