• C++ Qt 学习(三):无边框窗口设计


    1. 无边框窗口

    在这里插入图片描述

    1.1 主窗口实现

    • MainWidget.h
    #pragma once
    
    #include 
    #include "CTitleBar.h"
    #include "CFrameLessWidgetBase.h"
    
    // 主窗口 MainWidget 继承自无边框窗口公用类 CFrameLessWidgetBase
    class MainWidget : public CFrameLessWidgetBase {
        Q_OBJECT
    
    public:
        MainWidget(QWidget *parent = Q_NULLPTR);
    
    private slots:
        void on_closeSlot();
    
    private:
        void initUI();
    
    private:
        CTitleBar* m_pTitleBar = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • MainWidget.cpp
    #include "MainWidget.h"
    #include 
    #include 
    
    MainWidget::MainWidget(QWidget *parent) : CFrameLessWidgetBase(parent) {
        initUI();
    }
    
    void MainWidget::on_closeSlot() {
        // 显示一个警告对话框,询问用户是否要退出
        // 参数:创建对话框的父窗口,对话框标题,对话框内容,对话框按钮组合,默认值
        QMessageBox::StandardButton _exit = QMessageBox::warning(this, u8"提示", u8"确定要退出吗", 
            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
    
        if (_exit == QMessageBox::Yes) {
            close();
        }
    }
    
    void MainWidget::initUI() {
        // 创建标题栏和主窗口
        m_pTitleBar = new CTitleBar(this);
        QWidget* w = new QWidget(this);
        w->setMinimumSize(800, 600);  // 初始化主窗口大小
    
        // 将标题栏和主窗口放入垂直布局中
        QVBoxLayout* pVlay = new QVBoxLayout(this);
        pVlay->addWidget(m_pTitleBar);
        pVlay->addWidget(w);
    
        pVlay->setContentsMargins(0, 0, 0, 0);
        setLayout(pVlay);
    
        // 将标题栏中的关闭信号与主窗口关闭的槽函数建立连接
        connect(m_pTitleBar, &CTitleBar::sig_close, this, &MainWidget::on_closeSlot);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    1.2 标题栏实现

    • CTitleBar.h
    #pragma once
    #include 
    #include 
    #include 
    
    class CTitleBar : public QWidget {
        Q_OBJECT
    
    public:
        CTitleBar(QWidget* p = nullptr);
        ~CTitleBar();
    
    private:
        void initUI();
    
    private:
        // 鼠标拖动标题栏事件
        void mousePressEvent(QMouseEvent* event) override;
        // 鼠标双击标题栏事件
        void mouseDoubleClickEvent(QMouseEvent* event) override;
    
    private slots:
        void onClicked();
    
    signals:
        void sig_close();
    
    private:
        QLabel* m_pLogo;
        QLabel* m_pTitleTextLabel;
        
        QPushButton* m_pSetBtn;
        QPushButton* m_pMinBtn;
        QPushButton* m_pMaxBtn;
        QPushButton* m_pCloseBtn;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • CTitleBar.cpp
    #include "CTitleBar.h"
    #include 
    
    // 告诉编译器在链接时将 "user32.lib" 库文件加入到项目中
    #pragma comment(lib, "user32.lib")
    // 包含使用 Qt 框架与 Windows API 进行交互所需的定义和函数
    #include 
    
    CTitleBar::CTitleBar(QWidget* p): QWidget(p) {
        // 设置标题栏部件在关闭时自动删除的属性
        this->setAttribute(Qt::WA_DeleteOnClose);
        
        initUI();
    }
    
    CTitleBar::~CTitleBar() {}
    
    void CTitleBar::initUI() {
        setAttribute(Qt::WA_StyledBackground); // 禁止父窗口影响子窗口样式
        this->setFixedHeight(32 + 5 * 2);
        this->setStyleSheet("background-color:rgb(54,54,54)");
        
        // 创建 logo 控件
        m_pLogo = new QLabel(this);
        m_pLogo->setFixedSize(32, 32);
        m_pLogo->setStyleSheet("background-image:url(:/LessWidgetPro/resources/titlebar/title_icon.png);border:none");
        
        // 创建 标题 控件
        m_pTitleTextLabel = new QLabel(this);
        m_pTitleTextLabel->setText(u8"我是标题");
        m_pTitleTextLabel->setFixedWidth(120);
        m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \
            font-size:18px; \
            color:#BDC8E2;background-color:rgb(54,54,54);}");
        
        // 创建 设置 控件
        m_pSetBtn = new QPushButton(this);
        m_pSetBtn->setFixedSize(32, 32);
        m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/set.svg);border:none}" \
            "QPushButton:hover{" \
            "background-color:rgb(99, 99, 99);" \
            "background-image:url(:/LessWidgetPro/resources/titlebar/set_hover.svg);border:none;}");
        
        // 创建 最小化 控件
        m_pMinBtn = new QPushButton(this);
        m_pMinBtn->setFixedSize(32, 32);
        m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/min.svg);border:none}" \
            "QPushButton:hover{" \
            "background-color:rgb(99, 99, 99);" \
            "background-image:url(:/LessWidgetPro/resources/titlebar/min_hover.svg);border:none;}");
        
        // 创建 最大化 控件
        m_pMaxBtn = new QPushButton(this);
        m_pMaxBtn->setFixedSize(32, 32);
        m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/normal.svg);border:none}" \
            "QPushButton:hover{" \
            "background-color:rgb(99, 99, 99);" \
            "background-image:url(:/LessWidgetPro/resources/titlebar/normal_hover.svg);border:none;}");
        
        // 创建 关闭 控件
        m_pCloseBtn = new QPushButton(this);
        m_pCloseBtn->setFixedSize(32, 32);
        m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/close.svg);border:none}" \
            "QPushButton:hover{" \
            "background-color:rgb(99, 99, 99);" \
            "background-image:url(:/LessWidgetPro/resources/titlebar/close_hover.svg);border:none;}");
        
        // 创建水平布局将上述控件放进去
        QHBoxLayout* pHlay = new QHBoxLayout(this);
        
        pHlay->addWidget(m_pLogo);
        pHlay->addWidget(m_pTitleTextLabel);
        pHlay->addStretch();
        
        pHlay->addWidget(m_pSetBtn);
        QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
        pHlay->addSpacerItem(pItem1); // 弹簧每次使用时得 new 出来,不能重复使用
        
        pHlay->addWidget(m_pMinBtn);
        QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
        pHlay->addSpacerItem(pItem2); // 弹簧每次使用时得 new 出来,不能重复使用
        
        pHlay->addWidget(m_pMaxBtn);
        QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
        pHlay->addSpacerItem(pItem3); // 弹簧每次使用时得 new 出来,不能重复使用
        
        pHlay->addWidget(m_pCloseBtn);
        
        pHlay->setContentsMargins(5, 5, 5, 5);
        
        connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
        connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
        connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
    }
    
    // 处理鼠标按下事件的函数
    void CTitleBar::mousePressEvent(QMouseEvent* event) {
        if (ReleaseCapture()) {
            QWidget* pWindow = this->window();
            // 判断该窗口是否是顶级窗口(即主窗口)
            if (pWindow->isTopLevel()) {
                // 发送WM_SYSCOMMAND消息,参数为SC_MOVE + HTCAPTION,目的是模拟鼠标拖动窗口
                SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
            }
        }
    }
    
    // 当鼠标双击标题栏时,会触发与 m_pMaxBtn 按钮关联的点击事件
    void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event) {
        emit m_pMaxBtn->clicked();
    }
    
    // 处理标题栏上按钮的点击事件
    void CTitleBar::onClicked() {
        // 使用 sender() 函数获取发送信号的对象指针,并强转为 QPushButton* 类型
        QPushButton* pButton = qobject_cast<QPushButton*>(sender());
        QWidget* pWindow = this->window(); // 获取当前窗口的指针
        
        if (pButton == m_pMinBtn) {
            pWindow->showMinimized(); // 将窗口最小化
        } else if (pButton == m_pMaxBtn) {
            if (pWindow->isMaximized()) {
                pWindow->showNormal(); // 如果已经最大化,则先还原窗口
                m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/normal.svg);border:none}" \
                    "QPushButton:hover{" \
                    "background-color:rgb(99, 99, 99);" \
                    "background-image:url(:/LessWidgetPro/resources/titlebar/normal_hover.svg);border:none;}");
            } else {
                pWindow->showMaximized(); // 如果未最大化,则将窗口最大化
                m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/max.svg);border:none}" \
                    "QPushButton:hover{" \
                    "background-color:rgb(99, 99, 99);" \
                    "background-image:url(:/LessWidgetPro/resources/titlebar/max_hover.svg);border:none;}");
            }
        } else if (pButton == m_pCloseBtn) {
            emit sig_close(); // 通知其他部分执行关闭窗口的操作
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138

    1.3 无边框窗口公用类实现

    • CFrameLessWidgetBase.h
    #pragma once
    #include 
    
    class CFrameLessWidgetBase : public QWidget {
    public:
        CFrameLessWidgetBase(QWidget* p = nullptr);
        ~CFrameLessWidgetBase() {}
    
    protected:
        // 参数:指定事件类型的标识符,传递原始操作系统事件消息,存储和返回处理结果
        bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
    
    private:
        int m_nBorderWidth = 5;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • CFrameLessWidgetBase.cpp
    #include "CFrameLessWidgetBase.h"
    #include 
    #include 
    #include 
    
    #pragma comment(lib, "user32.lib")
    #pragma comment(lib,"dwmapi.lib")
    
    CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* p) :QWidget(p) {
        this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
        // Qt::WA_Hover 表示窗口具有悬停功能,可以在鼠标悬停时触发相应的事件
        setAttribute(Qt::WA_Hover);
    }
    
    // 根据鼠标在窗口的位置判断其是否在边框区域,并设置相应的 result 值以实现窗口的拖动或调整大小操作
    bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result) {
        MSG* param = static_cast<MSG*>(message);
        
        switch (param->message) {
            case WM_NCHITTEST: {
                // 获取鼠标在窗口上的坐标 (nX 和 nY)
                int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
                int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
        
                // 判断鼠标是否在内部区域
                if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&
                    nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth)
                {
                    if (childAt(nX, nY) != nullptr) // 如果在内部区域且有子部件
                        return QWidget::nativeEvent(eventType, message, result);
                }
        
                // 如果鼠标在边界区域,则根据具体位置设置 result 的值,以实现不同的窗口行为
                if ((nX > 0) && (nX < m_nBorderWidth))
                    // 鼠标在左边界,则 *result 被设置为 HTLEFT,表示拖动窗口左边界
                    *result = HTLEFT;
        
                if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()))
                    *result = HTRIGHT;
        
                if ((nY > 0) && (nY < m_nBorderWidth))
                    *result = HTTOP;
        
                if ((nY > this->height() - m_nBorderWidth) && (nY < this->height()))
                    *result = HTBOTTOM;
        
                if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0)
                    && (nY < m_nBorderWidth))
                    *result = HTTOPLEFT;
        
                if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
                    && (nY > 0) && (nY < m_nBorderWidth))
                    *result = HTTOPRIGHT;
        
                if ((nX > 0) && (nX < m_nBorderWidth)
                    && (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
                    *result = HTBOTTOMLEFT;
        
                if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())
                    && (nY > this->height() - m_nBorderWidth) && (nY < this->height()))
                    *result = HTBOTTOMRIGHT;
        
                return true;
            }
        }
        
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    2. Qt 实现窗口阴影

    在这里插入图片描述

    • CLoginDlg.cpp
    #include "CLoginDlg.h"
    #include "CLoginRealWidget.h"
    #include   // 添加窗口阴影
    #include 
    #include 
    
    CLoginDlg::CLoginDlg(QWidget *parent) : QDialog(parent) {
        // 设置主窗口透明
        this->setAttribute(Qt::WA_TranslucentBackground, true);
        // 设置主窗口无边框
        this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    
        QVBoxLayout* pMainLay = new QVBoxLayout(this);
        CLoginRealWidget* pRealWidget = new CLoginRealWidget(this);
        pMainLay->addWidget(pRealWidget);
        pMainLay->setContentsMargins(30, 30, 30, 30);
        setLayout(pMainLay);
    
        // 给顶层 widget 设置背景颜色,不然看不见,因为底层 widget 已经透明了
        pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");
    
        // 创建窗口阴影并设置属性,最后添加给顶层 widget
        QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);
        shadow->setOffset(0, 0);                 // 设置阴影距离
        shadow->setColor(QColor("#686868"));     // 设置阴影颜色 686868
        shadow->setBlurRadius(30);               // 设置阴影区域
        pRealWidget->setGraphicsEffect(shadow);  // 给顶层 QWidget 设置阴影
    }
    
    CLoginDlg::~CLoginDlg() {}
    
    void CLoginDlg::mousePressEvent(QMouseEvent* event) {
        this->windowPos = this->pos();       // 获得部件当前位置
        this->mousePos = event->globalPos(); // 获取鼠标当前的全局位置
        this->dPos = mousePos - windowPos;   // 鼠标按下后,部件所在的新位置相对于按下前位置的偏移量
    }
    
    void CLoginDlg::mouseMoveEvent(QMouseEvent* event) {
        // 根据鼠标当前的全局位置和之前保存的偏移量,计算部件应该移动到的新位置,并将部件移动到新位置
        this->move(event->globalPos() - this->dPos);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    3. Qt 实现圆角窗口

    3.1 方式一:绘制法

    在这里插入图片描述

    • MainWidget.h
    #pragma once
    #include 
    
    class MainWidget : public QWidget {
        Q_OBJECT
    
    public:
        MainWidget(QWidget *parent = Q_NULLPTR);
    
    private:
        void paintEvent(QPaintEvent* event) override;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • MainWidget.cpp
    #include "MainWidget.h"
    #include 
    
    MainWidget::MainWidget(QWidget *parent) : QWidget(parent) {
        resize(600, 400);
    
        // 设置窗口背景透明
        setAttribute(Qt::WA_TranslucentBackground);  
        // 设置窗口无边框
        setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    }
    
    void MainWidget::paintEvent(QPaintEvent* event) {
        QPainter painter(this);
        // 设置抗锯齿渲染方式,使绘制的图形边缘更加平滑
        painter.setRenderHint(QPainter::Antialiasing);
        // 设置画刷,使用颜色(R=168, G=68, B=68)作为填充色
        painter.setBrush(QBrush(QColor(168, 68, 68)));
        // 设置画笔,将其设置为透明,即不绘制边框
        painter.setPen(Qt::transparent);
        // 获取当前窗口部件的矩形区域
        QRect rect = this->rect();
        // 绘制一个具有圆角的矩形,圆角 15px
        painter.drawRoundedRect(rect, 15, 15);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.2 方式二:qss(推荐,更灵活)

    在这里插入图片描述

    • MainWidget.cpp
    #include "MainWidget.h"
    #include 
    #include 
    
    MainWidget::MainWidget(QWidget *parent) : QWidget(parent) {
        // 设置窗口背景透明
        setAttribute(Qt::WA_TranslucentBackground);
        // 设置窗口无边框
        setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
    
        // 设置左上、右下圆角,大小 15px
        this->setStyleSheet("QWidget{background-color:#A84444;  \
            border-top-left-radius:15px;   \
            border-bottom-right-radius:15px; \
        }");
    }
    
    void MainWidget::paintEvent(QPaintEvent*) {
        QStyleOption opt;    // 用于存储窗口部件的绘制选项
        opt.init(this);      // 初始化 opt,将其与当前窗口部件相关联,以便获取窗口部件的状态和外观选项
        QPainter p(this);    // 创建一个QPainter对象,并将其与当前窗口部件相关联,以便于进行绘图操作
        // 使用 QStyle 对象的 drawPrimitive 方法来绘制窗口部件的外观
        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4. 实现 WPS tab 页面

    在这里插入图片描述

    4.1 主窗口实现

    • WPSDemo.h
    #pragma once
    
    #include 
    #include "ui_WPSDemo.h"
    
    class WPSDemo : public QWidget {
        Q_OBJECT
    
    public:
        WPSDemo(QWidget *parent = Q_NULLPTR);
    
    protected:
        bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;
    
    private slots:
        void on_close();
    
    private:
        Ui::WPSDemoClass ui;
        int m_BorderWidth = 5; // 表示鼠标位于边框缩放范围的宽度
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • WPSDemo.cpp
    #include "WPSDemo.h"
    #include "tabbrowser.h"
    #include 
    
    #ifdef Q_OS_WIN
    #include 
    #include 
    #include 
    #endif
    
    #pragma comment(lib, "user32.lib")
    #pragma comment(lib,"dwmapi.lib")
    
    WPSDemo::WPSDemo(QWidget *parent) : QWidget(parent) {
        ui.setupUi(this);
        setWindowFlags(Qt::FramelessWindowHint);
        setStyleSheet("background-color:#E3E4E7");
        
        CTabBrowser* pTab = new CTabBrowser(this);
        
        QHBoxLayout* pHLay = new QHBoxLayout(this);
        pHLay->addWidget(pTab);
        pHLay->setContentsMargins(6, 6, 6, 6);
        setLayout(pHLay);
        
        connect(pTab, &CTabBrowser::sig_close, this, &WPSDemo::on_close);
    }
    
    void WPSDemo::on_close() {
        // 其它业务逻辑
    
        close();
    }
    
    bool WPSDemo::nativeEvent(const QByteArray& eventType, void* message, long* result) {
        Q_UNUSED(eventType)
        MSG* param = static_cast<MSG*>(message);
    
    	switch (param->message) {
            case WM_NCHITTEST: {
                int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();
                int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();
        
                // 如果鼠标位于子控件上,则不进行处理
                if (childAt(nX, nY) != nullptr)
                    return QWidget::nativeEvent(eventType, message, result);
        
                // 鼠标区域位于窗体边框,进行缩放
                if ((nX > 0) && (nX < m_BorderWidth))
                    *result = HTLEFT;
        
                if ((nX > this->width() - m_BorderWidth) && (nX < this->width()))
                    *result = HTRIGHT;
        
                if ((nY > 0) && (nY < m_BorderWidth))
                    *result = HTTOP;
        
                if ((nY > this->height() - m_BorderWidth) && (nY < this->height()))
                    *result = HTBOTTOM;
        
                if ((nX > 0) && (nX < m_BorderWidth) && (nY > 0)
                    && (nY < m_BorderWidth))
                    *result = HTTOPLEFT;
        
                if ((nX > this->width() - m_BorderWidth) && (nX < this->width())
                    && (nY > 0) && (nY < m_BorderWidth))
                    *result = HTTOPRIGHT;
        
                if ((nX > 0) && (nX < m_BorderWidth)
                    && (nY > this->height() - m_BorderWidth) && (nY < this->height()))
                    *result = HTBOTTOMLEFT;
        
                if ((nX > this->width() - m_BorderWidth) && (nX < this->width())
                    && (nY > this->height() - m_BorderWidth) && (nY < this->height()))
                    *result = HTBOTTOMRIGHT;
        
                return true;
            }
        }
    
        return QWidget::nativeEvent(eventType, message, result);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    4.2 标签栏实现

    • CTabTitleWidget.h
    #pragma once
    #include 
    #include 
    
    class CTabTitleWidget : public QWidget {
        Q_OBJECT
    
    public:
        CTabTitleWidget(QWidget* parent = nullptr);
        ~CTabTitleWidget();
        
        void setEmptyWidgetWidth(int w);
        
    protected:
        void paintEvent(QPaintEvent* event) override;
        void mousePressEvent(QMouseEvent* event) override;
        void mouseDoubleClickEvent(QMouseEvent* event);
    
    signals:
        void sig_close();
        void sig_addtab();
    
    private slots:
        void on_Clicked();
    
    private:
        QPushButton* m_pAddBtn = nullptr;
        QWidget*     m_pEmptyWidget = nullptr;
        QPushButton* m_pUserBtn = nullptr;
        QPushButton* m_pMinBtn = nullptr;
        QPushButton* m_pMaxBtn = nullptr;
        QPushButton* m_pCloseBtn = nullptr;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • CTabTitleWidget.cpp
    #include "CTabTitleWidget.h"
    #include 
    #include 
    #include 
    #include 
    
    #ifdef Q_OS_WIN
    #include 
    #pragma comment(lib, "user32.lib")
    #endif
    
    CTabTitleWidget::CTabTitleWidget(QWidget* parent) {
        setStyleSheet("background-color:#E3E4E7");
        m_pAddBtn = new QPushButton(this);
        m_pAddBtn->setFlat(true);
        m_pAddBtn->setFixedSize(32, 32);
        m_pAddBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/add.svg)");
    
        m_pEmptyWidget = new QWidget(this);
    
        m_pUserBtn = new QPushButton(this);
        m_pUserBtn->setFlat(true);
        m_pUserBtn->setFixedSize(32, 32);
        m_pUserBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/user)");
    
        m_pMinBtn = new QPushButton(this);
        m_pMinBtn->setFlat(true);
        m_pMinBtn->setFixedSize(32, 32);
        m_pMinBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/min.svg)");
    
        m_pMaxBtn = new QPushButton(this);
        m_pMaxBtn->setFlat(true);
        m_pMaxBtn->setFixedSize(32, 32);
        m_pMaxBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/max.svg)");
    
        m_pCloseBtn = new QPushButton(this);
        m_pCloseBtn->setFlat(true);
        m_pCloseBtn->setFixedSize(32, 32);
        m_pCloseBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/close.svg)");
    
        QHBoxLayout* pHLay = new QHBoxLayout(this);
        pHLay->addWidget(m_pAddBtn);
        pHLay->addWidget(m_pEmptyWidget);
        this->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);
        pHLay->addWidget(m_pUserBtn);
        pHLay->addSpacing(8);
        pHLay->addWidget(m_pMinBtn);
        pHLay->addWidget(m_pMaxBtn);
        pHLay->addWidget(m_pCloseBtn);
        pHLay->setContentsMargins(1, 0, 1, 3);
        setLayout(pHLay);
    
        connect(m_pAddBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);
        connect(m_pMinBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);
        connect(m_pMaxBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);
        connect(m_pCloseBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);
    }
    
    CTabTitleWidget::~CTabTitleWidget() {}
    
    void CTabTitleWidget::setEmptyWidgetWidth(int w) {
        m_pEmptyWidget->setMinimumWidth(w);
    }
    
    void CTabTitleWidget::paintEvent(QPaintEvent* event) {
        QStyleOption opt;
        opt.init(this);
        QPainter p(this);
        style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
        QWidget::paintEvent(event);
    }
    
    void CTabTitleWidget::mousePressEvent(QMouseEvent* event) {
        if (ReleaseCapture()) {
            QWidget* pWindow = this->window();
            if (pWindow->isTopLevel()) {
                SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
            }
        }
        event->ignore();
    }
    
    void CTabTitleWidget::mouseDoubleClickEvent(QMouseEvent* event) {
        emit m_pMaxBtn->clicked();
    }
    
    void CTabTitleWidget::on_Clicked() {
        QPushButton* pButton = qobject_cast<QPushButton*>(sender());
    
        QWidget* pWindow = this->window();
    
        if (pWindow->isTopLevel()) {
            if (pButton == m_pAddBtn) {
                emit sig_addtab();
            } else if (pButton == m_pMinBtn) {
                pWindow->showMinimized();
            } else if (pButton == m_pMaxBtn) {
                pWindow->isMaximized() ? pWindow->showNormal() : pWindow->showMaximized();
            } else if (pButton == m_pCloseBtn) {
                emit sig_close();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    4.3 标签栏右键导航菜单实现

    • tabbrowser.h
    #pragma once
      
    #include    
    #include  
    #include "CTabTitleWidget.h"
      
    class CTabBrowser : public QTabWidget {  
        Q_OBJECT  
    
    public:  
        explicit CTabBrowser(QWidget *parent = 0);  
    
        // tab 操作标志
        enum TAB_FLAG {
            NEW,
            CLOSE,
            NORMAL,
            SPECIAL
        };
            
    protected:  
        void resizeEvent(QResizeEvent *e) override;
    
    private:
        void initTabWidget();
        void setTabBarFlag(TAB_FLAG flag);
        void createTabMenu();
      
    private slots:  
        void on_newTab();
        void on_closeTab(int index);
        void onMenuShow(const QPoint& pos);
        void on_closeAllTab();
    
    signals:
        void sig_close();
    
    private:
        CTabTitleWidget* m_pRightWidget = nullptr;
        QMenu* m_pTabMenu = nullptr;
    }; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • tabbrowser.cpp
    #include "tabbrowser.h"  
    #include   
    #include 
    #include 
    #include 
    #include 
    
    QString qss0 = "QTabBar::tab{ \
                font: 75 12pt Arial; \
                text-align:left; \
                width:184px; \
                height:32; \
                background:#FFFFFF; \
                border:2px solid #FFFFFF; \
                border-bottom-color:#FFFFFF; \
                border-top-left-radius:4px; \
                border-top-right-radius:4px; \
                padding:2px; \
                margin-top:0px; \
                margin-right:1px; \
                margin-left:1px;  \
                margin-bottom:0px;} \
            QTabBar::tab:selected{  \
                color:#333333; /*文字颜色*/  \
                background-color:#FFFFFF;} \
            QTabBar::tab:!selected{ \
                color:#B2B2B2; \
                border-color:#FFFFFF;} \
            QTabBar::scroller{width: 0px;}";
    
    QString qss1 = "QTabBar::tab{ \
                font: 75 12pt Arial; \
                text-align:left; \
                width:184px; \
                height:32; \
                background:#FFFFFF; \
                border:2px solid #FFFFFF; \
                border-bottom-color:#FFFFFF; \
                border-top-left-radius:4px; \
                border-top-right-radius:4px; \
                padding:2px; \
                margin-top:0px; \
                margin-right:1px; \
                margin-left:1px;  \
                margin-bottom:0px;} \
            QTabBar::tab:selected{  \
                color:#333333; /*文字颜色*/  \
                background-color:#FFFFFF;} \
            QTabBar::tab:!selected{ \
                color:#B2B2B2; \
                border-color:#FFFFFF;} \
            QTabBar::scroller{width: 36px;}";
      
    CTabBrowser::CTabBrowser(QWidget *parent) : QTabWidget(parent) {  
        this->addTab(new QWidget,u8"稻壳");  
    
        this->setUsesScrollButtons(true);  // 滚动鼠标可切换 tab
        this->setTabsClosable(true);       // 显示 tab 右侧的关闭按钮
        this->setMovable(true);            // tab 可移动位置
    
        initTabWidget();
        setTabBarFlag(NORMAL);
    
        this->setStyleSheet(qss0);
    
        connect(this, &QTabWidget::tabCloseRequested,this, &CTabBrowser::on_closeTab);
    }  
      
    void CTabBrowser::resizeEvent(QResizeEvent *e) {  
        setTabBarFlag(NORMAL);
        QTabWidget::resizeEvent(e);  
    }
    
    void CTabBrowser::createTabMenu() {
        m_pTabMenu = new QMenu(this);
    
        QAction* pAcSave = new QAction(QIcon(":/WPSDemo/resources/save.png"), u8"保存", m_pTabMenu);
        m_pTabMenu->addAction(pAcSave);
    
        connect(pAcSave, &QAction::triggered, [=] {
            QMessageBox::information(this, u8"提示", u8"你点击了 保存");
            });
    
        QAction* pAcSaveAs = new QAction(QString(u8"另存为"), m_pTabMenu);
        m_pTabMenu->addAction(pAcSaveAs);
    
        m_pTabMenu->addSeparator();
    
        QAction* pAcShareDoc = new QAction(QIcon(":/WPSDemo/resources/share.png"), QString(u8"分享文档"), m_pTabMenu);
        m_pTabMenu->addAction(pAcShareDoc);
    
        QAction* pAcSendToDevice = new QAction(QString(u8"发送到设备"), m_pTabMenu);
        m_pTabMenu->addAction(pAcSendToDevice);
    
        m_pTabMenu->addSeparator();
    
        QAction* pAcNewName = new QAction(QString(u8"重命名"), m_pTabMenu);
        m_pTabMenu->addAction(pAcNewName);
    
        QAction* pAcSaveToWPSCloud = new QAction(QString(u8"保存到WPS云文档"), m_pTabMenu);
        m_pTabMenu->addAction(pAcSaveToWPSCloud);
    
        QAction* pAcCloseAll = new QAction(QString(u8"关闭所有文件"), m_pTabMenu);
        m_pTabMenu->addAction(pAcCloseAll);
        connect(pAcCloseAll, &QAction::triggered, this, &CTabBrowser::on_closeAllTab);
    }
      
    void CTabBrowser::setTabBarFlag(TAB_FLAG flag) {  
        int w = this->width();
    
        int tabsWidth = 0;  // 所有 tab 的总宽度
        int tabsHeight = tabBar()->height();  
        int tabs = this->count();  
    
        if (flag == NEW || flag == NORMAL) {
            for (int i = 0; i < tabs; ++i) {
                tabsWidth += tabBar()->tabRect(i).width();  
            }  
        } else {
            for (int i = 0; i < tabs - 1; ++i) {
                tabsWidth += tabBar()->tabRect(i).width();  
            }  
        } if (w > tabsWidth) {
            m_pRightWidget->setEmptyWidgetWidth(w - tabsWidth - 32 * 5 - 15);
            this->setStyleSheet(qss0);
        } else {
            // 当所有 tab 的宽度大于整个 tabWidget 的宽时
            m_pRightWidget->setEmptyWidgetWidth(150);
            this->setStyleSheet(qss1);
        }  
    }  
      
    void CTabBrowser::initTabWidget() {  
        // 修改菜单策略
        this->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(this, &QTabWidget::customContextMenuRequested, this, &CTabBrowser::onMenuShow);
        createTabMenu();
    
        m_pRightWidget = new CTabTitleWidget(this);
    
        this->setCornerWidget(m_pRightWidget, Qt::TopRightCorner);
        connect(m_pRightWidget, &CTabTitleWidget::sig_addtab, this, &CTabBrowser::on_newTab);
        connect(m_pRightWidget, &CTabTitleWidget::sig_close, this, &CTabBrowser::sig_close);
    }  
     
    // 新建tab
    void CTabBrowser::on_newTab() {  
    	int nCount = count();
    	QString  title = QString::number(nCount);
        title = "Page" + title;
    
        this->addTab(new QWidget, title);
    
        if (!tabsClosable()) {
            setTabsClosable(true);  
        }  
    
        setTabBarFlag(NEW);
    }  
    
    void CTabBrowser::on_closeTab(int index) {  
        widget(index)->deleteLater();  
        setTabBarFlag(CLOSE);
    
        // 当只剩下 1 个 tab时
        if (count() == 1) {
            setTabsClosable(false);  
            setTabBarFlag(SPECIAL);
        }  
    }  
     
    void CTabBrowser::onMenuShow(const QPoint& pos) {
        int index = this->tabBar()->tabAt(pos);
    
    #ifdef _DEBUG
        qDebug() << u8"当前tab为:" << QString::number(index);
        this->setCurrentIndex(index);
    #endif
    
        if (index != -1) {
            m_pTabMenu->exec(QCursor::pos());
        }
    }
    
    void CTabBrowser::on_closeAllTab() {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
  • 相关阅读:
    基于ssm流浪动物救助管理系统
    二刷力扣--数组
    别乱用 FULL_CASE 和 PARALLEL_CASE
    PAT 1164 Good in C 测试点3,4
    Pyecharts | 历年全国各地民政局登记数据分析+可视化
    FTP协议 21
    Matlab:正则表达式
    LINUX安装KDC服务
    security异常处理机制
    Linux搭建DNS服务
  • 原文地址:https://blog.csdn.net/qq_42994487/article/details/134239128