文章目录
- 一、手写代码实现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();
}
运行结果: