Qt高级——Qt自定义标题栏(qt 标题栏添加按钮)
QWidget及其子类窗体组件的标题栏受操作系统的控制,即标题栏的界面风格与操作系统的主题风格相同,工程实践中需要开发者自行定一,达到美化应用程序界面的目的。
一、Qt自定义标题栏实现
1、自定义标题栏的功能
自定义标题栏需要完成功能如下:
(1)自定义标题栏需要包含最小化按钮、最大化按钮、关闭按钮、标题标签、图标标签等图形元素。
(2)标题栏的拖拽。
(3)鼠标双击标题栏实现窗体的最大化、最小化。
2、自定义标题栏的界面布局
自定义标题栏的界面布局如下:
3、标题栏拖拽功能的实现
窗体的拖拽平移过程如下图:
当鼠标在窗体的标题栏按下并移动时,窗体会按照鼠标移动的轨迹进行平移。因此,窗体每次移动都是在当前位置按照鼠标移动的矢量进行移动。标题栏拖拽功能的实现需要实现mousePressEvent、mouseMoveEvent、mouseReleaseEvent三个事件处理函数。
MouseEvent中的globalPos()函数返回的是相对屏幕的位置坐标,而pos()则是返回鼠标在当前控件(即捕获该鼠标事件的控件)中的位置。
QWidget窗体的geometry().topLeft()则返回的是当前窗体的左上角在屏幕中的位置。 startPos = event->globalPos();// 鼠标的全局初始位置,按下时记住 curWindowPos = geometry().topleft();// 窗体的全部位置,移动时 endPos = event->globalPos();// 鼠标按下发生移动之后的位置,移动时 move(curWindowPos+(startPos-endPos));// 根据矢量移动方向是初始位置减去末位置,移动时 startPos = endPos;// 将初始位置记为上次末位置,然后执行直到释放拖拽,移动时实现代码如下:
void TitleBar::mousePressEvent(QMouseEvent *event) { // 鼠标左键按下事件 if (event->button() == Qt::LeftButton) { // 记录鼠标左键状态 m_leftButtonPressed = true; //记录鼠标在屏幕中的位置 m_start = event->globalPos(); } } void TitleBar::mouseMoveEvent(QMouseEvent *event) { // 持续按住才做对应事件 if(m_leftButtonPressed) { //将父窗体移动到父窗体原来的位置加上鼠标移动的位置:event->globalPos()-m_start parentWidget()->move(parentWidget()->geometry().topLeft() + event->globalPos() - m_start); //将鼠标在屏幕中的位置替换为新的位置 m_start = event->globalPos(); } } void TitleBar::mouseReleaseEvent(QMouseEvent *event) { // 鼠标左键释放 if (event->button() == Qt::LeftButton) { // 记录鼠标状态 m_leftButtonPressed = false; } }4、标题栏双击实现最大化、最小化
鼠标双击事件处理函数mouseDoubleClickEvent实现如下:
void TitleBar::mouseDoubleClickEvent(QMouseEvent *event) { m_maximizeButton->click(); }最大化、最小化、关闭按钮的槽函数如下:
void TitleBar::onClicked() { QPushButton *pButton = qobject_cast<QPushButton *>(sender()); QWidget *pWindow = this->window(); if (pWindow->isTopLevel()) { if (pButton == m_minimizeButton) { pWindow->showMinimized(); } else if (pButton == m_maximizeButton) { pWindow->isMaximized() ? pWindow->showNormal() : pWindow->showMaximized(); } else if (pButton == m_closeButton) { pWindow->close(); } } }二、Qt自定义窗体基类示例
1、自定义窗体基类的功能
自定义窗体基类的功能如下:
(1)自定义标题栏。
(2)增加内容组件,内容组件内部的界面布局完全由具体的用户决定。
2、自定义窗体基类的实现
TitleBar.h文件:
#ifndef TITLEBAR_H #define TITLEBAR_H #include <QWidget> #include <QPushButton> #include <QLabel> #include <QHBoxLayout> #include <QEvent> #include <QMouseEvent> #include <QApplication> #include <QPoint> #include <QPixmap> #include <QString> /** * @brief 标题栏界面组件 * @author */ class TitleBar : public QWidget { Q_OBJECT public: explicit TitleBar(QWidget *parent = NULL); /** * @brief 设置标题栏标题 * @param title,参数,设置的标题 */ void setWindowTitle(const QString& title); /** * @brief 设置标题栏的图标 * @param iconPath,参数,图标的路径 */ void SetTitleBarIcon(const QString& iconPath); protected: /** * @brief 鼠标双击事件处理函数 * @param event,参数,事件 * @note 双击标题栏进行界面的最大化/还原 */ virtual void mouseDoubleClickEvent(QMouseEvent *event); /** * @brief 鼠标按下事件处理函数 * @param event,参数,事件 * @note 按下鼠标左键 */ virtual void mousePressEvent(QMouseEvent *event); /** * @brief 鼠标移动事件处理函数 * @param event,参数,事件 * @note 移动鼠标 */ virtual void mouseMoveEvent(QMouseEvent *event); /** * @brief 鼠标释放事件处理函数 * @param event,参数,事件 * @note 释放鼠标 */ virtual void mouseReleaseEvent(QMouseEvent *event); /** * @brief 事件过滤处理器 * @param obj,参数 * @param event,参数,事件 * @return 成功返回true,失败返回false * @note 设置标题、图标 */ virtual bool eventFilter(QObject *obj, QEvent *event); /** * @brief 最大化/还原 */ void updateMaximize(); protected slots: /** * @brief 最小化、最大化/还原、关闭按钮点击时响应的槽函数 */ void onClicked(); private: QLabel* m_iconLabel; QLabel* m_titleLabel; QPushButton* m_minimizeButton; QPushButton* m_maximizeButton; QPushButton* m_closeButton; QPoint m_start;//起始点 QPoint m_end;//结束点 bool m_leftButtonPressed;//鼠标左键按下标记 }; #endif // TITLEBAR_HTitleBar.cpp文件:
#include "TitleBar.h" TitleBar::TitleBar(QWidget *parent) : QWidget(parent) { setFixedHeight(30); setWindowFlags(Qt::FramelessWindowHint); m_iconLabel = new QLabel(this); m_iconLabel->setFixedSize(20, 20); m_iconLabel->setScaledContents(true); m_titleLabel = new QLabel(this); m_titleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); m_minimizeButton = new QPushButton(this); m_minimizeButton->setFixedSize(27, 22); m_minimizeButton->setObjectName("minimizeButton"); m_maximizeButton = new QPushButton(this); m_maximizeButton->setFixedSize(27, 22); m_maximizeButton->setObjectName("maximizeButton"); m_closeButton = new QPushButton(this); m_closeButton->setFixedSize(27, 22); m_closeButton->setObjectName("closeButton"); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_iconLabel); layout->addStretch(1); layout->addWidget(m_titleLabel); layout->addStretch(1); layout->addWidget(m_minimizeButton); layout->addWidget(m_maximizeButton); layout->addWidget(m_closeButton); setLayout(layout); setProperty("titleBar", true); setObjectName("titleBar"); connect(m_minimizeButton, SIGNAL(clicked(bool)), this, SLOT(onClicked())); connect(m_maximizeButton, SIGNAL(clicked(bool)), this, SLOT(onClicked())); connect(m_closeButton, SIGNAL(clicked(bool)), this, SLOT(onClicked())); } void TitleBar::setWindowTitle(const QString &title) { m_titleLabel->setAlignment(Qt::AlignCenter); m_titleLabel->setText(title); } void TitleBar::SetTitleBarIcon(const QString &iconPath) { QPixmap map(iconPath); m_iconLabel->setPixmap(map); } void TitleBar::mouseDoubleClickEvent(QMouseEvent *event) { m_maximizeButton->click(); } void TitleBar::mousePressEvent(QMouseEvent *event) { // 鼠标左键按下事件 if (event->button() == Qt::LeftButton) { // 记录鼠标左键状态 m_leftButtonPressed = true; //记录鼠标在屏幕中的位置 m_start = event->globalPos(); } } void TitleBar::mouseMoveEvent(QMouseEvent *event) { // 持续按住才做对应事件 if(m_leftButtonPressed) { //将父窗体移动到父窗体原来的位置加上鼠标移动的位置:event->globalPos()-m_start parentWidget()->move(parentWidget()->geometry().topLeft() + event->globalPos() - m_start); //将鼠标在屏幕中的位置替换为新的位置 m_start = event->globalPos(); } } void TitleBar::mouseReleaseEvent(QMouseEvent *event) { // 鼠标左键释放 if (event->button() == Qt::LeftButton) { // 记录鼠标状态 m_leftButtonPressed = false; } } bool TitleBar::eventFilter(QObject *obj, QEvent *event) { switch(event->type()) { //设置标题 case QEvent::WindowTitleChange: { QWidget *pWidget = qobject_cast<QWidget *>(obj); if (pWidget) { m_titleLabel->setText(pWidget->windowTitle()); return true; } } //设置图标 case QEvent::WindowIconChange: { QWidget *pWidget = qobject_cast<QWidget *>(obj); if (pWidget) { QIcon icon = pWidget->windowIcon(); m_iconLabel->setPixmap(icon.pixmap(m_iconLabel->size())); return true; } } // 窗口状态变化、窗口大小变化 case QEvent::WindowStateChange: case QEvent::Resize: updateMaximize(); return true; } return QWidget::eventFilter(obj, event); } void TitleBar::updateMaximize() { QWidget *pWindow = this->window(); if (pWindow->isTopLevel()) { bool bMaximize = pWindow->isMaximized(); if (bMaximize) { m_maximizeButton->setToolTip(tr("Restore")); m_maximizeButton->setProperty("maximizeProperty", "restore"); } else { m_maximizeButton->setProperty("maximizeProperty", "maximize"); m_maximizeButton->setToolTip(tr("Maximize")); } m_maximizeButton->setStyle(QApplication::style()); } } void TitleBar::onClicked() { QPushButton *pButton = qobject_cast<QPushButton *>(sender()); QWidget *pWindow = this->window(); if (pWindow->isTopLevel()) { if (pButton == m_minimizeButton) { pWindow->showMinimized(); } else if (pButton == m_maximizeButton) { pWindow->isMaximized() ? pWindow->showNormal() : pWindow->showMaximized(); } else if (pButton == m_closeButton) { pWindow->close(); } } }QWindowBase.h文件:
#ifndef QWINDOWBASE_H #define QWINDOWBASE_H #include <QFrame> #include <QWidget> #include <QVBoxLayout> #include "TitleBar.h" /** * @brief 界面组件基类 * @note QWindowBase界面组件主要用作顶层窗口,对于非顶层窗口的界面组件使用QWidget。 */ class QWindowBase : public QFrame { Q_OBJECT public: QWindowBase(QFrame* parent = NULL); /** * @brief 设置标题 * @param title,输入参数,标题内容 */ void setWindowTitle(const QString& title); /** * @brief 设置标题栏的图标 * @param iconPath,输入参数,图标资源路径 */ void SetTitleBarIcon(const QString& iconPath); /** * @brief 获取内容组件对象指针 * @return 返回QWidget* */ QWidget* contentWidget(); /** * @brief 设置标题栏高度 * @param h,输入参数,标题栏高度 */ void setWindowTitleHeight(int h); private: QWidget* m_contentWidget;//内容组件 TitleBar* m_titleBar;//标题栏 QVBoxLayout* m_layout;//布局管理器 }; #endif // QWINDOWBASE_HQWindowBase.cpp文件:
#include "QWindowBase.h" QWindowBase::QWindowBase(QFrame *parent): QFrame(parent) { setWindowFlags(windowFlags() | Qt::FramelessWindowHint); m_titleBar = new TitleBar(this); m_contentWidget = new QWidget(this); m_contentWidget->setObjectName("Contents"); m_layout = new QVBoxLayout; m_layout->addWidget(m_titleBar); m_layout->addWidget(m_contentWidget); m_layout->setSpacing(0); m_layout->setContentsMargins(0, 0, 0, 0); setLayout(m_layout); } void QWindowBase::setWindowTitle(const QString &title) { m_titleBar->setWindowTitle(title); } void QWindowBase::SetTitleBarIcon(const QString &iconPath) { m_titleBar->SetTitleBarIcon(iconPath); } QWidget *QWindowBase::contentWidget() { return m_contentWidget; } void QWindowBase::setWindowTitleHeight(int h) { m_titleBar->setFixedHeight(h); }CommonHelper.h文件:
#ifndef COMMONHELPER_H #define COMMONHELPER_H #include <QString> #include <QFile> #include <QApplication> #include <QDebug> #include <QColor> #include <QPalette> /** * @brief 通用功能辅助类 */ class CommonHelper { public: /** * @brief 为应用程序设置QSS样式表 * @param filepath,输入参数,QSS文件路径 */ static void setStyleSheet(const QString& filepath) { //加载样式文件 QFile qss(filepath); if(qss.open(QFile::ReadOnly)) { QString stylesheet = QLatin1String(qss.readAll()); QString paletteColor = stylesheet.mid(20, 7); qApp->setPalette(QPalette(QColor(paletteColor))); qApp->setStyleSheet(stylesheet); } } }; #endif // COMMONHELPER_H点击领取Qt学习资料+视频教程~「链接」
main.cpp文件:
#include <QApplication> #include "CommonHelper.h" #include "QWindowBase.h" #include <QPushButton> #include <QVBoxLayout> #include <QHBoxLayout> #include <QTreeView> int main(int argc, char *argv[]) { QApplication a(argc, argv); QWindowBase w; w.setWindowTitle("WidgetBase"); QPushButton* button1 = new QPushButton("OK"); QHBoxLayout* hLayout1 = new QHBoxLayout; hLayout1->addStretch(1); hLayout1->addWidget(button1); QVBoxLayout* layout = new QVBoxLayout; QTreeView* treeView = new QTreeView; layout->addWidget(treeView); layout->addLayout(hLayout1); layout->addStretch(1); w.contentWidget()->setLayout(layout); w.setWindowTitleHeight(40); w.show(); CommonHelper::setStyleSheet("://qss/lightblue.qss"); return a.exec(); }工程文件:
QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = TitleBarDemo TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ TitleBar.cpp \ QWindowBase.cpp HEADERS += \ TitleBar.h \ CommonHelper.h \ QWindowBase.h RESOURCES += \ TitileBarDemo.qrc 工程目录结构:
3、自定义窗体基类结果展示
运行结果:
轻松掌握Shadowrocket订阅添加:从入门到精通的完整指南
在当今数字时代,网络自由已成为许多网民的基本需求。无论是为了获取全球资讯、进行学术研究,还是享受跨国娱乐内容,科学上网工具都扮演着关键角色。作为iOS平台上广受欢迎的代理工具,Shadowrocket以其强大的功能和简洁的界面赢得了大量用户的青睐。本文将为您全面解析Shadowrocket的订阅功能,从基础概念到实操技巧,助您轻松驾驭这款工具。
一、认识Shadowrocket:您的网络自由钥匙
Shadowrocket不仅仅是一个简单的VPN应用,它是iOS系统上一款功能全面的网络代理工具。与同类产品相比,其独特之处在于支持多种协议,包括但不限于Shadowsocks、SOCKS5、HTTP/HTTPS等,能够满足不同用户的多样化需求。
这款应用的界面设计遵循了iOS的简约美学,操作逻辑清晰明了。除了基本的代理功能外,Shadowrocket还提供了流量监控、规则设置等实用功能,让用户能够精准控制每一份网络流量。值得一提的是,其订阅功能大大简化了节点管理流程,使维护代理服务器列表变得前所未有的轻松。
二、订阅机制解析:智能节点管理的核心
在Shadowrocket的生态中,"订阅"是一个核心概念。简单来说,订阅就是通过一个特定链接获取并自动更新代理节点列表的服务。这相当于为您的Shadowrocket安装了一个"自动更新器",省去了手动添加、删除节点的繁琐操作。
订阅的工作原理相当智能:当您添加一个订阅链接后,Shadowrocket会定期(通常每天)向订阅服务器请求最新的节点信息。这意味着您始终拥有最新鲜、最可用的代理资源,无需担心节点失效的问题。这种机制特别适合那些节点经常轮换的高质量付费服务,也方便免费用户及时获取可用的公共节点。
三、手把手教学:订阅添加全流程
第一步:获取可靠的订阅链接
优质的订阅链接是良好体验的基础。您可以通过以下渠道获取:
- 付费VPN服务商提供的专属订阅(推荐首选)
- 技术论坛分享的公共订阅(时效性有限)
- 朋友共享的私人订阅链接
专业建议:选择付费服务不仅能获得更稳定的连接,还能享受专业的技术支持和隐私保护。免费订阅虽然零成本,但往往伴随着速度慢、不稳定甚至安全风险等问题。
第二步:启动Shadowrocket应用
在您的iPhone或iPad上找到那个火箭图标的应用。初次使用时,您可能会看到一个简洁的界面,顶部是连接开关,下方是节点列表(目前为空)。
第三步:添加订阅链接
- 点击右上角的"+"图标(或"配置"选项)
- 选择"添加订阅"
- 在弹出的对话框中粘贴您事先复制的订阅链接
- 为这个订阅起个容易识别的名称(如"美国高速节点")
- 点击"完成"保存设置
操作技巧:长按订阅链接区域可以调出更丰富的编辑选项,包括测试链接有效性等。
第四步:启用并享受
返回主界面,您将看到订阅提供的节点列表已经自动加载。点击心仪的节点,然后拨动顶部的开关,几秒钟内您就能体验到无界浏览的快感了。
四、深度问答:解决您的所有疑惑
1. 为什么我的订阅添加后没有显示节点?
这种情况通常有几个原因:
- 订阅链接已过期(联系服务商更新)
- 网络连接问题(尝试切换WiFi/4G)
- Shadowrocket版本过旧(前往App Store更新)
排查步骤:先检查链接有效性,再尝试手动更新订阅(下拉节点列表),最后考虑重新安装应用。
2. 付费订阅与免费订阅有何本质区别?
从技术角度看,两者的核心差异体现在:
| 对比项 | 付费订阅 | 免费订阅 |
|--------|----------|----------|
| 节点质量 | 高速专线 | 公共拥挤 |
| 更新频率 | 每日多次 | 随机不定 |
| 隐私保护 | 严格日志政策 | 可能存在监控 |
| 技术支持 | 24小时响应 | 无保障 |
用户反馈:90%的长期用户最终会选择付费服务,因为稳定性和安全性确实物有所值。
3. 如何管理多个订阅源?
Shadowrocket支持添加无限数量的订阅,管理方式也很直观:
- 左滑订阅可删除
- 长按可重命名或设置更新频率
- 不同订阅的节点会自动合并显示
高级技巧:为不同地区(如美国、日本)创建独立订阅,方便按需切换。
五、安全警示与最佳实践
在使用Shadowrocket订阅时,请牢记以下安全准则:
1. 切勿使用来源不明的订阅链接(可能包含恶意节点)
2. 定期更换订阅密码(如果服务支持)
3. 敏感操作时关闭"全局模式"(避免不必要的流量经过代理)
4. 关注应用的权限请求(确保不会过度收集数据)
专家建议:配合Surge等防火墙工具使用,可以进一步提升隐私保护级别。
六、未来展望:订阅技术的演进
随着网络环境的不断变化,订阅技术也在持续进化。我们可能很快会看到:
- 基于区块链的去中心化订阅验证
- AI智能节点选择(自动匹配最佳线路)
- 跨平台订阅同步(iOS/Android/PC无缝切换)
这些创新将进一步简化科学上网的体验,同时也对用户的数字素养提出更高要求。
结语:掌握技术,畅游网络
通过本文的系统介绍,相信您已经对Shadowrocket的订阅功能有了全面了解。从获取链接到故障排除,每个环节都蕴含着提升体验的机会。记住,在追求网络自由的同时,也要时刻关注数字安全与隐私保护。
技术本身是中性的,关键在于我们如何使用它。愿Shadowrocket成为您探索数字世界的可靠伙伴,而非仅仅是一个"翻墙工具"。在这个信息爆炸的时代,保持理性判断与独立思考,或许比简单的网络访问更为珍贵。
最后提醒:本文内容仅供技术交流,请确保您的网络使用行为符合当地法律法规。技术可以打开视野,但真正的智慧在于如何善用这些工具创造价值。
版权声明:
作者: freeclashnode
链接: https://www.freeclashnode.com/news/article-2016.htm
来源: FreeClashNode
文章版权归作者所有,未经允许请勿转载。
热门文章
- 12月6日|19.8M/S,V2ray节点/Clash节点/SSR节点/Singbox节点|免费订阅机场|每天更新免费梯子
- 11月22日|20.2M/S,Shadowrocket节点/V2ray节点/Clash节点/Singbox节点|免费订阅机场|每天更新免费梯子
- 12月5日|23M/S,Singbox节点/V2ray节点/Clash节点/SSR节点|免费订阅机场|每天更新免费梯子
- 12月11日|23M/S,Singbox节点/V2ray节点/Clash节点/Shadowrocket节点|免费订阅机场|每天更新免费梯子
- 11月25日|20.3M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费订阅机场|每天更新免费梯子
- 12月9日|20M/S,Singbox节点/V2ray节点/Clash节点/SSR节点|免费订阅机场|每天更新免费梯子
- 12月12日|18.6M/S,Singbox节点/Clash节点/Shadowrocket节点/V2ray节点|免费订阅机场|每天更新免费梯子
- 12月8日|21.6M/S,Singbox节点/SSR节点/V2ray节点/Clash节点|免费订阅机场|每天更新免费梯子
- 11月20日|19.3M/S,Singbox节点/Shadowrocket节点/V2ray节点/Clash节点|免费订阅机场|每天更新免费梯子
- 12月3日|18.2M/S,V2ray节点/Clash节点/Singbox节点/SSR节点|免费订阅机场|每天更新免费梯子
最新文章
- 12月16日|21M/S,SSR节点/Singbox节点/Clash节点/V2ray节点|免费订阅机场|每天更新免费梯子
- 12月15日|20.8M/S,SSR节点/Singbox节点/Clash节点/V2ray节点|免费订阅机场|每天更新免费梯子
- 12月14日|21.5M/S,V2ray节点/Shadowrocket节点/Singbox节点/Clash节点|免费订阅机场|每天更新免费梯子
- 12月13日|18.1M/S,V2ray节点/SSR节点/Clash节点/Singbox节点|免费订阅机场|每天更新免费梯子
- 12月12日|18.6M/S,Singbox节点/Clash节点/Shadowrocket节点/V2ray节点|免费订阅机场|每天更新免费梯子
- 12月11日|23M/S,Singbox节点/V2ray节点/Clash节点/Shadowrocket节点|免费订阅机场|每天更新免费梯子
- 12月10日|19.9M/S,Clash节点/V2ray节点/Singbox节点/SSR节点|免费订阅机场|每天更新免费梯子
- 12月9日|20M/S,Singbox节点/V2ray节点/Clash节点/SSR节点|免费订阅机场|每天更新免费梯子
- 12月8日|21.6M/S,Singbox节点/SSR节点/V2ray节点/Clash节点|免费订阅机场|每天更新免费梯子
- 12月7日|22.8M/S,Shadowrocket节点/Singbox节点/V2ray节点/Clash节点|免费订阅机场|每天更新免费梯子
归档
- 2025-12 28
- 2025-11 55
- 2025-10 56
- 2025-09 55
- 2025-08 49
- 2025-07 31
- 2025-06 30
- 2025-05 31
- 2025-04 31
- 2025-03 383
- 2025-02 360
- 2025-01 403
- 2024-12 403
- 2024-11 390
- 2024-10 403
- 2024-09 388
- 2024-08 402
- 2024-07 424
- 2024-06 446
- 2024-05 184
- 2024-04 33
- 2024-03 32
- 2024-02 29
- 2024-01 50
- 2023-12 53
- 2023-11 32
- 2023-10 32
- 2023-09 3