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_cast
对message
进行类型转换,将其转换为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_NX
和int_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);
:
如果鼠标位于子控件上,调用基类 QWidget
的 nativeEvent
方法进行默认处理,并立即返回。这表示当前的自定义 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
为相应的命中测试值(如 HTLEFT
、HTTOPRIGHT
、HTBOTTOM
等)。这些值用于指示窗口区域的性质,从而实现对窗口调整大小、移动等操作的处理。
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
表明 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
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_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())
获取当前窗口的句柄,确保消息发送到正确的窗口实例。这一操作触发了系统的窗口拖动机制,使用户可以通过鼠标拖动标题栏来移动整个窗口。
控件的信号与槽函数连接
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_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
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);
}
-
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()
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
,用于指示当前标题栏的状态。此处判断是否为 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)
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);
:- 调用基类
QTabWidget
的resizeEvent
方法,执行其默认的调整大小操作。这样确保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::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§
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
,标题为"稻壳"
。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()
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)
获取鼠标点击位置所在的选项卡索引。tabAt
是QTabBar
类的成员函数,接受局部坐标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 像素。这样,在选项卡数量较多超出可视区域时,滑动条可以显示出来,允许用户通过滚动按钮查看所有选项卡。这种设置确保了选项卡栏在内容过多的情况下仍然具有良好的可用性和导航性。
- 通过设置
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!