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
上一篇: 【华为OD机试真题 Python语言】457、根据IP查找城市 | 机试真题+思路参考+代码解析(C卷)
下一篇: 【python】pyarrow.parquet+pandas:读取及使用parquet文件
本文标签
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。