【二十八】【QT开发应用】模拟WPS Tab

WidgetBase 类旨在实现窗口的可调整大小功能,使用户能够手动改变窗口的尺寸。该类通过以下机制实现窗口缩放效果:当鼠标移动至窗口边缘时,鼠标指针样式会动态改变以指示可调整大小的方向。用户在边缘区域按下鼠标左键后,可以通过拖动鼠标实时调整窗口的大小,从而实现对窗口尺寸的交互式控制。

WidgetBase.h

cpp 复制代码
#pragma once
#include <qwidget.h>
class WidgetBase :
    public QWidget {
    Q_OBJECT
public:
    WidgetBase(QWidget* p = nullptr);
    ~WidgetBase();

protected:
    bool nativeEvent(const QByteArray& eventType, void* message, qintptr* result) override;

private:
    int int_BorderWidth = 5;

};

为了实现窗口可调整大小的效果,我们通过重写 nativeEvent 函数来实现所需的功能。int_BorderWidth 表示窗口边缘的自定义宽度。当鼠标位于距离窗口边界 5 像素以内的区域时,将其视为处于窗口边界。通过这种方式,我们能够检测鼠标与窗口边缘的交互,进而调整窗口的大小。

WidgetBase.cpp

cpp 复制代码
#include "WidgetBase.h"


#ifdef Q_OS_WINDOWS
#include <windows.h>
#include <windowsx.h>
#include <qt_windows.h>
#endif // Q_OS_WINDOWS


WidgetBase::WidgetBase(QWidget* p)
:QWidget(p)
{
	setWindowFlags(Qt::FramelessWindowHint);
}

WidgetBase::~WidgetBase() {}

bool WidgetBase::nativeEvent(const QByteArray& eventType, void* message, qintptr* result) {
	MSG* param = static_cast<MSG*>(message);

	switch (param->message) {
	case WM_NCHITTEST:
		QPoint qpoint_globalPos = QCursor::pos();
		QPoint qpoint_lobalPos = this->mapFromGlobal(qpoint_globalPos);
		int int_NX = qpoint_lobalPos.x();
		int int_NY = qpoint_lobalPos.y();

		if (childAt(int_NX, int_NY) != nullptr) return QWidget::nativeEvent(eventType, message, result);



		if (int_NX > 0 && int_NX < int_BorderWidth) *result = HTLEFT;
		if (int_NY > 0 && int_NY < int_BorderWidth) *result = HTTOP;
		if (int_NX<this->width() && int_NX>this->width() - int_BorderWidth) *result = HTRIGHT;
		if (int_NY<this->height() && int_NY>this->height() - int_BorderWidth) *result = HTBOTTOM;

		if (int_NX > 0 && int_NX < int_BorderWidth && int_NY > 0 && int_NY < int_BorderWidth)*result = HTTOPLEFT;
		if (int_NX > 0 && int_NX < int_BorderWidth && int_NY<this->height() && int_NY>this->height() - int_BorderWidth) *result = HTBOTTOMLEFT;
		if (int_NY > 0 && int_NY < int_BorderWidth && int_NX<this->width() && int_NX>this->width() - int_BorderWidth) *result = HTTOPRIGHT;
		if (int_NY<this->height() && int_NY>this->height() - int_BorderWidth && int_NX<this->width() && int_NX>this->width() - int_BorderWidth)*result = HTBOTTOMRIGHT;

		return true;

	}

	return QWidget::nativeEvent(eventType, message, result);
}

1. MSG* param = static_cast<MSG*>(message);

  • 使用 static_castmessage 进行类型转换,将其转换为 MSG* 类型。这是一种安全的类型转换方式,用于将通用的 void* 指针转换为特定的数据类型。在这里,将 message 转换为 MSG* 类型,以便访问 Windows 消息的相关信息。

2. switch (param->message) {

  • 检查 param 中的 message 成员变量,该变量表示当前接收到的 Windows 消息类型。通过 switch 语句进行分支处理,根据不同的消息类型执行相应的操作。

3. case WM_NCHITTEST:

  • WM_NCHITTEST 是一个 Windows 消息,用于确定鼠标指针相对于窗口的位置。该消息的返回值指示鼠标所在窗口区域(例如,标题栏、边框、工作区等),从而决定了鼠标指针的功能(例如移动、调整大小等)。在自定义窗口中,该消息的处理是实现窗口边缘调整大小等交互行为的关键。
cpp 复制代码
		QPoint qpoint_globalPos = QCursor::pos();
		QPoint qpoint_lobalPos = this->mapFromGlobal(qpoint_globalPos);
		int int_NX = qpoint_lobalPos.x();
		int int_NY = qpoint_lobalPos.y();

1. QPoint qpoint_globalPos = QCursor::pos();

  • 该语句使用 QCursor::pos() 获取鼠标在屏幕上的全局坐标,并将其存储在 qpoint_globalPos 变量中。QCursor::pos() 返回一个 QPoint 对象,表示当前鼠标指针在整个屏幕坐标系中的位置。

2. QPoint qpoint_localPos = this->mapFromGlobal(qpoint_globalPos);

  • 使用 mapFromGlobal() 函数将鼠标的全局坐标转换为窗口的局部坐标。this->mapFromGlobal(qpoint_globalPos) 会将全局坐标 qpoint_globalPos 转换为相对于当前窗口(this)的坐标系,并将结果存储在 qpoint_localPos 变量中。这一步使得鼠标的位置可以相对于窗口的边界进行检测。

3. int int_NX = qpoint_localPos.x();

int int_NY = qpoint_localPos.y();

  • 获取局部坐标中的横坐标(x)和纵坐标(y),并分别存储在 int_NXint_NY 中。这两个值表示鼠标在窗口内部的位置,方便后续判断鼠标是否处于窗口边缘,以实现自定义的调整大小功能。
cpp 复制代码
if (childAt(int_NX, int_NY) != nullptr) return QWidget::nativeEvent(eventType, message, result);

1. childAt(int_NX, int_NY):

该函数用于检测给定的局部坐标 (int_NX, int_NY) 是否位于窗口的某个子控件上。它返回一个指向位于此坐标处的子控件的指针。如果没有子控件位于该位置,返回 nullptr

2. if (childAt(int_NX, int_NY) != nullptr):

这条语句检查鼠标是否位于窗口的子控件上。如果 childAt 返回的不是 nullptr,表示鼠标在某个子控件上。

3. return QWidget::nativeEvent(eventType, message, result);:

如果鼠标位于子控件上,调用基类 QWidgetnativeEvent 方法进行默认处理,并立即返回。这表示当前的自定义 nativeEvent 函数不处理鼠标事件,交由 QWidget 类的默认实现来处理。这样做的目的是让子控件自行处理鼠标事件,例如点击按钮、输入框等。

cpp 复制代码
		if (int_NX > 0 && int_NX < int_BorderWidth) *result = HTLEFT;
		if (int_NY > 0 && int_NY < int_BorderWidth) *result = HTTOP;
		if (int_NX<this->width() && int_NX>this->width() - int_BorderWidth) *result = HTRIGHT;
		if (int_NY<this->height() && int_NY>this->height() - int_BorderWidth) *result = HTBOTTOM;

		if (int_NX > 0 && int_NX < int_BorderWidth && int_NY > 0 && int_NY < int_BorderWidth)*result = HTTOPLEFT;
		if (int_NX > 0 && int_NX < int_BorderWidth && int_NY<this->height() && int_NY>this->height() - int_BorderWidth) *result = HTBOTTOMLEFT;
		if (int_NY > 0 && int_NY < int_BorderWidth && int_NX<this->width() && int_NX>this->width() - int_BorderWidth) *result = HTTOPRIGHT;
		if (int_NY<this->height() && int_NY>this->height() - int_BorderWidth && int_NX<this->width() && int_NX>this->width() - int_BorderWidth)*result = HTBOTTOMRIGHT;

		return true;

通过检测鼠标在窗口局部坐标中的位置,确定其所处的窗口区域(例如,左侧边缘、右下角、顶部边缘等),并根据不同的区域设置 *result 为相应的命中测试值(如 HTLEFTHTTOPRIGHTHTBOTTOM 等)。这些值用于指示窗口区域的性质,从而实现对窗口调整大小、移动等操作的处理。

MainWidget.h

cpp 复制代码
#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_MainWidget.h"
#include "WidgetBase.h"

class MainWidget : public WidgetBase
{
    Q_OBJECT

public:
    MainWidget(QWidget *parent = nullptr);
    ~MainWidget();

private slots:
    void on_close();

private:
    Ui::MainWidgetClass ui;
};

class MainWidget : public WidgetBase主窗口头文件继承WidgetBase.

MainWidget.cpp

cpp 复制代码
#include "MainWidget.h"
#include "TapTitle.h"
#include "QBoxLayout"
#include "TabBrowser.h"

MainWidget::MainWidget(QWidget *parent)
    : WidgetBase(parent)
{
    
    resize(600, 400);

    TabBrowser* TabBrowser_pmain = new TabBrowser(this);
    QVBoxLayout* layoutVP_main = new QVBoxLayout(this);
    QWidget* widget_main = new QWidget(this);



    layoutVP_main->addWidget(TabBrowser_pmain);
    layoutVP_main->addWidget(widget_main);
    setLayout(layoutVP_main);

    connect(TabBrowser_pmain, &TabBrowser::sig_close, this, &MainWidget::on_close);

}

MainWidget::~MainWidget()
{}

void MainWidget::on_close() {
    /*其他业务逻辑*/

    close();
}

MainWidget 类的声明采用了单继承的方式,继承自基类 WidgetBase。在主窗口的头文件中,class MainWidget : public WidgetBase 表明 MainWidgetWidgetBase 的派生类,具备 WidgetBase 中的所有属性和方法,并可以在此基础上扩展功能。

在主窗口的实现文件(.cpp 文件)中,通过构造函数初始化列表 MainWidget::MainWidget(QWidget *parent) : WidgetBase(parent) 调用基类 WidgetBase 的构造函数,并将 parent 参数传递给基类,以确保 WidgetBase 的初始化过程得以正确执行。这种继承机制使得 MainWidget 可以重用 WidgetBase 的功能,并在此基础上实现自定义的窗口逻辑。

通过继承 WidgetBase 类,MainWidget 自动继承了 WidgetBase 实现的窗口缩放功能。由于 WidgetBase 已重写了窗口缩放逻辑相关的事件处理(如 nativeEvent),因此 MainWidget 作为派生类,能够直接复用这些功能,而无需额外的实现。这种基于继承的机制,允许 MainWidget 具备 WidgetBase 的自定义窗口交互特性,包括对窗口边缘的调整大小等行为。

TapTitle.h

cpp 复制代码
#pragma once
#include <qwidget.h>
#include "qpushbutton.h"
#include "qboxlayout.h"

class TapTitle :
	public QWidget {
	Q_OBJECT
public:
	TapTitle(QWidget* p = nullptr);
	~TapTitle();
protected:
	void paintEvent(QPaintEvent* event) override;
	void mousePressEvent(QMouseEvent* event) override;
	void mouseDoubleClickEvent(QMouseEvent* event) override;

public:
	void setEmptyWidgetWidth(int w);
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;

};

TapTitle.cpp

cpp 复制代码
#include "TapTitle.h"
#include <QMouseEvent>
#include "QDebug"
#include <QPainter>
#include <QStyleOption>

#ifdef Q_OS_WINDOWS
#include "qt_windows.h"
#pragma comment(lib,"user32.lib")
#endif // Q_OS_WINDOWS

TapTitle::TapTitle(QWidget* p) :QWidget(p) {
	setStyleSheet("background-color:#E3E4E7");
	this->setFixedHeight(32 + 8);
	this->setContentsMargins(0, 0, 0, 0);

	m_pAddBtn = new QPushButton(this);
	m_pEmptyWidget = new QWidget(this);
	m_pUserBtn = new QPushButton(this);
	m_pMinBtn = new QPushButton(this);
	m_pMaxBtn = new QPushButton(this);
	m_pCloseBtn = new QPushButton(this);

	m_pAddBtn->setFixedSize(32, 32);
	m_pUserBtn->setFixedSize(32, 32);
	m_pMinBtn->setFixedSize(32, 32);
	m_pMaxBtn->setFixedSize(32, 32);
	m_pCloseBtn->setFixedSize(32, 32);

	//background-image "border-image:url(:/MainWidget/resources/titlebar/title_icon.png);"
	m_pAddBtn->setStyleSheet("background-image:url(:/MainWidget/resources/add.svg)");
	m_pEmptyWidget->setStyleSheet("QWidget:hover { background: none; border: none; }");
	m_pUserBtn->setStyleSheet("background-image:url(:/MainWidget/resources/user.png)");
	m_pMinBtn->setStyleSheet("background-image:url(:/MainWidget/resources/min.svg)");
	m_pMaxBtn->setStyleSheet("background-image:url(:/MainWidget/resources/max.svg)");
	m_pCloseBtn->setStyleSheet("background-image:url(:/MainWidget/resources/close.svg)");

	m_pAddBtn->setFlat(true);
	m_pUserBtn->setFlat(true);
	m_pMinBtn->setFlat(true);
	m_pMaxBtn->setFlat(true);
	m_pCloseBtn->setFlat(true);

	//m_pEmptyWidget->setAttribute(Qt::WA_Hover, false);

	this->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);

	QHBoxLayout* pHLay = new QHBoxLayout(this);
	pHLay->addWidget(m_pAddBtn);
	pHLay->addWidget(m_pEmptyWidget);
	pHLay->addStretch();
	pHLay->addWidget(m_pUserBtn);
	pHLay->addWidget(m_pMinBtn);
	pHLay->addWidget(m_pMaxBtn);
	pHLay->addWidget(m_pCloseBtn);
	setLayout(pHLay);


	connect(m_pAddBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pMinBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pMaxBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pCloseBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);

}

TapTitle::~TapTitle() {}

void TapTitle::paintEvent(QPaintEvent* event) {
	QStyleOption opt;
	opt.initFrom(this);
	QPainter p(this);
	style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
	QWidget::paintEvent(event);
}

void TapTitle::mousePressEvent(QMouseEvent* event) {
	if (ReleaseCapture()) {
		if (this->window()->isTopLevel()) {
			SendMessage(HWND(this->window()->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
		}

	}
}

void TapTitle::mouseDoubleClickEvent(QMouseEvent* event) {
	emit m_pMaxBtn->clicked();
}

void TapTitle::setEmptyWidgetWidth(int w) {
	m_pEmptyWidget->setMinimumWidth(w);
}

void TapTitle::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) {
			if (pWindow->isMaximized()) pWindow->showNormal();
			else pWindow->showMaximized();
		} else if (pButton == m_pCloseBtn) {
			emit sig_close();
		}
	}

}

标题栏拖拉窗口

TapTitle 类表示自定义窗口的标题栏,由多个控件组成,包括 m_pAddBtnm_pEmptyWidgetm_pUserBtnm_pMinBtnm_pMaxBtnm_pCloseBtn 等。这些控件共同构成了标题栏的交互部分,如添加按钮、最小化、最大化、关闭等功能。

由于 TapTitle 是一个自定义标题栏,为了实现窗口的拖动效果,需要重写 mousePressEvent 以处理鼠标按下事件。通过调用 Windows API,来实现窗口在标题栏区域的拖动。以下是各个关键步骤的专业描述:

  • ReleaseCapture():调用此函数释放当前窗口对鼠标事件的捕获,使得操作系统能够重新接管鼠标事件的处理。这是实现窗口拖动的第一步,确保后续的拖动操作能够由系统默认的窗口移动机制处理。

  • if (this->window()->isTopLevel()):检查当前窗口是否为顶级窗口。只有顶级窗口(即没有父窗口的窗口)才具备移动的能力,因此这一步是执行窗口拖动操作的前提条件。此判断用于确保仅在合适的窗口层级上执行拖动。

  • SendMessage(HWND(this->window()->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0); :通过 SendMessage 函数向操作系统发送系统命令 (WM_SYSCOMMAND) 消息,指示系统开始处理窗口的移动操作。SC_MOVE + HTCAPTION 参数告诉操作系统此次移动是通过拖动标题栏来实现的。HWND(this->window()->winId()) 获取当前窗口的句柄,确保消息发送到正确的窗口实例。这一操作触发了系统的窗口拖动机制,使用户可以通过鼠标拖动标题栏来移动整个窗口。

控件的信号与槽函数连接

cpp 复制代码
	connect(m_pAddBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pMinBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pMaxBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);
	connect(m_pCloseBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);

void TapTitle::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) {
			if (pWindow->isMaximized()) pWindow->showNormal();
			else pWindow->showMaximized();
		} else if (pButton == m_pCloseBtn) {
			emit sig_close();
		}
	}

}
  • connect(m_pAddBtn, &QPushButton::clicked, this, &TapTitle::on_Clicked);

    • 该语句使用 Qt 的信号-槽机制,将 m_pAddBtn 对象的 clicked 信号与当前窗口的槽函数 TapTitle::on_Clicked 进行连接。这样,当用户点击 m_pAddBtn 按钮时,Qt 框架会自动调用 on_Clicked 槽函数。由于其他按钮(m_pMinBtnm_pMaxBtnm_pCloseBtn)也连接到相同的槽函数,所以在槽函数内部通过逻辑判断具体是哪个按钮触发了信号,并执行相应的操作。
  • QPushButton* pButton = qobject_cast<QPushButton*>(sender());

    • 该语句使用 qobject_cast 将信号发送者 (sender()) 安全地转换为 QPushButton* 类型。sender() 返回发出信号的对象的指针,但它是一个通用的 QObject*qobject_cast 提供了一种类型安全的方式,将 QObject* 转换为特定的 QPushButton* 类型。如果 sender() 实际上不是一个 QPushButton,则 qobject_cast 将返回 nullptr,确保程序不会因为类型不匹配而发生错误。
  • QWidget* pWindow = this->window();

    • 通过调用 this->window() 获取当前组件所属的顶层窗口对象的指针。这个方法用于返回包含当前部件的窗口,这样可以在顶层窗口上执行窗口控制操作,例如最小化、最大化和关闭等操作。
  • if (pWindow->isTopLevel())

    • 该语句检查当前窗口是否为顶级窗口。顶级窗口是没有父窗口的窗口,是应用程序的主要界面窗口。只有顶级窗口才能执行特定的窗口管理操作,如最小化、最大化和关闭。此检查确保后续的窗口控制操作只在适用的上下文中执行,避免在子窗口或嵌套窗口中执行不合理的操作。

按钮点击事件的处理逻辑

  • if (pButton == m_pAddBtn):

    • 判断触发信号的按钮是否为 m_pAddBtn(添加标签页按钮)。如果是,发射自定义信号 sig_addtab()。该信号是一个自定义的 Qt 信号,外部对象可以通过连接到该信号的槽函数来响应标签页添加的操作。
  • else if (pButton == m_pMinBtn):

    • 判断触发信号的按钮是否为 m_pMinBtn(最小化按钮)。如果是,则调用 pWindow->showMinimized() 方法将顶层窗口最小化。showMinimized()QWidget 类的成员函数,用于改变窗口的状态,使其最小化到任务栏中,从而不占用桌面空间。
  • else if (pButton == m_pMaxBtn):

    • 判断触发信号的按钮是否为 m_pMaxBtn(最大化按钮)。如果是,进一步检查窗口的当前状态:
      • 如果窗口已经处于最大化状态,调用 pWindow->showNormal() 恢复窗口到普通尺寸。
      • 如果窗口未处于最大化状态,调用 pWindow->showMaximized() 将窗口最大化。此逻辑实现了窗口在普通状态和最大化状态之间的切换。
  • else if (pButton == m_pCloseBtn):

    • 判断触发信号的按钮是否为 m_pCloseBtn(关闭按钮)。如果是,发射自定义信号 sig_close(),通知外部对象执行窗口关闭操作。通过使用自定义信号,可以灵活地让外部槽函数响应关闭事件,进行如保存数据、清理资源等操作。

双击标题栏

  • void TapTitle::mouseDoubleClickEvent(QMouseEvent* event):

    • 这是 TapTitle 类中重写的 mouseDoubleClickEvent 函数,用于处理鼠标在窗口标题栏上的双击事件。QMouseEvent* event 是鼠标事件对象,包含了双击的位置信息和其他属性。通过重写此事件处理函数,TapTitle 类可以自定义双击标题栏时的行为。
  • emit m_pMaxBtn->clicked();:

    • 该语句模拟了 m_pMaxBtn 的点击信号。emit 是 Qt 中用于发送信号的关键字,在这里,直接触发了 m_pMaxBtnclicked() 信号。这样就实现了鼠标双击标题栏与点击最大化按钮的等效效果。通常情况下,双击标题栏会触发窗口在最大化和还原状态之间切换,此实现通过模拟按钮点击来完成这一交互。

自定义函数实现设置空白widget宽度

  • void TapTitle::setEmptyWidgetWidth(int w):

    • 这是 TapTitle 类的成员函数,用于设置标题栏中 m_pEmptyWidget 的宽度。该函数接受一个整数参数 w,表示所需的宽度值。
  • m_pEmptyWidget->setMinimumWidth(w);:

    • 调用了 m_pEmptyWidgetsetMinimumWidth 函数,将其最小宽度设置为传入的参数 wsetMinimumWidthQWidget 类的成员函数,它指定了控件的最小宽度,确保控件不会被压缩到比这个宽度更小的尺寸。此操作允许动态调整标题栏中占位控件的大小,以适应不同的布局需求。

TabBrowser.h

cpp 复制代码
#pragma once
#include <QTabWidget>   
#include "TapTitle.h"
#include "QMenu"

class TabBrowser :public QTabWidget {
	Q_OBJECT
public:
	TabBrowser(QWidget* p = nullptr);
	~TabBrowser();

	enum TAB_FLAG {
		NEW,
		CLOSE,
		NORMAL,
		SPECIAL
	};

signals:
	void sig_close();
	void sig_addtab();

private slots:
	void on_newTab();
	void on_closeTab(int index);


protected:
	void resizeEvent(QResizeEvent* e) override;
private:
	void initTabWidget();
	void setTabBarFlag(TAB_FLAG flag);
	void creatTabMenu();
	void onMenuShow(const QPoint& pos);

private:
	TapTitle* m_pTabTitle = nullptr;
	QMenu* m_pTabMenu = nullptr;
};

TabBrowser.cpp

cpp 复制代码
#include "TabBrowser.h"
#include "QTabBar"
#include "QFrame"
#include "QMenu"
#include "QAction"

QString commonQss = R"(
    QTabBar::tab {
        font: 75 12pt Arial;
        text-align: left;
        width: 184px;
        height: 32px;  /* 添加像素单位 */
        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;
    }
)";

QString qss0 = commonQss + R"(
    QTabBar::scroller {
        width: 0px;
    }
)";

QString qss1 = commonQss + R"(
    QTabBar::scroller {
        width: 36px;
    }
)";


TabBrowser::TabBrowser(QWidget* p):QTabWidget(p) {
	initTabWidget();

	this->addTab(new QWidget, "稻壳");

	this->setUsesScrollButtons(true);
	this->setTabsClosable(true);
	this->setMovable(true);


    setTabBarFlag(NORMAL);
    this->setStyleSheet(qss0);

    connect(this, &QTabWidget::tabCloseRequested, this, &TabBrowser::on_closeTab);
}

TabBrowser::~TabBrowser() {}


void TabBrowser::on_newTab() {
    int Ncount = count();
    QString title = QString::number(Ncount);
    title = "Page" + title;

    this->insertTab(Ncount,new QWidget,title);


	if (!tabsClosable()) {
		setTabsClosable(true);
	}

	setTabBarFlag(NEW);

}

void TabBrowser::on_closeTab(int index) {
	widget(index)->deleteLater();
	setTabBarFlag(CLOSE);

	//当只剩下1个tab时
	if (count() == 1) {
		setTabsClosable(false);
		setTabBarFlag(SPECIAL);
	}
}

void TabBrowser::resizeEvent(QResizeEvent* e) {
    setTabBarFlag(NORMAL);
    QTabWidget::resizeEvent(e);

}

void TabBrowser::initTabWidget() {
	this->setContextMenuPolicy(Qt::CustomContextMenu);
	m_pTabTitle = new TapTitle(this);

    creatTabMenu();
    connect(this, &QTabWidget::customContextMenuRequested, this, &TabBrowser::onMenuShow);

	this->setCornerWidget(m_pTabTitle, Qt::TopRightCorner);
	connect(m_pTabTitle, &TapTitle::sig_close, this, &TabBrowser::sig_close);
    connect(m_pTabTitle, &TapTitle::sig_addtab, this, &TabBrowser::on_newTab);
}

void TabBrowser::setTabBarFlag(TAB_FLAG flag) {
    int w = this->width();

    int tableWidth = 0;
    int tabs = this->count();

    if (flag == NULL || flag == NORMAL) {
        for (int i = 0; i < tabs; i++) {
            tableWidth += tabBar()->tabRect(i).width();
        }
    } else {
        for (int i = 0; i < tabs - 1; i++) {
            tableWidth += tabBar()->tabRect(i).width();
        }
    }

    if (w > tableWidth) {
        m_pTabTitle->setEmptyWidgetWidth(w - tableWidth - 32 * 5 - 15);
        this->setStyleSheet(qss0);
    } else {
        m_pTabTitle->setEmptyWidgetWidth(100);
        this->setStyleSheet(qss1);
    }
}

void TabBrowser::creatTabMenu() {
    m_pTabMenu = new QMenu(this);
    QAction* pAcSave = new QAction(QIcon(":/MainWidget/resources/save.png"), u8"保存", m_pTabMenu);
    QAction* pAcSaveAS = new QAction(QIcon(),u8"另存为",m_pTabMenu);
    QAction* pAcShareDoc = new QAction(QIcon(":/MainWidget/resources/share.png"), u8"分享文档", m_pTabMenu);
    QAction* pAcSendToDevice = new QAction(QIcon(),u8"发送到设备");
    QAction* pAcNewName = new QAction(QIcon(),u8"重命名",m_pTabMenu);
    QAction* pAcSaveToWPSCloud = new QAction(QIcon(),u8"保存到WPS文档",m_pTabMenu);
    QAction* pAcCloseAll = new QAction(QIcon(),u8"关闭所有文件",m_pTabMenu);


    m_pTabMenu->addAction( pAcSave);
    m_pTabMenu->addAction(pAcSaveAS);
    m_pTabMenu->addSeparator();
    m_pTabMenu->addAction(pAcShareDoc);
    m_pTabMenu->addAction(pAcSendToDevice);
    m_pTabMenu->addSeparator();
    m_pTabMenu->addAction(pAcNewName);
    m_pTabMenu->addAction(pAcSaveToWPSCloud);
    m_pTabMenu->addAction(pAcCloseAll);



}

void TabBrowser::onMenuShow(const QPoint& pos) {
	int index = this->tabBar()->tabAt(pos);

	if (index != -1) {
		m_pTabMenu->exec(QCursor::pos());
	}
}

TabBrowser 类继承自 QTabWidget,用于创建一个具有自定义标题栏和交互功能的选项卡控件。在 TabBrowser 的构造函数 TabBrowser::TabBrowser(QWidget* p) 中,调用了 QTabWidget 的构造函数并将父窗口指针 p 传递给基类,实现对选项卡控件的初始化。TabBrowser 类默认添加一个 QTabWidget 实例,同时引入了其他自定义组件,组合成一个新的选项卡控件。

  • TapTitle* m_pTabTitle = nullptr;:

    • m_pTabTitle 是一个指向 TapTitle 对象的指针,用于表示自定义的标题栏。TapTitle 是一个自定义类,继承自 QWidget,用于替代默认的 QTabWidget 标题栏,实现更加丰富的交互功能,例如添加标签、关闭标签、拖动窗口等。通过在 TabBrowser 中组合 TapTitle,实现对选项卡标题栏的高度自定义。
  • QMenu* m_pTabMenu = nullptr;:

    • m_pTabMenu 是一个指向 QMenu 对象的指针,用于表示选项卡控件的上下文菜单。通过在 TabBrowser 中组合 QMenu,实现右键菜单的自定义,为用户提供选项卡的额外操作,如添加、关闭、重命名等。

initTabWidget()

cpp 复制代码
void TabBrowser::initTabWidget() {
	this->setContextMenuPolicy(Qt::CustomContextMenu);
	m_pTabTitle = new TapTitle(this);

    creatTabMenu();
    connect(this, &QTabWidget::customContextMenuRequested, this, &TabBrowser::onMenuShow);

	this->setCornerWidget(m_pTabTitle, Qt::TopRightCorner);
	connect(m_pTabTitle, &TapTitle::sig_close, this, &TabBrowser::sig_close);
    connect(m_pTabTitle, &TapTitle::sig_addtab, this, &TabBrowser::on_newTab);
}
  1. this->setContextMenuPolicy(Qt::CustomContextMenu);

    • 设置选项卡控件的上下文菜单策略为 Qt::CustomContextMenu,允许自定义右键菜单的行为。此策略指示控件在接收到右键单击事件时,不显示默认的上下文菜单,而是发射 customContextMenuRequested 信号,以便开发者自行实现自定义菜单。
  2. m_pTabTitle = new TapTitle(this);

    • 创建一个 TapTitle 对象作为自定义标题栏,并将 TabBrowserthis)设为其父对象。自定义标题栏将用于取代默认的选项卡栏,实现更丰富的交互功能。
  3. creatTabMenu();

    • 调用 creatTabMenu 方法,用于创建选项卡的上下文菜单。此方法通常用于定义右键菜单的各项内容和行为,为选项卡的交互提供更多的操作选项。
  4. connect(this, &QTabWidget::customContextMenuRequested, this, &TabBrowser::onMenuShow);

    • 使用 Qt 的信号-槽机制,将 QTabWidgetcustomContextMenuRequested 信号与 TabBrowser 的槽函数 onMenuShow 关联。customContextMenuRequested 信号在用户右键单击选项卡时触发,调用 onMenuShow 槽函数以显示自定义的上下文菜单。这种机制实现了选项卡的自定义右键菜单功能。
  5. this->setCornerWidget(m_pTabTitle, Qt::TopRightCorner);

    • 使用 setCornerWidget 方法,将自定义的 TapTitle 设置为 QTabWidget 的右上角小部件(Qt::TopRightCorner)。这为选项卡控件提供了一个可自定义的标题栏区域,可以在该区域添加按钮、文本或其他控件,以增强选项卡的功能和外观。
  6. connect(m_pTabTitle, &TapTitle::sig_close, this, &TabBrowser::sig_close);

    • 将自定义标题栏(m_pTabTitle)的 sig_close 信号与 TabBrowsersig_close 信号进行连接。这一操作允许标题栏触发关闭操作,并将此操作传递给 TabBrowser,从而实现自定义标题栏与选项卡控件之间的交互。
  7. connect(m_pTabTitle, &TapTitle::sig_addtab, this, &TabBrowser::on_newTab);

    • TapTitlesig_addtab 信号与 TabBrowseron_newTab 槽函数关联。当自定义标题栏中添加标签页的信号触发时,on_newTab 槽函数被调用,实现了通过标题栏添加新选项卡的功能。

setTabBarFlag()

cpp 复制代码
void TabBrowser::setTabBarFlag(TAB_FLAG flag) {
    int w = this->width();

    int tableWidth = 0;
    int tabs = this->count();

    if (flag == NULL || flag == NORMAL) {
        for (int i = 0; i < tabs; i++) {
            tableWidth += tabBar()->tabRect(i).width();
        }
    } else {
        for (int i = 0; i < tabs - 1; i++) {
            tableWidth += tabBar()->tabRect(i).width();
        }
    }

    if (w > tableWidth) {
        m_pTabTitle->setEmptyWidgetWidth(w - tableWidth - 32 * 5 - 15);
        this->setStyleSheet(qss0);
    } else {
        m_pTabTitle->setEmptyWidgetWidth(100);
        this->setStyleSheet(qss1);
    }
}

int w = this->width();

获取当前 TabBrowser 窗口的宽度,用于后续计算选项卡和标题栏的宽度布局。

int tableWidth = 0;

初始化 tableWidth,用于累加所有选项卡的宽度,以便在后续步骤中比较窗口宽度和选项卡总宽度。

int tabs = this->count();

获取选项卡的数量,count() 返回当前 QTabWidget 中的选项卡数目,用于遍历选项卡并计算它们的总宽度。

if (flag == NULL || flag == NORMAL) { ... } else { ... }

根据 flag 参数决定如何计算选项卡的总宽度。flag 是一个枚举类型 TAB_FLAG,用于指示当前标题栏的状态。此处判断是否为 NULLNORMAL,以确定选项卡的宽度计算方式。

  • for (int i = 0; i < tabs; i++) { ... }

    flagNULLNORMAL 时,遍历所有选项卡,调用 tabBar()->tabRect(i).width() 获取每个选项卡的宽度并累加到 tableWidth 中。

  • for (int i = 0; i < tabs - 1; i++) { ... }

    如果 flag 不为 NULLNORMAL,则遍历所有选项卡,但忽略最后一个。这样可以调整选项卡布局,以满足某些特殊情况下的需求。

if (w > tableWidth) { ... } else { ... }

比较窗口宽度 w 与选项卡总宽度 tableWidth,根据结果调整标题栏中空白区域的宽度,并设置相应的样式表。

  • m_pTabTitle->setEmptyWidgetWidth(w - tableWidth - 32 * 5 - 15);

    如果窗口宽度大于选项卡总宽度,计算空白区域的宽度并调用 m_pTabTitle->setEmptyWidgetWidth() 方法进行设置。这里的计算包括减去一些固定值(32 * 5 + 15),用于留出其他控件(如按钮)的空间。

  • this->setStyleSheet(qss0);

    应用 qss0 样式表,以调整选项卡的外观。这通常用于调整选项卡栏的样式,如颜色、边距等。

  • m_pTabTitle->setEmptyWidgetWidth(100);

    如果窗口宽度不够,则将空白区域的宽度设置为固定值 100,以防止选项卡栏中出现布局问题。

  • this->setStyleSheet(qss1);

    应用 qss1 样式表,以调整选项卡的外观。与前面的 qss0 不同,这个样式表定义了滑动条的宽度。

resizeEvent(QResizeEvent* e)

cpp 复制代码
void TabBrowser::resizeEvent(QResizeEvent* e) {
    setTabBarFlag(NORMAL);
    QTabWidget::resizeEvent(e);

}
  • void TabBrowser::resizeEvent(QResizeEvent* e):

    • 这是 TabBrowser 类中重写的 resizeEvent 方法,用于响应窗口调整大小的事件。QResizeEvent* e 是调整大小事件对象,包含有关窗口尺寸变化的信息。通过重写该方法,TabBrowser 可以在每次窗口大小发生变化时执行自定义的处理逻辑。
  • setTabBarFlag(NORMAL);:

    • 调用 setTabBarFlag 方法,并传递参数 NORMAL。在此上下文中,NORMAL 是枚举类型 TAB_FLAG 的一个值,用于指示选项卡栏的布局状态。此调用用于根据当前窗口的新尺寸重新调整选项卡栏的布局和样式,包括计算空白区域的宽度以及设置合适的样式表。
    • 当窗口大小发生变化时,setTabBarFlag 的调用确保选项卡栏根据新的窗口尺寸进行自适应调整,从而保持界面的美观和功能性。
  • QTabWidget::resizeEvent(e);:

    • 调用基类 QTabWidgetresizeEvent 方法,执行其默认的调整大小操作。这样确保 QTabWidget 及其子类中与调整大小相关的所有内部处理逻辑都得以正确执行。
    • 在重写事件处理函数时,调用基类的默认实现是常见的做法,确保继承链中其他类的处理逻辑不会被遗漏。

on_closeTab(int index)

cpp 复制代码
void TabBrowser::on_closeTab(int index) {
	widget(index)->deleteLater();
	setTabBarFlag(CLOSE);

	//当只剩下1个tab时
	if (count() == 1) {
		setTabsClosable(false);
		setTabBarFlag(SPECIAL);
	}
}
  • widget(index)->deleteLater();:

    • 获取指定索引的选项卡中的小部件,并调用其 deleteLater() 方法,延迟销毁该小部件。deleteLater() 是一种安全的对象销毁方式,它将对象的销毁操作延迟到事件循环的下一次迭代,避免立即销毁可能导致的潜在问题(例如:访问已销毁的对象)。此操作实现了选项卡及其内容的安全释放。
  • setTabBarFlag(CLOSE);:

    • 调用 setTabBarFlag 方法,将选项卡栏状态设置为 CLOSE。这通常用于调整选项卡栏的布局和样式,以反映选项卡关闭后的状态。例如,调整空白区域的宽度和重新应用样式表,以保持界面的视觉一致性。
  • if (count() == 1) { ... }:

    • 检查选项卡的数量,count() 方法返回当前选项卡的总数。该逻辑用于判断当前选项卡关闭后,是否只剩下最后一个选项卡。此判断是为了防止用户关闭所有选项卡,使界面出现异常状态。
  • setTabsClosable(false);:

    • 当只剩下一个选项卡时,调用 setTabsClosable(false) 禁用选项卡的关闭按钮。这是为了防止用户关闭最后一个选项卡,确保界面始终保留至少一个可用选项卡。该方法是 QTabWidget 的成员函数,用于设置选项卡是否显示关闭按钮。
  • setTabBarFlag(SPECIAL);:

    • 调用 setTabBarFlag 方法,将选项卡栏状态设置为 SPECIAL,以适应只有一个选项卡的特殊情况。此操作可能包括调整选项卡栏的外观、样式以及其他交互行为,以反映特殊状态。

on_newTab()

cpp 复制代码
void TabBrowser::on_newTab() {
    int Ncount = count();
    QString title = QString::number(Ncount);
    title = "Page" + title;

    this->insertTab(Ncount,new QWidget,title);


	if (!tabsClosable()) {
		setTabsClosable(true);
	}

	setTabBarFlag(NEW);

}
  • int Ncount = count();:

    • 调用 count() 方法获取当前选项卡的总数量,并将其存储在 Ncount 中。count()QTabWidget 的成员函数,返回当前选项卡的数量。此值用于确定新选项卡的插入位置和命名。
  • QString title = QString::number(Ncount);:

    • 使用 QString::numberNcount 转换为字符串形式,以便生成新选项卡的标题。QString 是 Qt 提供的字符串类,用于文本处理。将数字转换为字符串,便于创建具有动态编号的选项卡标题。
  • title = "Page" + title;:

    • 将字符串 "Page"title 拼接,生成新选项卡的完整标题,例如 "Page1"、"Page2" 等。此操作为新添加的选项卡提供了唯一且有意义的标识。
  • this->insertTab(Ncount, new QWidget, title);:

    • 调用 insertTab 方法在指定位置插入一个新的选项卡。insertTabQTabWidget 的成员函数,接受三个参数:插入位置(Ncount)、新选项卡的内容(new QWidget)和选项卡标题(title)。此操作在选项卡栏中添加一个新的空白选项卡,并将其放置在当前选项卡总数的位置,使新选项卡始终添加在末尾。
  • if (!tabsClosable()) { setTabsClosable(true); }:

    • 调用 tabsClosable() 检查选项卡是否可关闭。如果不可关闭,则调用 setTabsClosable(true) 将其设置为可关闭。setTabsClosableQTabWidget 的成员函数,用于显示或隐藏选项卡上的关闭按钮。此逻辑确保在新选项卡被添加后,用户可以关闭选项卡。
  • setTabBarFlag(NEW);:

    • 调用自定义方法 setTabBarFlag,将选项卡栏状态设置为 NEW。此操作用于调整选项卡栏的布局和样式,以适应新的选项卡。通过 setTabBarFlag,可以根据选项卡数量和状态的变化来动态调整选项卡栏的外观和行为。

TabBrowser(QWidget* p):QTabWidget§

cpp 复制代码
TabBrowser::TabBrowser(QWidget* p):QTabWidget(p) {
	initTabWidget();

	this->addTab(new QWidget, "稻壳");

	this->setUsesScrollButtons(true);
	this->setTabsClosable(true);
	this->setMovable(true);


    setTabBarFlag(NORMAL);
    this->setStyleSheet(qss0);

    connect(this, &QTabWidget::tabCloseRequested, this, &TabBrowser::on_closeTab);
}
  • initTabWidget();:

    • 调用自定义的 initTabWidget 方法,初始化选项卡控件的属性、标题栏和上下文菜单等功能。此步骤将 TabBrowser 的界面和交互逻辑进行自定义配置,使其满足应用需求。
  • this->addTab(new QWidget, "稻壳");:

    • 使用 addTab 方法在选项卡栏中添加一个新选项卡,包含一个空的 QWidget,标题为 "稻壳"addTabQTabWidget 的成员函数,用于向选项卡控件中添加新的页面。此步骤确保 TabBrowser 在初始化时具有至少一个选项卡。
  • this->setUsesScrollButtons(true);:

    • 启用选项卡栏的滚动按钮。当选项卡数量超过显示区域时,滚动按钮可以用于查看所有选项卡。setUsesScrollButtonsQTabWidget 的成员函数,该设置增强了选项卡栏的可用性,确保在选项卡数量较多时仍能提供良好的导航体验。
  • this->setTabsClosable(true);:

    • 设置选项卡可关闭,在每个选项卡上显示关闭按钮。setTabsClosableQTabWidget 的成员函数,允许用户通过点击关闭按钮来移除选项卡。这为选项卡的管理提供了更灵活的操作方式。
  • this->setMovable(true);:

    • 使选项卡可拖动,以调整其在选项卡栏中的顺序。setMovableQTabWidget 的成员函数,启用该属性后,用户可以通过拖拽选项卡在不同位置之间移动,提供了更灵活的界面布局。
  • setTabBarFlag(NORMAL);:

    • 调用自定义的 setTabBarFlag 方法,将选项卡栏的状态设置为 NORMAL。该方法通常用于调整选项卡栏的布局和空白区域,并应用适当的样式,以符合选项卡栏的初始状态。
  • this->setStyleSheet(qss0);:

    • 应用样式表 qss0,用于自定义选项卡栏的外观。通过调用 setStyleSheet,可以调整选项卡栏的视觉样式,例如颜色、字体、边距等,使其符合应用的界面设计。
  • connect(this, &QTabWidget::tabCloseRequested, this, &TabBrowser::on_closeTab);:

    • 使用 Qt 的信号-槽机制,将 QTabWidgettabCloseRequested 信号连接到 TabBrowser 的槽函数 on_closeTab。当用户点击选项卡上的关闭按钮时,tabCloseRequested 信号会被触发,并传递要关闭的选项卡的索引给 on_closeTab,从而执行选项卡关闭的逻辑。

creatTabMenu()

cpp 复制代码
void TabBrowser::creatTabMenu() {
    m_pTabMenu = new QMenu(this);
    QAction* pAcSave = new QAction(QIcon(":/MainWidget/resources/save.png"), u8"保存", m_pTabMenu);
    QAction* pAcSaveAS = new QAction(QIcon(),u8"另存为",m_pTabMenu);
    QAction* pAcShareDoc = new QAction(QIcon(":/MainWidget/resources/share.png"), u8"分享文档", m_pTabMenu);
    QAction* pAcSendToDevice = new QAction(QIcon(),u8"发送到设备");
    QAction* pAcNewName = new QAction(QIcon(),u8"重命名",m_pTabMenu);
    QAction* pAcSaveToWPSCloud = new QAction(QIcon(),u8"保存到WPS文档",m_pTabMenu);
    QAction* pAcCloseAll = new QAction(QIcon(),u8"关闭所有文件",m_pTabMenu);


    m_pTabMenu->addAction( pAcSave);
    m_pTabMenu->addAction(pAcSaveAS);
    m_pTabMenu->addSeparator();
    m_pTabMenu->addAction(pAcShareDoc);
    m_pTabMenu->addAction(pAcSendToDevice);
    m_pTabMenu->addSeparator();
    m_pTabMenu->addAction(pAcNewName);
    m_pTabMenu->addAction(pAcSaveToWPSCloud);
    m_pTabMenu->addAction(pAcCloseAll);



}
  • m_pTabMenu->addSeparator(); :

    调用 QMenu 的成员函数 addSeparator(),在菜单中插入一个分隔符(QSeparator)。分隔符在菜单中起到分组和视觉分割的作用,提高菜单的可读性和用户体验。通过这种方式,可以将菜单选项按照功能或类别进行逻辑划分,使用户更容易找到所需的操作。

  • QAction* pAcSave = new QAction(QIcon(":/MainWidget/resources/save.png"), u8"保存", m_pTabMenu); :

    创建一个 QAction 对象 pAcSave,表示菜单中的一个可交互选项。"保存"选项通过以下参数进行初始化:

    • QIcon(":/MainWidget/resources/save.png") : 从资源文件中加载图标,并为菜单项指定一个图标,提供视觉提示,增强用户界面体验。QIcon 构造函数使用资源路径 ":/MainWidget/resources/save.png" 来定位并加载图标,确保图标在不同的运行环境中正确显示。

    • u8"保存" : 指定菜单选项的文本。u8 前缀表示这是一个 UTF-8 编码的字符串,确保中文字符在不同的系统和语言环境中正确显示。

    • m_pTabMenu : 设置菜单项的父对象,指定 QAction 归属的菜单 (m_pTabMenu) 管理其生命周期。这样在 m_pTabMenu 被销毁时,pAcSave 也会被自动销毁,确保资源的正确管理。

onMenuShow(const QPoint& pos)

cpp 复制代码
void TabBrowser::onMenuShow(const QPoint& pos) {
	int index = this->tabBar()->tabAt(pos);

	if (index != -1) {
		m_pTabMenu->exec(QCursor::pos());
	}
}
  • void TabBrowser::onMenuShow(const QPoint& pos):

    • 该函数是一个槽函数,用于响应上下文菜单请求(例如右键单击)事件。pos 参数表示鼠标事件相对于 QTabWidget 的局部坐标,通过该坐标可以确定鼠标指针位于哪个选项卡上。
  • int index = this->tabBar()->tabAt(pos);:

    • 调用 tabBar()->tabAt(pos) 获取鼠标点击位置所在的选项卡索引。tabAtQTabBar 类的成员函数,接受局部坐标 pos 作为参数,返回位于该坐标上的选项卡的索引。如果 pos 不在任何选项卡上,则返回 -1。这一步用于判断鼠标点击是否发生在某个选项卡上,从而决定是否显示上下文菜单。
  • if (index != -1):

    • 判断鼠标点击是否位于某个有效的选项卡上。如果 index 不等于 -1,则表示鼠标点击的位置属于一个选项卡,此时可以继续执行显示菜单的操作。
  • m_pTabMenu->exec(QCursor::pos());:

    • 调用 QMenu 的成员函数 exec() 在指定位置显示上下文菜单。QCursor::pos() 获取当前鼠标的全局屏幕坐标,确保菜单在鼠标指针的位置弹出。exec() 是一个阻塞式的菜单显示方法,菜单在执行期间会阻止其他事件处理,直到用户在菜单中选择一项或点击其他区域以关闭菜单。这一步实现了自定义右键菜单的显示,使用户能够对选项卡执行特定操作(例如保存、关闭等)。

样式表

cpp 复制代码
QString commonQss = R"(
    QTabBar::tab {
        font: 75 12pt Arial;
        text-align: left;
        width: 184px;
        height: 32px;  /* 添加像素单位 */
        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;
    }
)";

QString qss0 = commonQss + R"(
    QTabBar::scroller {
        width: 0px;
    }
)";

QString qss1 = commonQss + R"(
    QTabBar::scroller {
        width: 36px;
    }
)";
  • qss0 样式表:

    • 通过设置 QTabBar::scroller { width: 0px; },将滑动条(滚动按钮)的宽度设为 0 像素。这样,即使启用了 QTabBar 的滚动功能,滑动条也不会在界面上显示。该设置实际上隐藏了滚动条,适用于选项卡数量较少,可以完全在选项卡栏中显示的场景,避免界面出现多余的滚动控件。
  • qss1 样式表:

    • 通过设置 QTabBar::scroller { width: 36px; },将滑动条的宽度设为 36 像素。这样,在选项卡数量较多超出可视区域时,滑动条可以显示出来,允许用户通过滚动按钮查看所有选项卡。这种设置确保了选项卡栏在内容过多的情况下仍然具有良好的可用性和导航性。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

相关推荐
小技与小术4 分钟前
数据结构之树与二叉树
开发语言·数据结构·python
hccee25 分钟前
C# IO文件操作
开发语言·c#
hummhumm30 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
J老熊40 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
zmd-zk1 小时前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.01 小时前
Go语言进阶&依赖管理
开发语言·后端·golang
花海少爷1 小时前
第十章 JavaScript的应用课后习题
开发语言·javascript·ecmascript
手握风云-1 小时前
数据结构(Java版)第二期:包装类和泛型
java·开发语言·数据结构
喵叔哟2 小时前
重构代码中引入外部方法和引入本地扩展的区别
java·开发语言·重构