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

1. 无边框窗口

在这里插入图片描述

1.1 主窗口实现

  • MainWidget.h
#pragma once

#include <QtWidgets/QWidget>
#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;
};
  • MainWidget.cpp
#include "MainWidget.h"
#include <QVBoxLayout>
#include <QMessageBox>

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 标题栏实现

  • CTitleBar.h
#pragma once
#include <QWidget>
#include <QLabel>
#include <QPushButton>

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;
};
  • CTitleBar.cpp
#include "CTitleBar.h"
#include <QHBoxLayout>

// 告诉编译器在链接时将 "user32.lib" 库文件加入到项目中
#pragma comment(lib, "user32.lib")
// 包含使用 Qt 框架与 Windows API 进行交互所需的定义和函数
#include <qt_windows.h>

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.3 无边框窗口公用类实现

  • CFrameLessWidgetBase.h
#pragma once
#include <QWidget>

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;
};
  • CFrameLessWidgetBase.cpp
#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>

#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;
}

2. Qt 实现窗口阴影

在这里插入图片描述

  • CLoginDlg.cpp
#include "CLoginDlg.h"
#include "CLoginRealWidget.h"
#include <QGraphicsDropShadowEffect>  // 添加窗口阴影
#include <QVboxLayout>
#include <QMouseEvent>

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);
}

3. Qt 实现圆角窗口

3.1 方式一:绘制法

在这里插入图片描述

  • MainWidget.h
#pragma once
#include <QtWidgets/QWidget>

class MainWidget : public QWidget {
    Q_OBJECT

public:
    MainWidget(QWidget *parent = Q_NULLPTR);

private:
    void paintEvent(QPaintEvent* event) override;
};
  • MainWidget.cpp
#include "MainWidget.h"
#include <QPainter>

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);
}

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

在这里插入图片描述

  • MainWidget.cpp
#include "MainWidget.h"
#include <QStyleOption>
#include <QPainter>

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);
}

4. 实现 WPS tab 页面

在这里插入图片描述

4.1 主窗口实现

  • WPSDemo.h
#pragma once

#include <QtWidgets/QWidget>
#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; // 表示鼠标位于边框缩放范围的宽度
};
  • WPSDemo.cpp
#include "WPSDemo.h"
#include "tabbrowser.h"
#include <QHBoxLayout>

#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <Windows.h>
#include <windowsx.h>
#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);
}

4.2 标签栏实现

  • CTabTitleWidget.h
#pragma once
#include <QWidget>
#include <QPushButton>

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;
};
  • CTabTitleWidget.cpp
#include "CTabTitleWidget.h"
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStyleOption>
#include <QPainter>

#ifdef Q_OS_WIN
#include <qt_windows.h>
#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();
        }
    }
}

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

  • tabbrowser.h
#pragma once
  
#include <QTabWidget>   
#include <QMenu> 
#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;
}; 
  • tabbrowser.cpp
#include "tabbrowser.h"  
#include <QDebug>  
#include <QPushButton>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QTabBar>

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() {}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值