QWindowKit实现Qt完美无边框窗口

9 篇文章 0 订阅

一、Qt无边框窗口

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

二、初识QWindowKit

最近用Qt做播放器的时候使用了一个第三方库https://github.com/stdware/qwindowkit
在这里插入图片描述

这个库是目前我见过最完美的Qt无边框窗口解决方案。
在这里插入图片描述

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

三、QWindowKit示例

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

1. git子项目导入qwindowkit

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加入如下代码

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/目录下所有文件加入进来。
在这里插入图片描述

结果如下

├─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

  • 37
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值