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

运行结果

相关推荐
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner1 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz6 天前
QML Hello World 入门示例
qt
xcyxiner9 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner10 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner10 天前
DicomViewer (添加模型类)3
qt
xcyxiner11 天前
DicomViewer (目录调整) 2
qt
xcyxiner11 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
Non-existent98713 天前
WPS批量清理单元格空白字符的4种方法-异常数字格式处理-实战
excel·wps
桥田智能13 天前
桥田智能 QT-650S:面向白车身焊装的 800kg 重载快换解决方案
开发语言·qt·系统架构