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