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

news2024/11/26 3:24:20

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全志R128应用开发案例——SPI驱动ST7789V1.3寸LCD

SPI驱动ST7789V1.3寸LCD R128 平台提供了 SPI DBI 的 SPI TFT 接口&#xff0c;具有如下特点&#xff1a; Supports DBI Type C 3 Line/4 Line Interface ModeSupports 2 Data Lane Interface ModeSupports data source from CPU or DMASupports RGB111/444/565/666/888 vide…

【LeetCode:318. 最大单词长度乘积 | 模拟 位运算】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

CRM软件如何高效培育销售线索?

​ 通过线索培育可以挖掘出更多CRM软件销售管道中的有价值客户提高销售业绩。但机遇与挑战总是共存的&#xff0c;培育线索要从不同的渠道执行大量重复性的操作&#xff0c;人为操控不仅速度慢而且容易出错&#xff0c;那么企业如何高效培育销售线索? 发送个性化邮件 我们知…

YOLO目标检测——汽车头部尾部检测数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;用于训练自动驾驶系统中的车辆感知模块&#xff0c;以实现对周围车辆头部和尾部的准确检测和识别数据集说明&#xff1a;汽车头部尾部检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软…

随机森林在生物信息中的应用

今天与大家分享一项强大的机器学习算法随机森林。这个算法不仅在数据科学领域广泛应用&#xff0c;还在生物信息学中发挥了巨大的作用。 让我们一起探索随机森林的原理、优缺点以及它在生物信息领域的实际应用场景&#xff0c;本文将给出R语言进行应用的实际方法&#xff0c;利…

数据采集卡如何选型?

数据采集卡如何选型? 一、 确认采集任务二、 选择合适的传感器三、采样频率、分辨率、总线类型、量程等关键参数选择 一、 确认采集任务 二、 选择合适的传感器 三、采样频率、分辨率、总线类型、量程等关键参数选择 第1步&#xff1a;确认采集任务&#xff0c;电压&#x…

产业园区中工业厂房的能源综合配置——工业园区综合能源数字化系统建设方案

以下内容转自微信公众号&#xff1a;PPP产业大讲堂&#xff0c;《产业园区中工业厂房的能源综合配置》。 园区工业地产中能源综合配置存在的问题 我国园区工业地产建设已历经近40年的发展, 园区在区域经济发展、产业集聚方面发挥了重要的载体和平台作用, 有力推动了我国社会经…

未来商业趋势:无人奶柜的无限潜力

未来商业趋势&#xff1a;无人奶柜的无限潜力 随着自动售货机的普及和公共场所需求的多样化&#xff0c;无人奶柜作为一种新兴的自动售货机&#xff0c;开始出现在学校、医院、办公楼、商场等公共场所&#xff0c;为人们提供便捷、低成本的饮品购买服务。 这种无人奶柜不仅可以…

windows 11渗透测试工具箱

系统简介 本环境旨在提供一个开箱即用的windows渗透测试环境&#xff1b;建议运行环境&#xff1a;【vmware&#xff1a;17.0 】 /【运行内存&#xff1a;8G】 /【固态硬盘&#xff1a;100G】 Windows11 Penetration Suite Toolkit v2.2 (WSL) 【推荐】 下载链接&#xff1a;h…

Leetcode-448 找到数组中消失的数字

原理&#xff1a;每个num[i]对应一个数组下标&#xff0c;对所有num[i]下标对应的数变负以后&#xff0c;没有变负的数没有下表对应&#xff0c;这个下标对应的数就缺失&#xff08;好难想&#xff09;。把数组下标当成一个有序数列用&#xff0c;数组里面的元素正负性对数列标…

AI:61-基于深度学习的草莓病害识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

ClickHouse开发系列

一、 ClickHouse详解、安装教程_clickhouse源码安装 二、ClickHouse 语法详解_clickhouse讲解 三、ClickHouse SQL 操作语句详解 四、ClickHouse 高级教程—官方原版 五、ClickHouse主键索引最佳实践 六、MySQL与ClickHouse集成 七、ClickHouse 集成MongoDB、Re…

如何通过一条数字人三维动画宣传片,打造出数字文旅

越来越多虚拟人&#xff0c;以文化挖掘者的身份通过数字人三维动画宣传片&#xff0c;打通次元壁&#xff0c;助力文化传播形式创造性转化、创新性表达&#xff0c;赋予文化发展新动能。 如南方都市报民间博物馆文化探寻者“岭梅香”&#xff0c;由一艘在南宋时期失事的沉船“南…

基于原子轨道搜索算法的无人机航迹规划-附代码

基于原子轨道搜索算法的无人机航迹规划 文章目录 基于原子轨道搜索算法的无人机航迹规划1.原子轨道搜索搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用原子轨道搜索算法来优化无…

干货满满,mac屏幕录制实用教程!

在当今科技飞速发展的时代&#xff0c;屏幕录制已经成为了人们日常生活中经常使用的功能&#xff0c;无论是工作还是生活&#xff0c;我们都需要使用到屏幕录制软件来捕捉屏幕上的内容。mac作为苹果公司开发的操作系统&#xff0c;拥有许多内置的屏幕录制工具。本文将详细介绍两…

录屏有声音吗?解答你的疑惑

录屏是我们在工作和生活中经常遇到的需求&#xff0c;有时候我们需要记录下屏幕的操作或展示给别人看。然而&#xff0c;很多人在录屏的时候都会遇到一个问题&#xff1a;录制的视频没有声音。那么&#xff0c;录屏有声音吗&#xff1f;答案是肯定的。在本文中&#xff0c;我们…

关于还看视频对讲没有效果而且不会报错 的解决方法

1. 海康初步引入 这里我用的是海康h5player 介入海康视频参考 https://www.maxssl.com/article/22480/ 这里如果需要需要对讲功能 这里不仅需要引入h5player.js 还需要将talk和talkW文件夹一并引入 2.项目需求 在我的项目中需求是 在地图中展示点位,点击点位开启视频 并有对…

Linux操作系统中软件安装来了

在系统中&#xff0c;应用软件是必不可少的&#xff0c;比如一些社交软件&#xff0c;影音娱乐软件&#xff0c;那么我们Windows中安装软件方法主要有两种&#xff1a;第一种是从网站上下载安装包&#xff0c;双击打开安装包后按照提示一步步操作完成安装&#xff1b;第二种是从…

【SpringBoot篇】SpringBoot整合Mybatis实战

&#x1f38a;专栏【SpringBoot】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;Spring Boot和MyBatis的好处&#x1f33a;创建工…

芯片制造中如何测薄膜厚度?

在半导体制造业中&#xff0c;薄膜的厚度对器件的性能和质量有重要影响。薄膜的厚度决定了许多重要的物理和化学性质&#xff0c;对其折射、反射和透射的光学性质有直接影响&#xff0c;可以导致显著的量子尺寸效应&#xff0c;从而改变材料的电子、光学和磁性等。准确测量和控…