【二十八】【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_WINDOWSWidgetBase::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_cast
对message
进行类型转换,将其转换为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_NX
和int_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);
:
如果鼠标位于子控件上,调用基类 QWidget
的 nativeEvent
方法进行默认处理,并立即返回。这表示当前的自定义 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
为相应的命中测试值(如 HTLEFT
、HTTOPRIGHT
、HTBOTTOM
等)。这些值用于指示窗口区域的性质,从而实现对窗口调整大小、移动等操作的处理。
MainWidget.h
#pragma once#include <QtWidgets/QMainWindow>
#include "ui_MainWidget.h"
#include "WidgetBase.h"class MainWidget : public WidgetBase
{Q_OBJECTpublic: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
表明 MainWidget
是 WidgetBase
的派生类,具备 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_WINDOWSTapTitle::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_pAddBtn
、m_pEmptyWidget
、m_pUserBtn
、m_pMinBtn
、m_pMaxBtn
和 m_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_pMinBtn
、m_pMaxBtn
、m_pCloseBtn
)也连接到相同的槽函数,所以在槽函数内部通过逻辑判断具体是哪个按钮触发了信号,并执行相应的操作。
- 该语句使用 Qt 的信号-槽机制,将
-
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_pMaxBtn
的clicked()
信号。这样就实现了鼠标双击标题栏与点击最大化按钮的等效效果。通常情况下,双击标题栏会触发窗口在最大化和还原状态之间切换,此实现通过模拟按钮点击来完成这一交互。
- 该语句模拟了
自定义函数实现设置空白widget宽度
-
void TapTitle::setEmptyWidgetWidth(int w)
:- 这是
TapTitle
类的成员函数,用于设置标题栏中m_pEmptyWidget
的宽度。该函数接受一个整数参数w
,表示所需的宽度值。
- 这是
-
m_pEmptyWidget->setMinimumWidth(w);
:- 调用了
m_pEmptyWidget
的setMinimumWidth
函数,将其最小宽度设置为传入的参数w
。setMinimumWidth
是QWidget
类的成员函数,它指定了控件的最小宽度,确保控件不会被压缩到比这个宽度更小的尺寸。此操作允许动态调整标题栏中占位控件的大小,以适应不同的布局需求。
- 调用了
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);
}
-
this->setContextMenuPolicy(Qt::CustomContextMenu);
- 设置选项卡控件的上下文菜单策略为
Qt::CustomContextMenu
,允许自定义右键菜单的行为。此策略指示控件在接收到右键单击事件时,不显示默认的上下文菜单,而是发射customContextMenuRequested
信号,以便开发者自行实现自定义菜单。
- 设置选项卡控件的上下文菜单策略为
-
m_pTabTitle = new TapTitle(this);
- 创建一个
TapTitle
对象作为自定义标题栏,并将TabBrowser
(this
)设为其父对象。自定义标题栏将用于取代默认的选项卡栏,实现更丰富的交互功能。
- 创建一个
-
creatTabMenu();
- 调用
creatTabMenu
方法,用于创建选项卡的上下文菜单。此方法通常用于定义右键菜单的各项内容和行为,为选项卡的交互提供更多的操作选项。
- 调用
-
connect(this, &QTabWidget::customContextMenuRequested, this, &TabBrowser::onMenuShow);
- 使用 Qt 的信号-槽机制,将
QTabWidget
的customContextMenuRequested
信号与TabBrowser
的槽函数onMenuShow
关联。customContextMenuRequested
信号在用户右键单击选项卡时触发,调用onMenuShow
槽函数以显示自定义的上下文菜单。这种机制实现了选项卡的自定义右键菜单功能。
- 使用 Qt 的信号-槽机制,将
-
this->setCornerWidget(m_pTabTitle, Qt::TopRightCorner);
- 使用
setCornerWidget
方法,将自定义的TapTitle
设置为QTabWidget
的右上角小部件(Qt::TopRightCorner
)。这为选项卡控件提供了一个可自定义的标题栏区域,可以在该区域添加按钮、文本或其他控件,以增强选项卡的功能和外观。
- 使用
-
connect(m_pTabTitle, &TapTitle::sig_close, this, &TabBrowser::sig_close);
- 将自定义标题栏(
m_pTabTitle
)的sig_close
信号与TabBrowser
的sig_close
信号进行连接。这一操作允许标题栏触发关闭操作,并将此操作传递给TabBrowser
,从而实现自定义标题栏与选项卡控件之间的交互。
- 将自定义标题栏(
-
connect(m_pTabTitle, &TapTitle::sig_addtab, this, &TabBrowser::on_newTab);
- 将
TapTitle
的sig_addtab
信号与TabBrowser
的on_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
,用于指示当前标题栏的状态。此处判断是否为 NULL
或 NORMAL
,以确定选项卡的宽度计算方式。
-
for (int i = 0; i < tabs; i++) { ... }
:
当flag
为NULL
或NORMAL
时,遍历所有选项卡,调用tabBar()->tabRect(i).width()
获取每个选项卡的宽度并累加到tableWidth
中。 -
for (int i = 0; i < tabs - 1; i++) { ... }
:
如果flag
不为NULL
或NORMAL
,则遍历所有选项卡,但忽略最后一个。这样可以调整选项卡布局,以满足某些特殊情况下的需求。
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);
:- 调用基类
QTabWidget
的resizeEvent
方法,执行其默认的调整大小操作。这样确保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::number
将Ncount
转换为字符串形式,以便生成新选项卡的标题。QString
是 Qt 提供的字符串类,用于文本处理。将数字转换为字符串,便于创建具有动态编号的选项卡标题。
- 使用
-
title = "Page" + title;
:- 将字符串
"Page"
与title
拼接,生成新选项卡的完整标题,例如 “Page1”、“Page2” 等。此操作为新添加的选项卡提供了唯一且有意义的标识。
- 将字符串
-
this->insertTab(Ncount, new QWidget, title);
:- 调用
insertTab
方法在指定位置插入一个新的选项卡。insertTab
是QTabWidget
的成员函数,接受三个参数:插入位置(Ncount
)、新选项卡的内容(new QWidget
)和选项卡标题(title
)。此操作在选项卡栏中添加一个新的空白选项卡,并将其放置在当前选项卡总数的位置,使新选项卡始终添加在末尾。
- 调用
-
if (!tabsClosable()) { setTabsClosable(true); }
:- 调用
tabsClosable()
检查选项卡是否可关闭。如果不可关闭,则调用setTabsClosable(true)
将其设置为可关闭。setTabsClosable
是QTabWidget
的成员函数,用于显示或隐藏选项卡上的关闭按钮。此逻辑确保在新选项卡被添加后,用户可以关闭选项卡。
- 调用
-
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
,标题为"稻壳"
。addTab
是QTabWidget
的成员函数,用于向选项卡控件中添加新的页面。此步骤确保TabBrowser
在初始化时具有至少一个选项卡。
- 使用
-
this->setUsesScrollButtons(true);
:- 启用选项卡栏的滚动按钮。当选项卡数量超过显示区域时,滚动按钮可以用于查看所有选项卡。
setUsesScrollButtons
是QTabWidget
的成员函数,该设置增强了选项卡栏的可用性,确保在选项卡数量较多时仍能提供良好的导航体验。
- 启用选项卡栏的滚动按钮。当选项卡数量超过显示区域时,滚动按钮可以用于查看所有选项卡。
-
this->setTabsClosable(true);
:- 设置选项卡可关闭,在每个选项卡上显示关闭按钮。
setTabsClosable
是QTabWidget
的成员函数,允许用户通过点击关闭按钮来移除选项卡。这为选项卡的管理提供了更灵活的操作方式。
- 设置选项卡可关闭,在每个选项卡上显示关闭按钮。
-
this->setMovable(true);
:- 使选项卡可拖动,以调整其在选项卡栏中的顺序。
setMovable
是QTabWidget
的成员函数,启用该属性后,用户可以通过拖拽选项卡在不同位置之间移动,提供了更灵活的界面布局。
- 使选项卡可拖动,以调整其在选项卡栏中的顺序。
-
setTabBarFlag(NORMAL);
:- 调用自定义的
setTabBarFlag
方法,将选项卡栏的状态设置为NORMAL
。该方法通常用于调整选项卡栏的布局和空白区域,并应用适当的样式,以符合选项卡栏的初始状态。
- 调用自定义的
-
this->setStyleSheet(qss0);
:- 应用样式表
qss0
,用于自定义选项卡栏的外观。通过调用setStyleSheet
,可以调整选项卡栏的视觉样式,例如颜色、字体、边距等,使其符合应用的界面设计。
- 应用样式表
-
connect(this, &QTabWidget::tabCloseRequested, this, &TabBrowser::on_closeTab);
:- 使用 Qt 的信号-槽机制,将
QTabWidget
的tabCloseRequested
信号连接到TabBrowser
的槽函数on_closeTab
。当用户点击选项卡上的关闭按钮时,tabCloseRequested
信号会被触发,并传递要关闭的选项卡的索引给on_closeTab
,从而执行选项卡关闭的逻辑。
- 使用 Qt 的信号-槽机制,将
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)
获取鼠标点击位置所在的选项卡索引。tabAt
是QTabBar
类的成员函数,接受局部坐标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 像素。这样,在选项卡数量较多超出可视区域时,滑动条可以显示出来,允许用户通过滚动按钮查看所有选项卡。这种设置确保了选项卡栏在内容过多的情况下仍然具有良好的可用性和导航性。
- 通过设置
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!
相关文章:

【二十八】【QT开发应用】模拟WPS Tab
WidgetBase 类旨在实现窗口的可调整大小功能,使用户能够手动改变窗口的尺寸。该类通过以下机制实现窗口缩放效果:当鼠标移动至窗口边缘时,鼠标指针样式会动态改变以指示可调整大小的方向。用户在边缘区域按下鼠标左键后,可以通过拖…...

PyQt入门指南四 事件处理机制详解
1. 事件处理概述 在PyQt中,事件处理是实现交互性的关键部分。事件可以是用户的操作(如点击按钮、键盘输入),也可以是系统的通知(如窗口最小化、定时器超时)。PyQt使用信号(Signals)…...

【24最新亲试】ubuntu下载go最新版本
系列综述: 💞目的:本系列是个人整理为了工具配置的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于Ubuntu 升级 golang 版本完美步骤进行的,每个知识点的修…...

InnoDB 事务模型
文章目录 InnoDB 事务模型事务ACID特性事务隔离级别 事务操作事务并发问题事务数据读写类型Consistent Nonlocking Reads 快照读Locking Reads 加锁读 MVCC 并发控制实现原理InnoDB 隐藏列Read ViewUndo log实现过程 MVCC与隔离级别MVCC和辅助索引 幻读可重复读MVCC会出现幻读的…...

STM32 Hal库SDIO在FATFS使用下的函数调用关系
STM32 Hal库SDIO在FATFS使用下的函数调用关系 本文并不将FATFS的相关接口操作,而是将HAL在使用FATFS通过SDIO外设管理SD卡时,内部函数的调用逻辑,有助于当我们使用CUBEMX生成FATFS读取SD卡的代码时无法运行时Debug。本文也会说明一些可能出现…...

网络基础知识笔记(五)接口管理
接口管理 1. 物理层的功能 物理层要解决的三个问题: 1-信号: 模拟信号,数字信号(一组有规律变化的电流脉冲) 2-传输介质: 同轴电缆,双绞线(电信号,电口),光纤(光信号,光口),(空气)电磁波(WiFi,…...

网站集群批量管理-密钥认证与Ansible模块
一、集群批量管理-密钥认证 1、概述 管理更加轻松:两个节点,通过密钥形式进行访问,不需要输入密码,仅支持单向. 服务要求(应用场景): 一些服务在使用前要求我们做秘钥认证.手动写批量管理脚本. 名字: 密钥认证,免密码登录,双机互信. 2、原理 税钥对…...

TCP四次挥手过程详解
TCP四次挥手全过程 有几点需要澄清: 1.首先,tcp四次挥手只有主动和被动方之分,没有客户端和服务端的概念 2.其次,发送报文段是tcp协议栈的行为,用户态调用close会陷入到内核态 3.再者,图中的情况前提是双…...

在 MySQL 中处理和优化大型报告查询经验分享
在 MySQL 数据库的使用过程中,我们经常会遇到需要生成大型报告的情况,这些查询可能涉及大量的数据和复杂的计算,对数据库的性能提出了很高的要求。 一、问题背景 大型报告查询通常具有以下特点: 数据量大:涉及大量的…...

数字图像处理:空间域滤波
1.数字图像处理:空间域滤波 1.1 滤波器核(相关核)与卷积 图像上的邻域计算 线性空间滤波的原理 滤波器核(相关核)是如何得到的? 空间域的卷积 卷积:滤波器核与window中的对应值相乘后所有…...

【easypoi 一对多导入解决方案】
easypoi 一对多导入解决方案 1.需求2.复现问题2.1校验时获取不到一对多中多的完整数据2.2控制台报错 Cannot add merged region B5:B7 to sheet because it overlaps with an existing merged region (B3:B5). 3.如何解决第二个问题处理: Cannot add merged region …...

DDOS攻击会对网站服务器造成哪些影响?
DDOS攻击作为日常生活正比较常见的网络攻击类型,可以让多台计算机在同一时间内遭受到攻击,下面小编就带领大家一起来了解一下DDOS攻击会对网站服务器造成哪些影响吧! 首先DDOS攻击在进行攻击的过程中,可以对源IP地址进行伪造&…...

linux基础指令的认识
在正式学习linux前,可以简单认识一下linux与win的区别 win:是图形界面,用户操作更简单;在刚开始win也是黑屏终端 指令操作,图形界面就是历史发展的结果。Linux:也存在图形界面比如desktop OS;但…...

html5 + css3(下)
目录 CSS基础基础认识体验cssCSS引入方式 基础选择器选择器-标签选择器-类选择器-id选择器-通配符 字体和文本样式1.1 字体大小1.2 字体粗细1.3 字体样式(是否倾斜)1.4 常见字体系列(了解)1.5 字体系列拓展-层叠性font复合属性文本…...

828华为云征文|部署个人文档管理系统 Docspell
828华为云征文|部署个人文档管理系统 Docspell 一、Flexus云服务器X实例介绍二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置2.4 Docker 环境搭建 三、Flexus云服务器X实例部署 Docspell3.1 Docspell 介绍3.2 Docspell 部署3.3 Docspell 使用…...

【深度学习】—激活函数、ReLU 函数、 Sigmoid 函数、Tanh 函数
【深度学习】—激活函数、ReLU 函数、 Sigmoid 函数、Tanh 函数 4.1.2 激活函数ReLU 函数参数化 ReLU Sigmoid 函数背景绘制 sigmoid 函数Sigmoid 函数的导数 Tanh 函数Tanh 函数的导数总结 4.1.2 激活函数 激活函数(activation function)用于计算加权和…...

对于基础汇编的趣味认识
汇编语言 机器指令 机器语言是机器指令的集合 机器指令展开来讲就是一台机器可以正确执行的命令 电子计算机的机器指令是一列二进制数字 (计算机将其转变为一列高低电平,使得计算机的电子器件受到驱动,进行运算 寄存器:微处理器…...

网络基础知识笔记(一)
什么是计算机网络 1.计算机网络发展的第一个阶段:(60年代) 标志性事件:ARPANET 关键技术:分组交换 计算机网络发展的第二个阶段:(70-80年代) 标志性事件:NSFNET 关键技术:TCP/IP 计算机网络发展的第三个阶段ÿ…...

fatal: urdf 中的 CRLF 将被 LF 替换
git add relaxed_ik_ros2 fatal: relaxed_ik_ros2/relaxed_ik_core/configs/urdfs/mobile_spot_arm.urdf 中的 CRLF 将被 LF 替换 这个错误信息表示 Git 在处理文件 mobile_spot_arm.urdf 时发现它使用了 CRLF(回车换行符,常见于 Windows 系统࿰…...

构建electron项目
1. 使用electron-vite构建工具 官网链接 安装构建工具 pnpm i electron-vite -g创建electron-vite项目 pnpm create quick-start/electron安装所有依赖 pnpm i其他 pnpm -D add sass scss1. 启动项目 2. 配置 package.json "dev": "electron-vite dev --…...

Stable Diffusion绘画 | 插件-Deforum:动态视频生成(中篇)
本篇文章重点讲解参数最多的 关键帧 模块。 「动画模式」选择「3D」: 下方「运动」Tab 会有一系列参数: 以下4个参数,只有「动画模式」选择「2D」才会生效,可忽略: 运动 平移 X 让镜头左右移动: 大于0&a…...

STM32中断——外部中断
目录 一、概述 二、外部中断(Extern Interrupt简称EXTI) 三、实例-对射式红外传感器 1、配置中断: 2 、完整代码 一、概述 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当…...

LeetCode78 子集
题目: 给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的 子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[…...

《python语言程序设计》2018版第8章19题几何Rectangle2D类(下)-头疼的几何和数学
希望这个下集里能有完整的代码 一、containsPoint实现 先从网上找一下Statement expected, found Py:DEDENTTAB还是空格呢??小小总结如何拆分矩形的四个点呢.我们来小小的测试一下这个函数结果出在哪里呢???修改完成variable in function should be lowercase 函数变量应该…...

【C++】入门基础介绍(上)C++的发展历史与命名空间
文章目录 1. 前言2. C发展历史2. 1 C版本更新特性一览2. 2 关于C23的一个小故事: 3. C的重要性3. 1 编程语言排行榜3. 2 C在工作领域中的应用 4. C学习建议和书籍推荐4. 1 C学习难度4. 2 学习书籍推荐 5. C的第一个程序6. 命名空间6. 1 namespace的价值6. 2 namespace的定义6. …...

dll动态库加载失败导致程序启动报错以及dll库加载失败的常见原因分析与总结
目录 1、问题说明 2、dll库的隐式加载与动态加载 2.1、dll库的隐式加载 2.2、dll库的显式加载 3、使用Process Explorer查看进程加载的dll库信息以及动态加载的dll库有没有加载成功 3.1、使用Process Explorer查看进程加载的dll库信息 3.2、使用Process Explorer查看动态…...

SAP MM学习笔记 - 豆知识10 - OMSY 初期化会计期间,ABAP调用MMPV/MMRV来批量更新会计期间(TODO)
之前用MMRV,MMPV来一次一个月来修改会计期间。 如果是老的测试机,可能是10几年前的,一次1个月,更新到当前期间,搞个100多次,手都抖。 SAP MM学习笔记 - 错误 M7053 - Posting only possible in periods 2…...

Pytorch实现RNN实验
一、实验要求 用 Pytorch 模块的 RNN 实现生成唐诗。要求给定一个字能够生成一首唐诗。 二、实验目的 理解循环神经网络(RNN)的基本原理:通过构建一个基于RNN的诗歌生成模型,学会RNN是如何处理序列数据的,以及如何在…...

四、Drf认证组件
四、Drf认证组件 4.1 快速使用 from django.shortcuts import render,HttpResponse from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.authentication import BaseAuthentication from rest_framework.exception…...

C++:静态成员
静态成员涉及到的关键字尾static 静态成员变量要在类外初始化 去掉static关键字类型类名::变量名 静态成员变量不属于任何对象 所有对象共享一份 静态成员可以不通过对象直接访问 类名::成员名 静态成员依旧受访问修饰符的约束 …...