【QT入门】 无边框窗口设计之综合运用,实现WPS的tab页面

往期回顾:

【QT入门】 无边框窗口设计之实现窗口阴影-CSDN博客

【QT入门】 无边框窗口设计之实现圆角窗口-CSDN博客

【QT入门】 无边框窗口设计综合运用之自定义标题栏带圆角阴影的窗口-CSDN博客

【QT入门】 无边框窗口设计之综合运用,实现WPS的tab页面

一、最终效果

实现自增tab页面,不同tab页面之间的切换等,同时右键单击tab页面会弹出菜单栏

二、主界面类设计

主界面类,负责整合标题栏和下面的widget,整合很简单,创建一个tab类添加进来就ok

WPSDemo::WPSDemo(QWidget* parent)
 : QWidget(parent)
{
 ui.setupUi(this);
 setWindowFlags(Qt::FramelessWindowHint);//设置为无边框窗口
 setStyleSheet("background-color:#E3E4E7");//设置背景颜色

 tabbrowser* pTab = new tabbrowser(this); 

 QHBoxLayout* pHLay = new QHBoxLayout(this);
 pHLay->addWidget(pTab);
 pHLay->setContentsMargins(6, 6, 6, 6);
 setLayout(pHLay);

 connect(pTab, &tabbrowser::sig_close, this, &WPSDemo::on_close);
}

三、Tab类设计

tab类负责创建tab,实现相关逻辑,重点还是在这里。主要是创建、删除tab标签,以及样式切换。还有就是单击右键显示菜单这几个点需要注意。

1、定义tab标签样式

首先,定义了两个字符串变量 qss0 和 qss1,分别存储了两种不同的 QSS 样式表,用于设置标签页的外观。当tab标签的数量超过一定范围时,会出现一个切换按钮,也就需要切换不同样式。

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

2、初始化标签页并设置属性

在 tabbrowser 类的构造函数中,初始化一个标签页,并设置了一些标签页的属性,如滚动、可关闭、可移动等。然后调用了initTabWidget()函数初始化标签页部件,并设置了初始的标签页操作标志为 NORMAL。

tabbrowser::tabbrowser(QWidget* parent) :
    QTabWidget(parent)
{
    this->addTab(new QWidget, u8"稻壳");  //定义最开始的tab

    this->setUsesScrollButtons(true);  //滚动鼠标可切换tab
    this->setTabsClosable(true);       //显示tab右侧的关闭按钮
    this->setMovable(true);            //tab可移动位置

    initTabWidget(); //初始化tab
    setTabBarFlag(NORMAL); 

    this->setStyleSheet(qss0);

    connect(this, &QTabWidget::tabCloseRequested, this, &tabbrowser::on_closeTab);
}

这里我们一共定义了4种标签页操作标志:

        //tab操作标志
        enum TAB_FLAG
    {
        NEW, //新建标签页的操作标志
        CLOSE, //关闭标签页的操作标志
        NORMAL, //普通标签页的操作标志
        SPECIAL //特殊标签页的操作标志,比如当只剩下一个标签页时
    };

然后需要根据传入的标志值计算标签页的总宽度,并根据总宽度是否超过当前窗口宽度来切换不同的 QSS 样式表。

3、创建和删除tab标签

1、创建tab标签

用一个 on_newTab() 函数来新建标签页,获取当前标签页数量并转换为字符串作为标题,然后添加新的标签页,并根据需要设置标签页为可关闭状态。

void tabbrowser::on_newTab()
{
    int nCount = count(); //获取当前标签页的数量。
    QString  title = QString::number(nCount); //将当前标签页数量转换为字符串。
    title = "Page" + title; //将标题设置为 "Page" 后接当前标签页数量的字符串

    // 这里写的有问题,应该是 insertTab
    this->addTab(new QWidget, title); 

    //检查标签页是否可关闭,如果不可关闭,则设置标签页为可关闭状态。
    if (!tabsClosable())
    {
        setTabsClosable(true);
    }

    setTabBarFlag(NEW);
}
2、删除tab标签

用一个on_closeTab() 函数来关闭指定索引的标签页,删除对应的窗口部件,并根据剩余标签页数量设置标签栏的标志,当只剩下一个标签页时,将标签页设置为不可关闭状态。

//关闭指定索引的标签页,并根据情况设置标签栏的标志
void tabbrowser::on_closeTab(int index)
{
    //删除指定索引的标签页的对应窗口部件
    widget(index)->deleteLater();
    setTabBarFlag(CLOSE); //设置标签栏的标志为 CLOSE

    //当只剩下1个tab时
    if (count() == 1)
    {
        setTabsClosable(false); //设置标签页为不可关闭状态
        setTabBarFlag(SPECIAL); //设置标签栏的标志为 SPECIAL
    }
}

参考一下删除一个tab是怎么做的,是根据鼠标点击获取的index来delete,基于删除一个tab标签的操作,那么删除整个标签栏的操作自然也就很简单。我直接for循环delete所有,只剩下初始的那一个即可。

void tabbrowser::on_closeAllTab()
{
    for (int i = count(); i > 0; i--)
    {
        //删除指定索引的标签页的对应窗口部件
        widget(i)->deleteLater();
        setTabBarFlag(CLOSE); //设置标签栏的标志为 CLOSE
            //当只剩下1个tab时
        if (count() == 1)
        {
            setTabsClosable(false); //设置标签页为不可关闭状态
            setTabBarFlag(SPECIAL); //设置标签栏的标志为 SPECIAL
        }
    }
}

4、单击右键显示菜单

由于都是只创建菜单,大部分的具体实现都没做,所以还是很简单的,

//右键单击tab出现菜单栏
void tabbrowser::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之间的横线

    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, &tabbrowser::on_closeAllTab);
}

四、标题栏和tab标签的结合

负责标题栏右边的按钮实现,最后和tab结合,这个很简单的,看看代码,已经是很熟悉了,在这个类里不仅进行ui设计,还实现标题栏拖拽功能,绘图功能等。

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

都看到这里了,点个赞再走呗朋友~

加油吧,预祝大家变得更强!

相关推荐
用余生去守护24 分钟前
python报错系列(16)--pyinstaller ????????
开发语言·python
数据小爬虫@28 分钟前
利用Python爬虫快速获取商品历史价格信息
开发语言·爬虫·python
向宇it30 分钟前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
莫名其妙小饼干1 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
十年一梦实验室1 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
最爱番茄味1 小时前
Python实例之函数基础打卡篇
开发语言·python
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
engchina2 小时前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it2 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成2 小时前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++