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

news/2024/7/8 4:51:08 标签: qt, wps, 无边框窗口

文章目录

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

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

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

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

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

void QTabWidget::setCornerWidget(QWidget *widget, Qt::Corner corner = Qt::TopRightCorner)

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

参数:

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

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

    enum Corner {
        TopLeftCorner = 0x00000,
        TopRightCorner = 0x00001,
        BottomLeftCorner = 0x00002,
        BottomRightCorner = 0x00003
    };
    

代码示例:
CTabTitleWidget.h

/*
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

#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

/*

  自定义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

#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

#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

#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

#include "WPSDemo.h"
#include <QtWidgets/QApplication>

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

运行结果
image-20240630180959376


http://www.niftyadmin.cn/n/5536458.html

相关文章

PPT中的文字跟随Excel动态变化,且保留文字格式

今天协助客户解决了一个有趣的问题&#xff0c;这里记录一下&#xff0c;以此共勉。 目录 1. 提出问题2. 此功能的应用场景3. 开始制作4. 注意事项5. 若遇到任何问题 1. 提出问题 PPT的图表是可以引用Excel的&#xff0c;那PPT的文本是否可以引用Excel实现动态更新呢&#xff…

使用香橙派AIpro做目标检测

使用香橙派AIpro做目标检测 文章目录 使用香橙派AIpro做目标检测香橙派AIpro开发板介绍香橙派AIpro应用体验识别图像识别视频摄像头 香橙派AIpro AI应用场景总结 香橙派AIpro开发板介绍 ​ OrangePi AIpro(8-12T)是一款集成昇腾AI技术的开发板&#xff0c;搭载4核64位CPU和AI处…

半导体制造企业 文件共享存储应用

用户背景&#xff1a;半导体设备&#xff08;上海&#xff09;股份有限公司是一家以中国为基地、面向全球的微观加工高端设备公司&#xff0c;为集成电路和泛半导体行业提供具竞争力的高端设备和高质量的服务。 挑战&#xff1a;芯片的行业在国内迅猛发展&#xff0c;用户在上海…

因为文件共享不安全,所以你不能连接到文件共享。此共享需要过时的SMB1协议,而此协议是不安全的 解决方法

目录 1. 问题所示2. 解决方法3. 解决方法1. 问题所示 输入共享文件地址的时候,出现如下信息: 因为文件共享不安全,所以你不能连接到文件共享。此共享需要过时的SMB1协议,而此协议是不安全的,可能会是你的系统遭受攻击。你的系统需要SMB2或更高版本截图如下所示: 2. 解决…

启航IT之旅:高考假期预习指南

标题&#xff1a;启航IT之旅&#xff1a;高考假期预习指南 随着高考的落幕&#xff0c;许多有志于IT领域的学子们即将踏上新的学习旅程。这个假期&#xff0c;是他们探索IT世界的黄金时期。本文将为准IT新生们提供一份全面的预习指南&#xff0c;帮助他们为未来的学习和职业生…

pytorch-时间序列

目录 1. 时间序列2. word embedding2.1 one hot2.2 word2vec2.3 GloVe 1. 时间序列 具有时间相关性的序列叫做时间序列&#xff0c;比如&#xff1a;语音、文本句子 2. word embedding 2.1 one hot 针对句子来说&#xff0c;可以用[seq_len, vector_len] 有多少个单词vecto…

[java]windows下jdk安装包所有版本系列下载地址汇总国内源下载

jdk1.8及其以前版本可以参考[java]windows和linux下jdk1.8安装包所有版本系列下载地址汇总&#xff0c;这里只发布jdk9及其以后最新版本。注意下面均为windows x64版本安装包exe格式 序号java版本下载地址1jdk-22.0.1-windows-x64-bin.exe点我下载2jdk-21.0.3-windows-x64-bin…

Kafka系列之SpringBoot集成Kafka

本文介绍如何在springboot项目中集成kafka收发message。 pom依赖 springboot相关的依赖我们就不提了&#xff0c;和kafka相关的只依赖一个spring-kafka集成包 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka<…