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

文章目录

  • 一、手写代码实现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_OBJECTpublic: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")
#endifCTabTitleWidget::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();   //初始化Tabvoid setTabBarFlag(TAB_FLAG flag);  //基于Tab操作设置样式void createTabMenu();  //创建菜单private slots:  void on_newTab();   //新建tabvoid on_closeTab(int index);    //关闭Tabvoid onMenuShow(const QPoint& pos); //显示菜单void on_closeAllTab();  //关闭所有Tabsignals: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);  //滚动鼠标可切换tabthis->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;// 这里写的有问题,应该是 insertTabthis->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 _DEBUGqDebug() << u8"当前tab为:" << QString::number(index);this->setCurrentIndex(index);
#endifif (index != -1){m_pTabMenu->exec(QCursor::pos());}
}void CTabBrowser::on_closeAllTab()
{
}

WPSDemo.h

#pragma once#include <QtWidgets/QWidget>class WPSDemo : public QWidget
{Q_OBJECTpublic: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.mzph.cn/bicheng/40652.shtml

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

相关文章

centos部署Nginx并配置网页进行访问

1. 安装 Nginx 安装 EPEL 仓库 EPEL (Extra Packages for Enterprise Linux) 仓库提供了许多不在 CentOS 基础仓库中的软件包&#xff0c;包括 Nginx。 sudo yum install epel-release -y#安装 Nginx sudo yum install nginx -y 2. 启动 Nginx 并设置开机启动 sudo system…

ECCV2024|AIGC(图像生成,视频生成,3D生成等)相关论文汇总(附论文链接/开源代码)【持续更新】

ECCV2024&#xff5c;AIGC相关论文汇总&#xff08;如果觉得有帮助&#xff0c;欢迎点赞和收藏&#xff09; Awesome-ECCV2024-AIGC1.图像生成(Image Generation/Image Synthesis)Accelerating Diffusion Sampling with Optimized Time StepsAnyControl: Create Your Artwork w…

Objection 对命令的批量操作

假定现在需要对好多不同的类进行批量hook&#xff0c;逐个hook非常繁琐&#xff0c;那么可以要将这些hook的类放到一个文件里&#xff0c;并且在这些类的前面加上hook命令&#xff0c;内容如下 使用如下命令执行该文件中的命令 objection -g 测试 explore -c d:/hookData/toHoo…

git上传本地单独修改的文件_git 只推送变化的文件

git上传本地单独修改的文件_git 只推送变化的文件-CSDN博客 只推送本地修改的文件&#xff0c;这篇文章方法可行。

昇思25天学习打卡营第13天|ResNet50图像分类

1. 学习内容复盘 图像分类是最基础的计算机视觉应用&#xff0c;属于有监督学习类别&#xff0c;如给定一张图像(猫、狗、飞机、汽车等等)&#xff0c;判断图像所属的类别。本章将介绍使用ResNet50网络对CIFAR-10数据集进行分类。 ResNet网络介绍 ResNet50网络是2015年由微软…

传承与创新,想让认字更简单?就来看《米小圈动画汉字》吧!

汉字&#xff0c;作为中华文化的精髓和根基&#xff0c;自古以来便承载着中华民族的思想与记忆。在现代社会&#xff0c;随着文化多样性的崛起和科技进步的推动&#xff0c;汉字的教育也更加的多元化&#xff0c;《米小圈动画汉字》作为一项全新的教育资源&#xff0c;不仅致力…

Postman介绍

Postman 是一款流行的 API 开发和测试工具&#xff0c;它提供了一个直观的用户界面&#xff0c;使开发者可以轻松地构建、测试和修改 HTTP 请求。Postman 不仅适用于测试人员&#xff0c;也广泛应用于开发人员、产品经理和API设计者中&#xff0c;以确保API的正确性和性能。 以…

删除账户相关信息

功能需求 获取正确的待删除账户名杀死系统中正在运行的属于该账户的进程确认系统中属于该账户的所有文件删除该账户 1. 获取正确的待删除账户名 #让用户输入账户名 read -t 10 -p "please input account name: " accountif [ -z $account ] thenecho "account…

【python基础】—calendar模块

文章目录 前言一、calendar模块方法1.firstweekday()2.setfirstweekday(firstweekday)3.isleap(year)4.leapdays(y1, y2)5.weekday(year, month, day)6.monthrange(year, month)7.weekheader(n)8.monthcalendar(year, month)9.prmonth(theyear, themonth, w0, l0)10.prcal(year…

【硬核科普】存算一体化系统(Processing-in-Memory, PIM)深入解析

文章目录 0. 前言1. 提出背景1.1 存储墙1.2 功耗墙 2. 架构方案2.1 核心特征2.2 技术实现2.2.1 电流模式2.2.2 电压模式2.2.3 模式选择 2.3 PIM方案优势 3. 应用场景4. 典型产品4.1 鸿图H304.2 三星HBM-PIM 5. 存算一体化缺点6. 总结 0. 前言 按照国际惯例&#xff0c;首先声明…

c++类模板及应用

文章目录 为什么要有函数模板一般实现举例类模板举例 继承中类模板的使用特殊情况 友元函数模板类和静态成员类模板实践 为什么要有函数模板 项目需求: 实现多个函数用来返回两个数的最大值&#xff0c;要求能支持char类型、int类型、double 一般实现举例 类模板举例 继承中类…

如视“VR+AI”实力闪耀2024世界人工智能大会

7月4日&#xff0c;2024世界人工智能大会暨人工智能全球治理高级别会议&#xff08;以下简称为“WAIC 2024”&#xff09;在上海盛大开幕&#xff0c;本届大会由外交部、国家发展和改革委员会、教育部等部门共同主办&#xff0c;围绕“以共商促共享 以善治促善智”主题&#xf…

什么是构造函数?Java 中构造函数的重载如何实现?

构造函数&#xff0c;就像是建筑房屋时的奠基仪式&#xff0c;是Java类中一个特殊的方法&#xff0c;主要用于初始化新创建的对象。 每当创建一个类的新实例时&#xff0c;构造函数就会自动调用&#xff0c;负责为这个新对象分配内存&#xff0c;并对其进行必要的设置&#xf…

【PythonGIS】基于Geopandas和Shapely计算矢量面最短路径

在GIS进行空间分析时经常会需要计算最短路径,我也是最近在计算DPC的时候有这方面的需求,刚开始直接是用面的中心点求得距离,但其对不规则或空洞面很不友好。所以今天跟大家分享一下基于Geopandas和Shapely计算矢量面最短路径,这里的最短即点/边的最短! 原创作者:RS迷途小…

mysql查询父级树

WITH RECURSIVE parents AS (SELECT id, parent_idFROM t_departmentWHERE id 10004154UNION ALLSELECT c.id, c.parent_idFROM t_department cINNER JOIN parents p ON c.id p.parent_id ) SELECT parent_id FROM parents;

【SSL 1823】消灭怪物(非传统BFS)

题目大意 小b现在玩一个极其无聊的游戏&#xff0c;它控制角色从基地出发&#xff0c;一路狂奔夺走了对方的水晶&#xff0c;可是正准备回城时&#xff0c;发现地图上已经生成了 n n n 个怪。 现在假设地图是二维平面&#xff0c;所有的怪和角色都认为是在这个二维平面的点上…

【算法训练记录——Day41】

Day41——动态规划Ⅲ 1.理论基础——代码随想录2.纯01背包_[kamacoder46](https://kamacoder.com/problempage.php?pid1046)3.leetcode_416分割等和子集 背包&#xff01;&#xff01; 1.理论基础——代码随想录 主要掌握01背包和完全背包 物品数量&#xff1a; 只有一个 ——…

农作物生长环境的远程监控与智能调控

农作物生长环境的远程监控与智能调控 农作物生长环境的远程监控与智能调控技术&#xff0c;作为现代农业科技的核心组成部分&#xff0c;正逐步革新传统农业的生产模式&#xff0c;推动农业向精准化、智能化转型。这一技术体系综合应用了物联网、大数据、云计算以及人工智能等…

龙芯杯个人赛记录

惊觉8.5就是个人赛ddl&#xff0c;啥都不会和没做&#xff0c;打算对着《cpu设计实战》和B站视频走。

chrome 谷歌浏览器插件打包

1、找到id对应的字符串去搜索 C:\Users\<你的用户名>\AppData\Local\Google\Chrome\User Data\Default\Extensions2、选择根目录 直接加载下面的路径扩展可用&#xff1a;