C++ Qt 学习(三):无边框窗口设计

1. 无边框窗口

在这里插入图片描述

1.1 主窗口实现

  • MainWidget.h
#pragma once#include <QtWidgets/QWidget>
#include "CTitleBar.h"
#include "CFrameLessWidgetBase.h"// 主窗口 MainWidget 继承自无边框窗口公用类 CFrameLessWidgetBase
class MainWidget : public CFrameLessWidgetBase {Q_OBJECTpublic:MainWidget(QWidget *parent = Q_NULLPTR);private slots:void on_closeSlot();private:void initUI();private:CTitleBar* m_pTitleBar = nullptr;
};
  • MainWidget.cpp
#include "MainWidget.h"
#include <QVBoxLayout>
#include <QMessageBox>MainWidget::MainWidget(QWidget *parent) : CFrameLessWidgetBase(parent) {initUI();
}void MainWidget::on_closeSlot() {// 显示一个警告对话框,询问用户是否要退出// 参数:创建对话框的父窗口,对话框标题,对话框内容,对话框按钮组合,默认值QMessageBox::StandardButton _exit = QMessageBox::warning(this, u8"提示", u8"确定要退出吗", QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);if (_exit == QMessageBox::Yes) {close();}
}void MainWidget::initUI() {// 创建标题栏和主窗口m_pTitleBar = new CTitleBar(this);QWidget* w = new QWidget(this);w->setMinimumSize(800, 600);  // 初始化主窗口大小// 将标题栏和主窗口放入垂直布局中QVBoxLayout* pVlay = new QVBoxLayout(this);pVlay->addWidget(m_pTitleBar);pVlay->addWidget(w);pVlay->setContentsMargins(0, 0, 0, 0);setLayout(pVlay);// 将标题栏中的关闭信号与主窗口关闭的槽函数建立连接connect(m_pTitleBar, &CTitleBar::sig_close, this, &MainWidget::on_closeSlot);
}

1.2 标题栏实现

  • CTitleBar.h
#pragma once
#include <QWidget>
#include <QLabel>
#include <QPushButton>class CTitleBar : public QWidget {Q_OBJECTpublic:CTitleBar(QWidget* p = nullptr);~CTitleBar();private:void initUI();private:// 鼠标拖动标题栏事件void mousePressEvent(QMouseEvent* event) override;// 鼠标双击标题栏事件void mouseDoubleClickEvent(QMouseEvent* event) override;private slots:void onClicked();signals:void sig_close();private:QLabel* m_pLogo;QLabel* m_pTitleTextLabel;QPushButton* m_pSetBtn;QPushButton* m_pMinBtn;QPushButton* m_pMaxBtn;QPushButton* m_pCloseBtn;
};
  • CTitleBar.cpp
#include "CTitleBar.h"
#include <QHBoxLayout>// 告诉编译器在链接时将 "user32.lib" 库文件加入到项目中
#pragma comment(lib, "user32.lib")
// 包含使用 Qt 框架与 Windows API 进行交互所需的定义和函数
#include <qt_windows.h>CTitleBar::CTitleBar(QWidget* p): QWidget(p) {// 设置标题栏部件在关闭时自动删除的属性this->setAttribute(Qt::WA_DeleteOnClose);initUI();
}CTitleBar::~CTitleBar() {}void CTitleBar::initUI() {setAttribute(Qt::WA_StyledBackground); // 禁止父窗口影响子窗口样式this->setFixedHeight(32 + 5 * 2);this->setStyleSheet("background-color:rgb(54,54,54)");// 创建 logo 控件m_pLogo = new QLabel(this);m_pLogo->setFixedSize(32, 32);m_pLogo->setStyleSheet("background-image:url(:/LessWidgetPro/resources/titlebar/title_icon.png);border:none");// 创建 标题 控件m_pTitleTextLabel = new QLabel(this);m_pTitleTextLabel->setText(u8"我是标题");m_pTitleTextLabel->setFixedWidth(120);m_pTitleTextLabel->setStyleSheet("QLabel{font-family: Microsoft YaHei; \font-size:18px; \color:#BDC8E2;background-color:rgb(54,54,54);}");// 创建 设置 控件m_pSetBtn = new QPushButton(this);m_pSetBtn->setFixedSize(32, 32);m_pSetBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/set.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/set_hover.svg);border:none;}");// 创建 最小化 控件m_pMinBtn = new QPushButton(this);m_pMinBtn->setFixedSize(32, 32);m_pMinBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/min.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/min_hover.svg);border:none;}");// 创建 最大化 控件m_pMaxBtn = new QPushButton(this);m_pMaxBtn->setFixedSize(32, 32);m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/normal.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/normal_hover.svg);border:none;}");// 创建 关闭 控件m_pCloseBtn = new QPushButton(this);m_pCloseBtn->setFixedSize(32, 32);m_pCloseBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/close.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/close_hover.svg);border:none;}");// 创建水平布局将上述控件放进去QHBoxLayout* pHlay = new QHBoxLayout(this);pHlay->addWidget(m_pLogo);pHlay->addWidget(m_pTitleTextLabel);pHlay->addStretch();pHlay->addWidget(m_pSetBtn);QSpacerItem* pItem1 = new QSpacerItem(20, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);pHlay->addSpacerItem(pItem1); // 弹簧每次使用时得 new 出来,不能重复使用pHlay->addWidget(m_pMinBtn);QSpacerItem* pItem2 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);pHlay->addSpacerItem(pItem2); // 弹簧每次使用时得 new 出来,不能重复使用pHlay->addWidget(m_pMaxBtn);QSpacerItem* pItem3 = new QSpacerItem(18, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);pHlay->addSpacerItem(pItem3); // 弹簧每次使用时得 new 出来,不能重复使用pHlay->addWidget(m_pCloseBtn);pHlay->setContentsMargins(5, 5, 5, 5);connect(m_pMinBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);connect(m_pMaxBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);connect(m_pCloseBtn, &QPushButton::clicked, this, &CTitleBar::onClicked);
}// 处理鼠标按下事件的函数
void CTitleBar::mousePressEvent(QMouseEvent* event) {if (ReleaseCapture()) {QWidget* pWindow = this->window();// 判断该窗口是否是顶级窗口(即主窗口)if (pWindow->isTopLevel()) {// 发送WM_SYSCOMMAND消息,参数为SC_MOVE + HTCAPTION,目的是模拟鼠标拖动窗口SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);}}
}// 当鼠标双击标题栏时,会触发与 m_pMaxBtn 按钮关联的点击事件
void CTitleBar::mouseDoubleClickEvent(QMouseEvent* event) {emit m_pMaxBtn->clicked();
}// 处理标题栏上按钮的点击事件
void CTitleBar::onClicked() {// 使用 sender() 函数获取发送信号的对象指针,并强转为 QPushButton* 类型QPushButton* pButton = qobject_cast<QPushButton*>(sender());QWidget* pWindow = this->window(); // 获取当前窗口的指针if (pButton == m_pMinBtn) {pWindow->showMinimized(); // 将窗口最小化} else if (pButton == m_pMaxBtn) {if (pWindow->isMaximized()) {pWindow->showNormal(); // 如果已经最大化,则先还原窗口m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/normal.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/normal_hover.svg);border:none;}");} else {pWindow->showMaximized(); // 如果未最大化,则将窗口最大化m_pMaxBtn->setStyleSheet("QPushButton{background-image:url(:/LessWidgetPro/resources/titlebar/max.svg);border:none}" \"QPushButton:hover{" \"background-color:rgb(99, 99, 99);" \"background-image:url(:/LessWidgetPro/resources/titlebar/max_hover.svg);border:none;}");}} else if (pButton == m_pCloseBtn) {emit sig_close(); // 通知其他部分执行关闭窗口的操作}
}

1.3 无边框窗口公用类实现

  • CFrameLessWidgetBase.h
#pragma once
#include <QWidget>class CFrameLessWidgetBase : public QWidget {
public:CFrameLessWidgetBase(QWidget* p = nullptr);~CFrameLessWidgetBase() {}protected:// 参数:指定事件类型的标识符,传递原始操作系统事件消息,存储和返回处理结果bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;private:int m_nBorderWidth = 5;
};
  • CFrameLessWidgetBase.cpp
#include "CFrameLessWidgetBase.h"
#include <qt_windows.h>
#include <windows.h>
#include <windowsx.h>#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")CFrameLessWidgetBase::CFrameLessWidgetBase(QWidget* p) :QWidget(p) {this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);// Qt::WA_Hover 表示窗口具有悬停功能,可以在鼠标悬停时触发相应的事件setAttribute(Qt::WA_Hover);
}// 根据鼠标在窗口的位置判断其是否在边框区域,并设置相应的 result 值以实现窗口的拖动或调整大小操作
bool CFrameLessWidgetBase::nativeEvent(const QByteArray& eventType, void* message, long* result) {MSG* param = static_cast<MSG*>(message);switch (param->message) {case WM_NCHITTEST: {// 获取鼠标在窗口上的坐标 (nX 和 nY)int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();// 判断鼠标是否在内部区域if (nX > m_nBorderWidth && nX < this->width() - m_nBorderWidth &&nY > m_nBorderWidth && nY < this->height() - m_nBorderWidth){if (childAt(nX, nY) != nullptr) // 如果在内部区域且有子部件return QWidget::nativeEvent(eventType, message, result);}// 如果鼠标在边界区域,则根据具体位置设置 result 的值,以实现不同的窗口行为if ((nX > 0) && (nX < m_nBorderWidth))// 鼠标在左边界,则 *result 被设置为 HTLEFT,表示拖动窗口左边界*result = HTLEFT;if ((nX > this->width() - m_nBorderWidth) && (nX < this->width()))*result = HTRIGHT;if ((nY > 0) && (nY < m_nBorderWidth))*result = HTTOP;if ((nY > this->height() - m_nBorderWidth) && (nY < this->height()))*result = HTBOTTOM;if ((nX > 0) && (nX < m_nBorderWidth) && (nY > 0)&& (nY < m_nBorderWidth))*result = HTTOPLEFT;if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())&& (nY > 0) && (nY < m_nBorderWidth))*result = HTTOPRIGHT;if ((nX > 0) && (nX < m_nBorderWidth)&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))*result = HTBOTTOMLEFT;if ((nX > this->width() - m_nBorderWidth) && (nX < this->width())&& (nY > this->height() - m_nBorderWidth) && (nY < this->height()))*result = HTBOTTOMRIGHT;return true;}}return false;
}

2. Qt 实现窗口阴影

在这里插入图片描述

  • CLoginDlg.cpp
#include "CLoginDlg.h"
#include "CLoginRealWidget.h"
#include <QGraphicsDropShadowEffect>  // 添加窗口阴影
#include <QVboxLayout>
#include <QMouseEvent>CLoginDlg::CLoginDlg(QWidget *parent) : QDialog(parent) {// 设置主窗口透明this->setAttribute(Qt::WA_TranslucentBackground, true);// 设置主窗口无边框this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);QVBoxLayout* pMainLay = new QVBoxLayout(this);CLoginRealWidget* pRealWidget = new CLoginRealWidget(this);pMainLay->addWidget(pRealWidget);pMainLay->setContentsMargins(30, 30, 30, 30);setLayout(pMainLay);// 给顶层 widget 设置背景颜色,不然看不见,因为底层 widget 已经透明了pRealWidget->setStyleSheet("background-color:rgb(255, 254, 253)");// 创建窗口阴影并设置属性,最后添加给顶层 widgetQGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(this);shadow->setOffset(0, 0);                 // 设置阴影距离shadow->setColor(QColor("#686868"));     // 设置阴影颜色 686868shadow->setBlurRadius(30);               // 设置阴影区域pRealWidget->setGraphicsEffect(shadow);  // 给顶层 QWidget 设置阴影
}CLoginDlg::~CLoginDlg() {}void CLoginDlg::mousePressEvent(QMouseEvent* event) {this->windowPos = this->pos();       // 获得部件当前位置this->mousePos = event->globalPos(); // 获取鼠标当前的全局位置this->dPos = mousePos - windowPos;   // 鼠标按下后,部件所在的新位置相对于按下前位置的偏移量
}void CLoginDlg::mouseMoveEvent(QMouseEvent* event) {// 根据鼠标当前的全局位置和之前保存的偏移量,计算部件应该移动到的新位置,并将部件移动到新位置this->move(event->globalPos() - this->dPos);
}

3. Qt 实现圆角窗口

3.1 方式一:绘制法

在这里插入图片描述

  • MainWidget.h
#pragma once
#include <QtWidgets/QWidget>class MainWidget : public QWidget {Q_OBJECTpublic:MainWidget(QWidget *parent = Q_NULLPTR);private:void paintEvent(QPaintEvent* event) override;
};
  • MainWidget.cpp
#include "MainWidget.h"
#include <QPainter>MainWidget::MainWidget(QWidget *parent) : QWidget(parent) {resize(600, 400);// 设置窗口背景透明setAttribute(Qt::WA_TranslucentBackground);  // 设置窗口无边框setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);
}void MainWidget::paintEvent(QPaintEvent* event) {QPainter painter(this);// 设置抗锯齿渲染方式,使绘制的图形边缘更加平滑painter.setRenderHint(QPainter::Antialiasing);// 设置画刷,使用颜色(R=168, G=68, B=68)作为填充色painter.setBrush(QBrush(QColor(168, 68, 68)));// 设置画笔,将其设置为透明,即不绘制边框painter.setPen(Qt::transparent);// 获取当前窗口部件的矩形区域QRect rect = this->rect();// 绘制一个具有圆角的矩形,圆角 15pxpainter.drawRoundedRect(rect, 15, 15);
}

3.2 方式二:qss(推荐,更灵活)

在这里插入图片描述

  • MainWidget.cpp
#include "MainWidget.h"
#include <QStyleOption>
#include <QPainter>MainWidget::MainWidget(QWidget *parent) : QWidget(parent) {// 设置窗口背景透明setAttribute(Qt::WA_TranslucentBackground);// 设置窗口无边框setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinMaxButtonsHint);// 设置左上、右下圆角,大小 15pxthis->setStyleSheet("QWidget{background-color:#A84444;  \border-top-left-radius:15px;   \border-bottom-right-radius:15px; \}");
}void MainWidget::paintEvent(QPaintEvent*) {QStyleOption opt;    // 用于存储窗口部件的绘制选项opt.init(this);      // 初始化 opt,将其与当前窗口部件相关联,以便获取窗口部件的状态和外观选项QPainter p(this);    // 创建一个QPainter对象,并将其与当前窗口部件相关联,以便于进行绘图操作// 使用 QStyle 对象的 drawPrimitive 方法来绘制窗口部件的外观style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

4. 实现 WPS tab 页面

在这里插入图片描述

4.1 主窗口实现

  • WPSDemo.h
#pragma once#include <QtWidgets/QWidget>
#include "ui_WPSDemo.h"class WPSDemo : public QWidget {Q_OBJECTpublic:WPSDemo(QWidget *parent = Q_NULLPTR);protected:bool nativeEvent(const QByteArray& eventType, void* message, long* result) override;private slots:void on_close();private:Ui::WPSDemoClass ui;int m_BorderWidth = 5; // 表示鼠标位于边框缩放范围的宽度
};
  • WPSDemo.cpp
#include "WPSDemo.h"
#include "tabbrowser.h"
#include <QHBoxLayout>#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <Windows.h>
#include <windowsx.h>
#endif#pragma comment(lib, "user32.lib")
#pragma comment(lib,"dwmapi.lib")WPSDemo::WPSDemo(QWidget *parent) : QWidget(parent) {ui.setupUi(this);setWindowFlags(Qt::FramelessWindowHint);setStyleSheet("background-color:#E3E4E7");CTabBrowser* pTab = new CTabBrowser(this);QHBoxLayout* pHLay = new QHBoxLayout(this);pHLay->addWidget(pTab);pHLay->setContentsMargins(6, 6, 6, 6);setLayout(pHLay);connect(pTab, &CTabBrowser::sig_close, this, &WPSDemo::on_close);
}void WPSDemo::on_close() {// 其它业务逻辑close();
}bool WPSDemo::nativeEvent(const QByteArray& eventType, void* message, long* result) {Q_UNUSED(eventType)MSG* param = static_cast<MSG*>(message);switch (param->message) {case WM_NCHITTEST: {int nX = GET_X_LPARAM(param->lParam) - this->geometry().x();int nY = GET_Y_LPARAM(param->lParam) - this->geometry().y();// 如果鼠标位于子控件上,则不进行处理if (childAt(nX, nY) != nullptr)return QWidget::nativeEvent(eventType, message, result);// 鼠标区域位于窗体边框,进行缩放if ((nX > 0) && (nX < m_BorderWidth))*result = HTLEFT;if ((nX > this->width() - m_BorderWidth) && (nX < this->width()))*result = HTRIGHT;if ((nY > 0) && (nY < m_BorderWidth))*result = HTTOP;if ((nY > this->height() - m_BorderWidth) && (nY < this->height()))*result = HTBOTTOM;if ((nX > 0) && (nX < m_BorderWidth) && (nY > 0)&& (nY < m_BorderWidth))*result = HTTOPLEFT;if ((nX > this->width() - m_BorderWidth) && (nX < this->width())&& (nY > 0) && (nY < m_BorderWidth))*result = HTTOPRIGHT;if ((nX > 0) && (nX < m_BorderWidth)&& (nY > this->height() - m_BorderWidth) && (nY < this->height()))*result = HTBOTTOMLEFT;if ((nX > this->width() - m_BorderWidth) && (nX < this->width())&& (nY > this->height() - m_BorderWidth) && (nY < this->height()))*result = HTBOTTOMRIGHT;return true;}}return QWidget::nativeEvent(eventType, message, result);
}

4.2 标签栏实现

  • CTabTitleWidget.h
#pragma once
#include <QWidget>
#include <QPushButton>class CTabTitleWidget : public QWidget {Q_OBJECTpublic:CTabTitleWidget(QWidget* parent = nullptr);~CTabTitleWidget();void setEmptyWidgetWidth(int w);protected:void paintEvent(QPaintEvent* event) override;void mousePressEvent(QMouseEvent* event) override;void mouseDoubleClickEvent(QMouseEvent* event);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;
};
  • CTabTitleWidget.cpp
#include "CTabTitleWidget.h"
#include <QHBoxLayout>
#include <QMouseEvent>
#include <QStyleOption>
#include <QPainter>#ifdef Q_OS_WIN
#include <qt_windows.h>
#pragma comment(lib, "user32.lib")
#endifCTabTitleWidget::CTabTitleWidget(QWidget* parent) {setStyleSheet("background-color:#E3E4E7");m_pAddBtn = new QPushButton(this);m_pAddBtn->setFlat(true);m_pAddBtn->setFixedSize(32, 32);m_pAddBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/add.svg)");m_pEmptyWidget = new QWidget(this);m_pUserBtn = new QPushButton(this);m_pUserBtn->setFlat(true);m_pUserBtn->setFixedSize(32, 32);m_pUserBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/user)");m_pMinBtn = new QPushButton(this);m_pMinBtn->setFlat(true);m_pMinBtn->setFixedSize(32, 32);m_pMinBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/min.svg)");m_pMaxBtn = new QPushButton(this);m_pMaxBtn->setFlat(true);m_pMaxBtn->setFixedSize(32, 32);m_pMaxBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/max.svg)");m_pCloseBtn = new QPushButton(this);m_pCloseBtn->setFlat(true);m_pCloseBtn->setFixedSize(32, 32);m_pCloseBtn->setStyleSheet("background-image:url(:/WPSDemo/resources/close.svg)");QHBoxLayout* pHLay = new QHBoxLayout(this);pHLay->addWidget(m_pAddBtn);pHLay->addWidget(m_pEmptyWidget);this->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding);pHLay->addWidget(m_pUserBtn);pHLay->addSpacing(8);pHLay->addWidget(m_pMinBtn);pHLay->addWidget(m_pMaxBtn);pHLay->addWidget(m_pCloseBtn);pHLay->setContentsMargins(1, 0, 1, 3);setLayout(pHLay);connect(m_pAddBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);connect(m_pMinBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);connect(m_pMaxBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);connect(m_pCloseBtn, &QPushButton::clicked, this, &CTabTitleWidget::on_Clicked);
}CTabTitleWidget::~CTabTitleWidget() {}void CTabTitleWidget::setEmptyWidgetWidth(int w) {m_pEmptyWidget->setMinimumWidth(w);
}void CTabTitleWidget::paintEvent(QPaintEvent* event) {QStyleOption opt;opt.init(this);QPainter p(this);style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);QWidget::paintEvent(event);
}void CTabTitleWidget::mousePressEvent(QMouseEvent* event) {if (ReleaseCapture()) {QWidget* pWindow = this->window();if (pWindow->isTopLevel()) {SendMessage(HWND(pWindow->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);}}event->ignore();
}void CTabTitleWidget::mouseDoubleClickEvent(QMouseEvent* event) {emit m_pMaxBtn->clicked();
}void CTabTitleWidget::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) {pWindow->isMaximized() ? pWindow->showNormal() : pWindow->showMaximized();} else if (pButton == m_pCloseBtn) {emit sig_close();}}
}

4.3 标签栏右键导航菜单实现

  • tabbrowser.h
#pragma once#include <QTabWidget>   
#include <QMenu> 
#include "CTabTitleWidget.h"class CTabBrowser : public QTabWidget {  Q_OBJECT  public:  explicit CTabBrowser(QWidget *parent = 0);  // tab 操作标志enum TAB_FLAG {NEW,CLOSE,NORMAL,SPECIAL};protected:  void resizeEvent(QResizeEvent *e) override;private:void initTabWidget();void setTabBarFlag(TAB_FLAG flag);void createTabMenu();private slots:  void on_newTab();void on_closeTab(int index);void onMenuShow(const QPoint& pos);void on_closeAllTab();signals:void sig_close();private:CTabTitleWidget* m_pRightWidget = nullptr;QMenu* m_pTabMenu = nullptr;
}; 
  • tabbrowser.cpp
#include "tabbrowser.h"  
#include <QDebug>  
#include <QPushButton>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QTabBar>QString qss0 = "QTabBar::tab{ \font: 75 12pt Arial; \text-align:left; \width:184px; \height:32; \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;} \QTabBar::scroller{width: 0px;}";QString qss1 = "QTabBar::tab{ \font: 75 12pt Arial; \text-align:left; \width:184px; \height:32; \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;} \QTabBar::scroller{width: 36px;}";CTabBrowser::CTabBrowser(QWidget *parent) : QTabWidget(parent) {  this->addTab(new QWidget,u8"稻壳");  this->setUsesScrollButtons(true);  // 滚动鼠标可切换 tabthis->setTabsClosable(true);       // 显示 tab 右侧的关闭按钮this->setMovable(true);            // tab 可移动位置initTabWidget();setTabBarFlag(NORMAL);this->setStyleSheet(qss0);connect(this, &QTabWidget::tabCloseRequested,this, &CTabBrowser::on_closeTab);
}  void CTabBrowser::resizeEvent(QResizeEvent *e) {  setTabBarFlag(NORMAL);QTabWidget::resizeEvent(e);  
}void CTabBrowser::createTabMenu() {m_pTabMenu = new QMenu(this);QAction* pAcSave = new QAction(QIcon(":/WPSDemo/resources/save.png"), u8"保存", m_pTabMenu);m_pTabMenu->addAction(pAcSave);connect(pAcSave, &QAction::triggered, [=] {QMessageBox::information(this, u8"提示", u8"你点击了 保存");});QAction* pAcSaveAs = new QAction(QString(u8"另存为"), m_pTabMenu);m_pTabMenu->addAction(pAcSaveAs);m_pTabMenu->addSeparator();QAction* pAcShareDoc = new QAction(QIcon(":/WPSDemo/resources/share.png"), QString(u8"分享文档"), m_pTabMenu);m_pTabMenu->addAction(pAcShareDoc);QAction* pAcSendToDevice = new QAction(QString(u8"发送到设备"), m_pTabMenu);m_pTabMenu->addAction(pAcSendToDevice);m_pTabMenu->addSeparator();QAction* pAcNewName = new QAction(QString(u8"重命名"), m_pTabMenu);m_pTabMenu->addAction(pAcNewName);QAction* pAcSaveToWPSCloud = new QAction(QString(u8"保存到WPS云文档"), m_pTabMenu);m_pTabMenu->addAction(pAcSaveToWPSCloud);QAction* pAcCloseAll = new QAction(QString(u8"关闭所有文件"), m_pTabMenu);m_pTabMenu->addAction(pAcCloseAll);connect(pAcCloseAll, &QAction::triggered, this, &CTabBrowser::on_closeAllTab);
}void CTabBrowser::setTabBarFlag(TAB_FLAG flag) {  int w = this->width();int tabsWidth = 0;  // 所有 tab 的总宽度int tabsHeight = tabBar()->height();  int tabs = this->count();  if (flag == NEW || flag == NORMAL) {for (int i = 0; i < tabs; ++i) {tabsWidth += tabBar()->tabRect(i).width();  }  } else {for (int i = 0; i < tabs - 1; ++i) {tabsWidth += tabBar()->tabRect(i).width();  }  } if (w > tabsWidth) {m_pRightWidget->setEmptyWidgetWidth(w - tabsWidth - 32 * 5 - 15);this->setStyleSheet(qss0);} else {// 当所有 tab 的宽度大于整个 tabWidget 的宽时m_pRightWidget->setEmptyWidgetWidth(150);this->setStyleSheet(qss1);}  
}  void CTabBrowser::initTabWidget() {  // 修改菜单策略this->setContextMenuPolicy(Qt::CustomContextMenu);connect(this, &QTabWidget::customContextMenuRequested, this, &CTabBrowser::onMenuShow);createTabMenu();m_pRightWidget = new CTabTitleWidget(this);this->setCornerWidget(m_pRightWidget, Qt::TopRightCorner);connect(m_pRightWidget, &CTabTitleWidget::sig_addtab, this, &CTabBrowser::on_newTab);connect(m_pRightWidget, &CTabTitleWidget::sig_close, this, &CTabBrowser::sig_close);
}  // 新建tab
void CTabBrowser::on_newTab() {  int nCount = count();QString  title = QString::number(nCount);title = "Page" + title;this->addTab(new QWidget, title);if (!tabsClosable()) {setTabsClosable(true);  }  setTabBarFlag(NEW);
}  void CTabBrowser::on_closeTab(int index) {  widget(index)->deleteLater();  setTabBarFlag(CLOSE);// 当只剩下 1 个 tab时if (count() == 1) {setTabsClosable(false);  setTabBarFlag(SPECIAL);}  
}  void CTabBrowser::onMenuShow(const QPoint& pos) {int index = this->tabBar()->tabAt(pos);#ifdef _DEBUGqDebug() << u8"当前tab为:" << QString::number(index);this->setCurrentIndex(index);
#endifif (index != -1) {m_pTabMenu->exec(QCursor::pos());}
}void CTabBrowser::on_closeAllTab() {}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS学习笔记(二)

一、时间片调度 1、同等优先级任务轮流地享有相同的 CPU 时间(可设置)&#xff0c; 叫时间片&#xff0c;在FreeRTOS中&#xff0c;一个时间片就等于SysTick 中断周期 /* 任务一&#xff0c;实现LED0每500ms翻转一次 */ void task1( void * pvParameters ) {uint32_t task1_n…

统计一个只包含大写字母的字符串中顺序对的数量.其中顺序对的定义为前面的字符小后面的字符大.例如在“ABC“中的顺序对为3,因为有AB,AC,BC

哈希法&#xff1a;扫描字符串&#xff0c;将出现的字符次数加1&#xff0c;统计比当前字符字典序小的字母出现的次数&#xff0c;即为顺序串的个数。 int CounSq(const char* arr)//时间复杂度O&#xff08;n&#xff09; {int sig[26] { 0 };int index 0;int sum 0;for (…

【自然语言处理】基于python的问答系统实现

一&#xff0c;文件准备 该问答系统是基于已知的问题和其一一对应的答案进行实现的。首先需要准备两个文本文件&#xff0c;分别命名为“question.txt”和“answer.txt”&#xff0c;分别是问题文件和答案文件&#xff0c;每一行是一个问题以及对应的答案。 问题文件: 中国的首…

C++ Qt 学习(四):自定义控件与 qss 应用

1. qss 简介 Qt style sheet&#xff08;qss&#xff0c;Qt 样式表&#xff09;&#xff0c;不需要用 C 代码控件进行重载&#xff0c;就可以修改控件外观&#xff0c;类似于前端的 css 2. qss 选择器 2.1 通配符选择器 /* 设置后控件窗口背景色都被修改为黄色 */ * {backg…

【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法】

文章目录 概要加载样例图像统计数据分析White Patch Algorithm小结 概要 白平衡技术在摄影和图像处理中扮演着至关重要的角色。在不同的光照条件下&#xff0c;相机可能无法准确地捕捉到物体的真实颜色&#xff0c;导致图像呈现出暗淡、色调不自然或者褪色的效果。为了解决这个…

项目实战:中央控制器实现(2)-优化Controller,将共性动作抽取到中央控制器

1、FruitController FruitController已经和Web没有关系了&#xff0c;和Web容器解耦&#xff0c;可以脱离Web容器做单元测试 package com.csdn.fruit.controller; import com.csdn.fruit.dto.PageInfo; import com.csdn.fruit.dto.PageQueryParam; import com.csdn.fruit.dto.R…

5G与物联网应用:新一代网络技术融合开创新时代

5G与物联网应用&#xff1a;新一代网络技术融合开创新时代 随着信息技术的不断演进&#xff0c;5G和物联网作为新一代网络技术&#xff0c;正在引领我们走向一个更加智能化、互联互通的新时代。本文将分析5G与物联网应用的技术原理、应用场景与发展趋势&#xff0c;并探讨它们…

软件测试|selenium执行js脚本

JavaScript是运行在客户端&#xff08;浏览器&#xff09;和服务器端的脚本语言&#xff0c;允许将静态网页转换为交互式网页。可以通过 Python Selenium WebDriver 执行 JavaScript 语句&#xff0c;在Web页面中进行js交互。那么js能做的事&#xff0c;Selenium应该大部分也能…

软件测试|测试方法论—边界值

边界值分析法是一种很实用的黑盒测试用例方法&#xff0c;它具有很强的发现故障的能力。边界值分析法也是作为对等价类划分法的补充&#xff0c;测试用例来自等价类的边界。 这个方法其实是在测试实践当中发现&#xff0c;Bug 往往出现在定义域或值域的边界上&#xff0c;而不…

Godot4实现游戏的多语言版本

要在Godot 4中实现多语言版本的游戏&#xff0c;您需要按照以下几个步骤来设置和管理游戏文本以及可能的其他资源&#xff0c;如图像或声音。以下是根据官方文档和详细教程整理的简明指南&#xff1a; 准备翻译文件&#xff1a; Godot支持使用.csv文件或.po文件进行国际化​​…

基于单片机GP2D12测距-proteus仿真-源程序

基于51单片机红外测距-proteus仿真-源程序 一、系统方案 本设计采用51单片机作为主控器&#xff0c;液晶1602显示&#xff0c;GP2D12采集距离值&#xff0c;按键设置报警阀值&#xff0c;测量值超过阀值&#xff0c;蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单…

Day45 力扣动态规划 : 1143.最长公共子序列 |1035.不相交的线 | 53. 最大子序和

Day45 力扣动态规划 : 1143.最长公共子序列 &#xff5c;1035.不相交的线 &#xff5c; 53. 最大子序和 1143.最长公共子序列第一印象看完题解的思路实现中的困难感悟代码 1035.不相交的线第一印象感悟代码 53. 最大子序和第一印象dp递推公式初始化遍历顺序 实现中的困难感悟代…

数字政府!3DCAT实时云渲染助推上海湾区数字孪生平台

数字孪生&#xff0c;是一种利用物理模型、传感器数据、运行历史等信息&#xff0c;在虚拟空间中构建实体对象或系统的精确映射&#xff0c;从而实现对其全生命周期的仿真、优化和管理的技术。数字孪生可以应用于各个领域&#xff0c;如工业制造、智慧城市、医疗健康、教育培训…

原厂可调漏电继电器 LLJ-125F Φ45导轨安装可选面板安装

LLJ-F(S)系列漏电继电器 原厂漏电继电器 LLJ-125F Φ45 导轨安装 系列型号&#xff1a; LLJ-10F(S)漏电继电器LLJ-15F(S)漏电继电器LLJ-16F(S)漏电继电器 LLJ-25F(S)漏电继电器LLJ-30F(S)漏电继电器LLJ-32F(S)漏电继电器 LLJ-60F(S)漏电继电器LLJ-63F(S)漏电继电器LLJ-80F(S)…

渲染管线详解

光栅化的渲染管线一般分为三大阶段&#xff1a;应用程序阶段->几何阶段->光栅化阶段 也可以四大阶段&#xff1a; 应用程序阶段->几何阶段->光栅化阶段->逐片元操作阶段 更详细的流程如下&#xff1a; Vertex Specification&#xff08;顶点规范化&#xff09…

一文搞定多端开发,做全栈大牛 附三大企业实战项目

一个功能三套代码 一改需求就是加不完的班&#xff1f; 不存在的&#xff0c;告别改改改 拥抱多端开发 一套代码搞定多个平台 高效开发&#xff1a;一套代码&#xff0c;多端通用 根据统计数据&#xff0c;全球移动设备用户数已经超过了50亿。随着智能手机、平板电脑等移动…

JSON——数组语法

一段JSON可能是以 ”{“ 开头 也可能仅包含一段JSON数组 如下 [ { "name" : "hello,world"}, {"name" : "SB JSON”}&#xff0c; {“name” : "SB互联网房地产CNM“}&#xff0c; ] 瞧&#xff0c;蛋疼不...CJSON过来还是得搜下网…

【ES专题】Logstash与FileBeat详解以及ELK整合详解

目录 前言阅读对象阅读导航前置知识笔记正文一、ELK架构1.1 经典的ELK1.2 整合消息队列Nginx架构 二、LogStash介绍2.1 Logstash核心概念2.1.1 Pipeline2.1.2 Event2.1.3 Codec (Code / Decode)2.1.4 Queue 2.2 Logstash数据传输原理2.3 Logstash的安装&#xff08;以windows为…

Python tkinter用iconphoto方法修改窗口标题的图片

修改Python Tkinter窗口的标题图片&#xff0c;可以使用PhotoImage、iconphoto方法。这个方法允许你设置窗口的图标。 运行结果 代码示例如下&#xff1a; import tkinter as tkroot Tk()# 加载图片&#xff0c;记住一定是要PNG图片 icon tk.PhotoImage(filephoto\\图片.pn…

python的format函数的用法及实例

目录 1.format函数的语法及用法 &#xff08;1&#xff09;语法&#xff1a;{}.format() &#xff08;2&#xff09;用法&#xff1a;用于格式化字符串。可以接受无限个参数&#xff0c;可以指定顺序。返回结果为字符串。 2.实例 &#xff08;1&#xff09;不设置位置&…