Qt入门小项目 | WPS tab页面(无边框窗口综合应用)

文章目录

  • [一、手写代码实现WPS tab页面](#一、手写代码实现WPS tab页面)

一、手写代码实现WPS tab页面

实现类似WPS tab效果,具体包含:

  • 自定义标题栏:最大、最小、关闭
  • 在QTabWidget的tab上增加控件
  • 在QTabWidget的tab上右键菜单
  • 可拖拽移动
  • 可拉伸窗口
  • 双击标题栏在最大与正常间切换

补充:如何给QTabWidget的左右tab栏增加控件

cpp 复制代码
void QTabWidget::setCornerWidget(QWidget *widget, Qt::Corner corner = Qt::TopRightCorner)

setCornerWidget 函数用于在标签控件(QTabWidget)的指定角落显示给定的控件(widget)。

参数:

  • QWidget *widget:指向要在角落显示的控件的指针。

  • Qt::Corner corner:一个可选参数,指定控件要显示在标签控件的哪个角落。默认值是 Qt::TopRightCorner,即右上角。

    cpp 复制代码
    enum Corner {
        TopLeftCorner = 0x00000,
        TopRightCorner = 0x00001,
        BottomLeftCorner = 0x00002,
        BottomRightCorner = 0x00003
    };

代码示例:
CTabTitleWidget.h

cpp 复制代码
/*
tabWidget右侧的widget控件
*/

#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

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

CTabBrowser.h

cpp 复制代码
/*

  自定义QTabWidget实现浏览器tab样式

*/

#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();   //初始化Tab
    void setTabBarFlag(TAB_FLAG flag);  //基于Tab操作设置样式
    void createTabMenu();  //创建菜单
  
private slots:  
    void on_newTab();   //新建tab
    void on_closeTab(int index);    //关闭Tab
    void onMenuShow(const QPoint& pos); //显示菜单
    void on_closeAllTab();  //关闭所有Tab

signals:
    void sig_close();

private:
    CTabTitleWidget* m_pRightWidget = nullptr;
    QMenu* m_pTabMenu = nullptr;
};  

CTabBrowser.cpp

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

//基于Tab操作设置样式
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();  //用于获取 QTabWidget 中索引为 i 的标签页的宽度
        }  
    }
    else
    {
        for (int i = 0;i < tabs - 1;++i)
        {
            tabsWidth += tabBar()->tabRect(i).width();   //用于获取 QTabWidget 中索引为 i 的标签页的宽度
        }  
    }  
  
    if (w > tabsWidth)
    {
        m_pRightWidget->setEmptyWidgetWidth(w - tabsWidth - 32 * 5 - 15);
        this->setStyleSheet(qss0);
    }
    else
    {
        //当所有tab的宽度大于整个tabWidget的宽时
        m_pRightWidget->setEmptyWidgetWidth(150);
        this->setStyleSheet(qss1);
    }  
}  

//初始化Tab
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);  //标题栏放到Tab右侧
    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;

    // 这里写的有问题,应该是 insertTab
    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()
{
}

WPSDemo.h

cpp 复制代码
#pragma once

#include <QtWidgets/QWidget>

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:
    int     m_BorderWidth = 5; //表示鼠标位于边框缩放范围的宽度
};

WPSDemo.cpp

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)
{
	this->resize(600, 400);
    setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);	//无边框窗口
	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);
}

main.cpp

cpp 复制代码
#include "WPSDemo.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    WPSDemo w;
    w.show();
    return a.exec();
}

运行结果

相关推荐
苏三有春2 小时前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Vanranrr2 小时前
C++ QT
java·c++·qt
兆。2 小时前
掌握 PyQt5:从零开始的桌面应用开发
开发语言·爬虫·python·qt
鹏大师运维3 小时前
【功能介绍】信创终端系统上各WPS版本的授权差异
linux·wps·授权·麒麟·国产操作系统·1024程序员节·统信uos
图片转成excel表格3 小时前
wps怎么算出一行1和0两种数值中连续数值1的个数,出现0后不再计算?
excel·wps
徒步僧13 小时前
ThingsBoard规则链节点:RPC Call Reply节点详解
qt·microsoft·rpc
可峰科技14 小时前
斗破QT编程入门系列之一:认识Qt:初步使用(四星斗师)
开发语言·qt
我喜欢就喜欢14 小时前
基于qt vs下的视频播放
开发语言·qt·音视频
CP-DD15 小时前
Qt的架构设计
qt
阿_旭16 小时前
基于YOLO11/v10/v8/v5深度学习的维修工具检测识别系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】
人工智能·python·深度学习·qt·ai