QWindowKit实现Qt完美无边框窗口

noevil 2024-08-12 17:35:03 阅读 74

一、Qt无边框窗口

当使用Qt::FramelessWindowHint对Qt设置无边框窗口的时候是不能进行窗口大小缩放的。我曾经在 Qt6 multimedia开发一个摄像头录像机文章中实现了一个简单的无边框窗口拉伸改变窗口大小的方法,但是根本不平滑,当拉伸左边框或者上边框的时候窗口会明显抖动。

二、初识QWindowKit

最近用Qt做播放器的时候使用了一个第三方库https://github.com/stdware/qwindowkit

在这里插入图片描述

这个库是目前我见过最完美的Qt无边框窗口解决方案。

在这里插入图片描述

窗口操作很丝滑,还是跨平台的支持Windows\Linux\MacOS。

三、QWindowKit示例

qwindowkit项目本身自带了一个例子。但我这里要将其引入到自己项目作为示例。

1. git子项目导入qwindowkit

<code>git submodule add https://github.com/stdware/qwindowkit.git

cd qwindowkit/

git submodule update --recursive --init

在这里插入图片描述

将其作为git submodule 导入项目以后更新的时候比较方便。

他本身也含子模块,所以要使用–recursive参数

2. 关于qmsetup

qwindowkit使用了qmsetup来作为cmake构建系统的辅助工具,也是同一作者开发,可以用来增强cmake功能。

qmsetup有很多命令,可以值得了解一下。自己项目导入的时候不用它的命令也没关系,不影响原始cmake相关命令。

3. qwindowkit能不能使用qmake?

在做播放器的时候我曾想将其用qmake组织一下,最终只弄了个半成品——qmake 加入qwindowkit的编译出来的库。这也算是失败了,真正原因就是qwindowkit源代码中include了cmake生成的中间文件,如果要改成qmake得去改源代码include,显然这是不可取的。

所以结论就是:如果要用qmake,则先编译好qwindowkit,当外部库导入项目吧。

4. 使用cmake构建系统

如果要源码使用qwindowkit就得必须使用cmake构建系统。

在使用的时候我们考虑:

作为静态库链接。关闭项目的示例

在CMakeLists.txt加入如下代码

<code>set(QWINDOWKIT_BUILD_EXAMPLES OFF CACHE BOOL "")

set(QWINDOWKIT_BUILD_STATIC ON CACHE BOOL "")

5. 复制示例widgetframe

源码qwindowkit\examples\shared\widgetframe 这个目录包含了标题栏的实现。含有菜单、标题、最小最大按钮等到实现。

这部分代码在QWINDOWKIT_BUILD_EXAMPLES设置off之后就不会再编译了,我们可以直接拿来使用,复制到项目下与qwindowkit目录同级。

6. 复制皮肤资源

qwindowkit\examples\shared\resources下有两个目录app和window-bar可以复制到项目下skin/(新建这个目录)

复制qwindowkit\examples\mainwindow\dark-style.qss和

qwindowkit\examples\mainwindow\light-style.qss到skin 目录下

再新建skin/skin.qrc将skin/目录下所有文件加入进来。

在这里插入图片描述

结果如下

<code>├─skin

│ │ dark-style.qss

│ │ light-style.qss

│ │ skin.qrc

│ │

│ ├─app

│ │ example.icns

│ │ example.ico

│ │ example.png

│ │

│ └─window-bar

│ close.svg

│ fullscreen.svg

│ maximize.svg

│ minimize.svg

│ more-line.svg

│ restore.svg

└─widgetframe

CMakeLists.txt

windowbar.cpp

windowbar.h

windowbar_p.h

windowbutton.cpp

windowbutton.h

windowbutton_p.h

7. CMakeLists.txt加入变更

add_subdirectory(qwindowkit)

add_subdirectory(widgetframe)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)

qt_add_executable(QWindowKitDemo

MANUAL_FINALIZATION

${PROJECT_SOURCES}

skin/skin.qrc

)

else()

add_executable(QWindowKitDemo

${PROJECT_SOURCES}

skin/skin.qrc

)

endif()

qm_configure_target(QWindowKitDemo

QT_LINKS Widgets

LINKS QWKCore QWKWidgets WidgetFrame

)

qwindowkit 放在前面

skin/skin.qrc 加入到源码列表中

qm_configure_target 这个宏是qmsetup的命令,使用一下也无妨——add_subdirectory(qwindowkit)之后就可以使用了。

8. 代码中使用qwindowkit

a) main函数

这是为了解决某个bug,issue列表中有。

int main(int argc, char *argv[]) {

QGuiApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);

...

b) MainWindow

加入两个成员变量。每一个窗口都必须有一个QWK::WidgetWindowAgent。

enum Theme {

Dark,

Light,

};

Q_ENUM(Theme)

Theme currentTheme{ };

QWK::WidgetWindowAgent *windowAgent;

private:

void installWindowAgent();

void loadStyleSheet(Theme theme);

源码中,在启动ui控件之后初始化agent相关内容。

MainWindow::MainWindow(QWidget *parent)

: QMainWindow(parent), ui(new Ui::MainWindow) {

ui->;setupUi(this);

// 放在ui之后

installWindowAgent();

setWindowTitle(tr("QWindowKit 示例 -- Noevil"));

loadStyleSheet(Light);

}

event 之中将标题栏激活, 才会匹配qss样式。

bool MainWindow::event(QEvent *event) {

switch (event->type()) {

case QEvent::WindowActivate: {

auto menu = menuWidget();

menu->setProperty("bar-active", true);

style()->polish(menu);

break;

}

case QEvent::WindowDeactivate: {

auto menu = menuWidget();

menu->setProperty("bar-active", false);

style()->polish(menu);

break;

}

default:

break;

}

return QMainWindow::event(event);

}

切换样式

void MainWindow::loadStyleSheet(Theme theme) {

if (!styleSheet().isEmpty() && theme == currentTheme)

return;

currentTheme = theme;

if (QFile qss(theme == Dark ? QStringLiteral(":/dark-style.qss")

: QStringLiteral(":/light-style.qss"));

qss.open(QIODevice::ReadOnly | QIODevice::Text)) {

setStyleSheet(QString::fromUtf8(qss.readAll()));

Q_EMIT themeChanged();

}

}

设置agent, 就是标题栏相关的东西,代码有点多,具体看仓库示例

void MainWindow::installWindowAgent() {

// 1. Setup window agent

windowAgent = new QWK::WidgetWindowAgent(this);

windowAgent->setup(this);

// 2. Construct your title bar

auto menuBar = [this]() {

auto menuBar = new QMenuBar();

// Virtual menu

auto file = new QMenu(tr("File(&F)"), menuBar);

file->addAction(new QAction(tr("New(&N)"), menuBar));

file->addAction(new QAction(tr("Open(&O)"), menuBar));

file->addSeparator();

auto edit = new QMenu(tr("Edit(&E)"), menuBar);

edit->addAction(new QAction(tr("Undo(&U)"), menuBar));

edit->addAction(new QAction(tr("Redo(&R)"), menuBar));

// Theme action

auto darkAction = new QAction(tr("Enable dark theme"), menuBar);

darkAction->setCheckable(true);

connect(darkAction, &QAction::triggered, this, [this](bool checked) {

loadStyleSheet(checked ? Dark : Light); //

});

connect(this, &MainWindow::themeChanged, darkAction,

[this, darkAction]() {

darkAction->setChecked(currentTheme == Dark); //

});

#ifdef Q_OS_WIN

auto dwmBlurAction = new QAction(tr("Enable DWM blur"), menuBar);

dwmBlurAction->setCheckable(true);

connect(dwmBlurAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("dwm-blur"),

checked)) {

return;

}

setProperty("custom-style", checked);

style()->polish(this);

});

auto acrylicAction =

new QAction(tr("Enable acrylic material"), menuBar);

acrylicAction->setCheckable(true);

connect(acrylicAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(

QStringLiteral("acrylic-material"), true)) {

return;

}

setProperty("custom-style", checked);

style()->polish(this);

});

auto micaAction = new QAction(tr("Enable mica"), menuBar);

micaAction->setCheckable(true);

connect(micaAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("mica"),

checked)) {

return;

}

setProperty("custom-style", checked);

style()->polish(this);

});

auto micaAltAction = new QAction(tr("Enable mica alt"), menuBar);

micaAltAction->setCheckable(true);

connect(micaAltAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("mica-alt"),

checked)) {

return;

}

setProperty("custom-style", checked);

style()->polish(this);

});

#elif defined(Q_OS_MAC)

auto darkBlurAction = new QAction(tr("Dark blur"), menuBar);

darkBlurAction->setCheckable(true);

connect(darkBlurAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"),

"dark")) {

return;

}

if (checked) {

setProperty("custom-style", true);

style()->polish(this);

}

});

auto lightBlurAction = new QAction(tr("Light blur"), menuBar);

lightBlurAction->setCheckable(true);

connect(lightBlurAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"),

"light")) {

return;

}

if (checked) {

setProperty("custom-style", true);

style()->polish(this);

}

});

auto noBlurAction = new QAction(tr("No blur"), menuBar);

noBlurAction->setCheckable(true);

connect(noBlurAction, &QAction::toggled, this, [this](bool checked) {

if (!windowAgent->setWindowAttribute(QStringLiteral("blur-effect"),

"none")) {

return;

}

if (checked) {

setProperty("custom-style", false);

style()->polish(this);

}

});

auto macStyleGroup = new QActionGroup(menuBar);

macStyleGroup->addAction(darkBlurAction);

macStyleGroup->addAction(lightBlurAction);

macStyleGroup->addAction(noBlurAction);

#endif

// Real menu

auto settings = new QMenu(tr("Settings(&S)"), menuBar);

settings->addAction(darkAction);

#ifdef Q_OS_WIN

settings->addSeparator();

settings->addAction(dwmBlurAction);

settings->addAction(acrylicAction);

settings->addAction(micaAction);

settings->addAction(micaAltAction);

#elif defined(Q_OS_MAC)

settings->addAction(darkBlurAction);

settings->addAction(lightBlurAction);

settings->addAction(noBlurAction);

#endif

menuBar->addMenu(file);

menuBar->addMenu(edit);

menuBar->addMenu(settings);

return menuBar;

}();

menuBar->setObjectName(QStringLiteral("win-menu-bar"));

auto titleLabel = new QLabel();

titleLabel->setAlignment(Qt::AlignCenter);

titleLabel->setObjectName(QStringLiteral("win-title-label"));

#ifndef Q_OS_MAC

auto iconButton = new QWK::WindowButton();

iconButton->setObjectName(QStringLiteral("icon-button"));

iconButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

auto minButton = new QWK::WindowButton();

minButton->setObjectName(QStringLiteral("min-button"));

minButton->setProperty("system-button", true);

minButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

auto maxButton = new QWK::WindowButton();

maxButton->setCheckable(true);

maxButton->setObjectName(QStringLiteral("max-button"));

maxButton->setProperty("system-button", true);

maxButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

auto closeButton = new QWK::WindowButton();

closeButton->setObjectName(QStringLiteral("close-button"));

closeButton->setProperty("system-button", true);

closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);

#endif

auto windowBar = new QWK::WindowBar();

#ifndef Q_OS_MAC

windowBar->setIconButton(iconButton);

windowBar->setMinButton(minButton);

windowBar->setMaxButton(maxButton);

windowBar->setCloseButton(closeButton);

#endif

windowBar->setMenuBar(menuBar);

windowBar->setTitleLabel(titleLabel);

windowBar->setHostWidget(this);

windowAgent->setTitleBar(windowBar);

#ifndef Q_OS_MAC

windowAgent->setSystemButton(QWK::WindowAgentBase::WindowIcon, iconButton);

windowAgent->setSystemButton(QWK::WindowAgentBase::Minimize, minButton);

windowAgent->setSystemButton(QWK::WindowAgentBase::Maximize, maxButton);

windowAgent->setSystemButton(QWK::WindowAgentBase::Close, closeButton);

#endif

windowAgent->setHitTestVisible(menuBar, true);

#ifndef Q_OS_MAC

connect(windowBar, &QWK::WindowBar::minimizeRequested, this,

&QWidget::showMinimized);

connect(windowBar, &QWK::WindowBar::maximizeRequested, this,

[this, maxButton](bool max) {

if (max) {

showMaximized();

} else {

showNormal();

}

// It's a Qt issue that if a QAbstractButton::clicked triggers a

// window's maximization, the button remains to be hovered until

// the mouse move. As a result, we need to manually send leave

// events to the button. emulateLeaveEvent(maxButton);

});

connect(windowBar, &QWK::WindowBar::closeRequested, this, &QWidget::close);

#endif

}

目前示例中实现的按钮中不包含全屏按钮响应,这个要自己去实现。

四、源码

https://gitee.com/noevilme/QtDemo/tree/master/QWindowKitDemo



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。