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() {}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119617.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Softing新版HART多路复用器现支持图尔克excom和西门子ET 200iSP等远程I/O

Softing工业自动化最近升级了用于访问配置和诊断数据的smartLink SW-HT软件&#xff0c;现在该软件可支持访问图尔克excom和西门子ET 200iSP等远程I/O。 &#xff08;smartLink SW-HT支持访问配置和诊断数据&#xff09; 越来越多的新型远程I/O选择使用以太网来替代PROFIBUS连接…

ts学习01-开发环境搭建

环境 nodejs 18 npm 安装typescript npm install typescript # 如果上面太慢&#xff0c;可以执行下面的方法 npm install typescript --registryhttps://registry.npm.taobao.orgHelloWorld 新建index.ts console.log("hello ts");执行下面命令进行编译 npx t…

Qwt QwtWheel绘制滚动轮

1.简介 QwtWheel 是一个用于实现滚动轮控件的类库。它基于 Qt 框架&#xff0c;并提供了一些方便的功能来处理滚动轮的事件和绘图。 QwtWheel 类继承自 QWidget类&#xff0c;用于定义滚动轮控件的通用行为。QwtWheel 添加了特定于滚动轮的功能。 QwtWheel 可以用于创建具有滚…

Elasticsearch:在 ES|QL 中使用 DISSECT 和 GROK 进行数据处理

目录 DISSECT 还是 GROK&#xff1f; 或者两者兼而有之&#xff1f; 使用 DISSECT 处理数据 Dissect pattern 术语 例子 DISSECT 关键修饰符 右填充修饰符 (->) 附加修饰符 () 添加顺序修饰符&#xff08; 和 /n&#xff09; 命名的跳过键&#xff08;&#xff1f…

仿真实现lio_sam建图和ndt_matching定位

文章目录 一、仿真环境二、lio_sam建图1.修改配置文件2.开始建图 三、ndt_matching定位1.新建启动文件2.启动 总结 一、仿真环境 使用现有开源的仿真环境—从零开始搭建一台ROS开源迷你无人车&#xff0c;作者已经配置好小车模型以及gazebo环境&#xff0c;imu频率已改为200HZ…

Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库

背景介绍 Apache Doris是一个基于MPP架构的易于使用&#xff0c;高性能和实时的分析数据库&#xff0c;以其极高的速度和易用性而闻名。海量数据下返回查询结果仅需亚秒级响应时间&#xff0c;不仅可以支持高并发点查询场景&#xff0c;还可以支持高通量复杂分析场景。 这些都…

Linux网络编程04

更高效的零拷贝 发送方过程零拷贝 sendfile 发送文件方的零拷贝&#xff0c;虽然之前我们就可以使用mmap来实现零拷贝但是存在一个方法sendfile也可以直接实现数据从内核区发送到网络发送区socket 直接把内核里面你的文件数据不经过用户态&#xff0c;直接发送给另外一个文件…

【Java 进阶篇】JSP EL 详解

在 Java Web 开发中&#xff0c;JavaServer Pages&#xff08;JSP&#xff09;是一种强大的技术&#xff0c;用于创建动态 Web 应用程序。JSP 的一个关键方面是 Expression Language&#xff08;EL&#xff09;表达语言&#xff0c;它允许您在 JSP 页面中嵌入 Java 代码&#x…

React动态生成二维码和毫米(mm)单位转像素(px)单位

一、使用qrcode.react生成二维码&#xff0c;qrcode.react - npm 很简单&#xff0c;安装依赖包&#xff0c;然后引用就行了 npm install qrcode.react或者 yarn add qrcode.react直接上写好的代码 import React, {useEffect, useState} from react; import QRCode from qr…

缓存-Spring Cache 缓存抽象

缓存-Spring Cache 缓存抽象 Spring从版本3.1开始提供非侵入的将Cache集成到Spring应用的方式。Spring Cache提供Cache的统一抽象&#xff0c;支持集成各种不同的缓存解决方案。从4.1版本开始&#xff0c;提供了注解和更多的定制参数。 Spring Cache 抽象提供了对Java方法的缓存…

结合双向LSTM和注意力机制的DQN-CE算法船舶能量调度

Title:Ship Energy Scheduling with DQN-CE Algorithm Combining Bi-directional LSTM and Attention Mechanism 【Applied Energy】结合双向LSTM和注意力机制的DQN-CE算法船舶能量调度(中科院1区Top,IF 11.2) 具体实现方法可以参考原文:论文地址 欢迎大家引用和交流,具体…

C++入门(2)

目录 1. 内联函数1.1概念1.2特性 2. auto关键字2.1 为什么要有auto2.2 auto 简介2.3 auto的使用细则 3.基于范围的for循环(C11)3.1 范围for的语法3.2 范围for的使用条件 4.指针空值nullptr(C11)4.1 C98中的指针空值4.2 用nullptr表示指针空值 1. 内联函数 1.1概念 用inline修饰…

【单链表】无头单项不循环(1)

目录 单链表 主函数test.c test1 test2 test3 test4 头文件&函数声明SList.h 函数实现SList.c 打印SLPrint 创建节点CreateNode 尾插SLPushBack 头插SLPushFront 头删SLPopBck 尾删SLPopFront 易错点 本篇开始链表学习。今天主要是单链表&OJ题目。 单链…

23个优秀开源免费BI仪表盘

BI也称为商业智能&#xff0c;是收集、分析和展示数据以支持决策者做出明智的业务决策的过程。BI帮助组织将其原始的生产数据转化为有意义的见解或者知识&#xff0c;以推动其业务战略。BI能够为组织改善决策、提高效率和提升资源利用率。 BI仪表盘是BI系统的重要组成部分&…

Websocket @ServerEndpoint不能注入@Autowired

在websocket中使用ServerEndpoint无法注入Autowired、Value 问题分析 Spring管理采用单例模式&#xff08;singleton&#xff09;&#xff0c;而 WebSocket 是多对象的&#xff0c;即每个客户端对应后台的一个 WebSocket 对象&#xff0c;也可以理解成 new 了一个 WebSocket&…

安全操作(安卓推流)程序

★ 安全操作项目 项目描述&#xff1a;安全操作项目旨在提高医疗设备的安全性&#xff0c;特别是在医生离开操作屏幕时&#xff0c;以减少非授权人员的误操作风险。为实现这一目标&#xff0c;我们采用多层次的保护措施&#xff0c;包括人脸识别、姿势检测以及二维码识别等技术…

Web逆向-某网络学院学习的”偷懒“思路分析

接到求助&#xff0c;帮朋友完成20课时的网络学习。 我想都没想就接下了&#xff0c;寻思找个接口直接把学习时间提交上去&#xff0c;易如反掌。 最不济最不济&#xff0c;咱还能16x播放&#xff0c;也简单的很 然鹅&#xff0c;当我登陆的时候&#xff0c;发现自己还是太天真…

边缘计算助力低速无人驾驶驶入多场景落地快车道

自动驾驶刮起的风&#xff0c;如今正吹向低速无人驾驶赛道。近期不完全统计显示&#xff0c;当前A股及港股正在排队IPO的自动驾驶相关企业共有12家&#xff0c;其中实现盈利的企业仅两家&#xff0c;而且实现盈利的两家企业最主要的收入并不完全源于自动驾驶领域。 相比之下&am…

mysql数据库的备份和恢复

目录 一、备份和恢复 1、备份&#xff1a; 2、备份的方法&#xff1a; 2.1物理备份&#xff1a; 2.2、逻辑备份 2.3增量备份&#xff1a; 一、备份和恢复 1、备份&#xff1a; 先备份再恢复 备份&#xff1a;完全备份&#xff0c;增量备份 完全备份&#xff1a;将整个…

JAVA中类和对象的认识

1、面向对象的初步认知 1.1 什么是面向对象 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。面 向对象是解决问题的一种思想&#xff0c;主要依靠对象之间的交互完成一件事情。用面向对象的…