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

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

WidgetBase.h

#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

#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 消息,用于确定鼠标指针相对于窗口的位置。该消息的返回值指示鼠标所在窗口区域(例如,标题栏、边框、工作区等),从而决定了鼠标指针的功能(例如移动、调整大小等)。在自定义窗口中,该消息的处理是实现窗口边缘调整大小等交互行为的关键。
		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 中。这两个值表示鼠标在窗口内部的位置,方便后续判断鼠标是否处于窗口边缘,以实现自定义的调整大小功能。
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 类的默认实现来处理。这样做的目的是让子控件自行处理鼠标事件,例如点击按钮、输入框等。

		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

#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

#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

#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

#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()) 获取当前窗口的句柄,确保消息发送到正确的窗口实例。这一操作触发了系统的窗口拖动机制,使用户可以通过鼠标拖动标题栏来移动整个窗口。

控件的信号与槽函数连接

	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

#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

#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()

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()

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)

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)

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()

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§

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()

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)

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() 是一个阻塞式的菜单显示方法,菜单在执行期间会阻止其他事件处理,直到用户在菜单中选择一项或点击其他区域以关闭菜单。这一步实现了自定义右键菜单的显示,使用户能够对选项卡执行特定操作(例如保存、关闭等)。

样式表

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 像素。这样,在选项卡数量较多超出可视区域时,滑动条可以显示出来,允许用户通过滚动按钮查看所有选项卡。这种设置确保了选项卡栏在内容过多的情况下仍然具有良好的可用性和导航性。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/887649.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Uniapp API

1.uni.showToast 显示消息提示框 unishowToast({ obj参数 }) 2.uni.showLoading 显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提示框。 3.uni.showModal 显示模态弹窗&#xff0c;可以只有一个确定按钮&#xff0c;也可以同时有确定和取消按钮。类似于一个A…

躺平成长:微信小程序运营日记第二天

在进行属于生活的开源之后&#xff0c;自己更加感受到自己存在的渺茫&#xff0c;同时更加开始深刻领会&#xff0c;开源的重要性&#xff0c;在开源&#xff0c;开放&#xff0c;创造&#xff0c;再创新的思维模式下&#xff0c;不发布八部金刚功相关的训练视频&#xff0c;自…

基于Node2Vec的图嵌入实现过程

目录 一、引言二、Node2Vec&#xff08;原理&#xff09;2.1 随机游走&#xff08;Random Walk&#xff09;2.2 嵌入学习2.3 Node2Vec 的优势 三、使用 Node2Vec 进行图嵌入&#xff08;实践&#xff09;3.1 读取和转换 JSON 文件为 Graph 对象3.2 训练 Node2Vec 模型3.3 二维嵌…

MySQL--三大范式(超详解)

目录 一、前言二、三大范式2.1概念2.2第一范式&#xff08;1NF&#xff09;2.3第二范式&#xff08;2NF&#xff09;2.3第三范式&#xff08;3NF&#xff09; 一、前言 欢迎大家来到权权的博客~欢迎大家对我的博客进行指导&#xff0c;有什么不对的地方&#xff0c;我会及时改进…

使用前端三剑客实现一个备忘录

一&#xff0c;界面介绍 这个备忘录的界面效果如下&#xff1a; 可以实现任务的增删&#xff0c;并且在任务被勾选后会被放到已完成的下面。 示例&#xff1a; &#xff08;1&#xff09;&#xff0c;增加一个任务 &#xff08;2&#xff09;&#xff0c;勾选任务 &#xff…

影视cms泛目录用什么程序?苹果cms二次开发泛目录插件

影视CMS泛目录一般使用的程序有很多种&#xff0c;&#xff08;maccmscn&#xff09;以下是其中几种常见的程序&#xff1a; WordPress&#xff1a;WordPress是一个非常流行的开源内容管理系统&#xff0c;可以通过安装一些插件来实现影视CMS泛目录功能。其中&#xff0c;一款常…

Linux中的进程间通信之共享内存

共享内存 共享内存示意图 共享内存数据结构 struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kerne…

招联2025校招内推倒计时

【投递方式】 直接扫下方二维码&#xff0c;或点击内推官网https://wecruit.hotjob.cn/SU61025e262f9d247b98e0a2c2/mc/position/campus&#xff0c;使用内推码 igcefb 投递&#xff09; 【招聘岗位】 后台开发 前端开发 数据开发 数据运营 算法开发 技术运维 软件测试 产品策…

制作离线版Koczkatamas工具包

一、下载源码 从https://github.com/koczkatamas/koczkatamas.github.io下载koczkatamas.github.io-master.zip 二、解压 $ unzip koczkatamas.github.io-master.zip三、运行index.html 可以看到输入一个字符后&#xff0c;下面的各种编码都没有显示&#xff0c;则表示运行…

力扣刷题 | 两数之和

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 给定一个整数数组 nums 和…

UOM无人机空域快速申请技术详解

UOM无人机空域快速申请技术详解主要包括以下几个步骤&#xff1a; 一、准备阶段 1. 实名登记&#xff1a;首先&#xff0c;您需要在相应的民航部门进行无人机的实名登记&#xff0c;这是合法飞行的前提。 2. 了解规定&#xff1a;熟悉并遵守当地关于无人机飞行的法律法规&am…

【微服务】初识(day1)

基础概念 集群 集群是将一个系统完整的部署到多个服务器&#xff0c;每个服务器提供系统的所有服务&#xff0c;多个服务器可以通过负载均衡完成任务&#xff0c;每个服务器都可以称为集群的节点。 分布式 分布式是将一个系统拆分为多个子系统&#xff0c;多个子系统部署在…

YOLO--前置基础词-学习总结

RFBNet是什么意思 RFBNet 是一种用于目标检测的深度学习网络&#xff0c;它的名字来源于 "Receptive Field Block Network"&#xff08;感受野块网络&#xff09;。简单来说&#xff0c;RFBNet 是一种可以让计算机更好地“看”图像中不同大小的物体的方法。 在图像处…

【重学 MySQL】五十四、整型数据类型

【重学 MySQL】五十四、整型数据类型 整型类型TINYINTSMALLINTMEDIUMINTINT&#xff08;或INTEGER&#xff09;BIGINT 可选属性UNSIGNEDZEROFILL显示宽度&#xff08;M&#xff09;AUTO_INCREMENT注意事项 适合场景TINYINTSMALLINTMEDIUMINTINT&#xff08;或INTEGER&#xff0…

tftp传文件被服务器拒绝进入tftp: server error: (768) Access to staonline.pcap denied

环境&#xff1a;测试一个ac下挂ap&#xff0c;ap下的抓包文件传出时&#xff0c;出现问题&#xff1a; ac的wan口ip是192.168.186.167/24&#xff0c;gw是192.168.186.1&#xff0c;下挂ap的ip是192.168.202.199/24&#xff0c;ac上开子接口192.168.202.1/24&#xff0c;ac上开…

C++ | Leetcode C++题解之第456题132模式

题目&#xff1a; 题解&#xff1a; class Solution { public:bool find132pattern(vector<int>& nums) {int n nums.size();vector<int> candidate_i {nums[0]};vector<int> candidate_j {nums[0]};for (int k 1; k < n; k) {auto it_i upper_…

微服务获取用户信息和OpenFeign传递用户

问题一&#xff1a; 网关已经完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时&#xff0c;微服务又该如何获取用户身份呢&#xff1f; 由于网关发送请求到微服务依然采用的是Http请求&#xff0c;因此我们可以将用户信息以请求头的方式传递到下游微服务…

PC端微信小程序如何调试?

向往常一样运行开微信小程序开发者工具 如果只弹出pc端小程序&#xff0c;没有出现调试的界面&#xff1a;点击胶囊按钮的三个…选择重新进入小程序 即可依次展开相应的功能调试&#xff0c;改完代码没反应再刷新看看&#xff0c;再没反应就再次重新点击编译并自动调试。

论文 | Model-tuning Via Prompts Makes NLP Models Adversarially Robust

这篇论文研究了使用提示 (Prompting) 方法微调预训练语言模型&#xff0c;以提高其在对抗样本攻击下的鲁棒性。论文的主要贡献如下&#xff1a; 1.MVP 比 MLP-FT 更鲁棒&#xff1a; 论文比较了 MVP (Model-tuning Via Prompts) 和传统的 MLP-FT (Fine-tuning with an MLP head…

C++模版SFIANE应用踩的一个小坑

一天一个C大佬同事&#xff0c;突然截图过来一段代码&#xff1a;这写的啥呀&#xff0c;啰里吧嗦的&#xff0c;这个构造函数模板参数T1感觉是多余的呀 template<class T> class TestClass { public:TestClass(){}//函数1template<class T1 T, std::enable_if_t<…
最新文章