接上一篇,继续探索“覆盖层”的使用方法。

五、覆盖层进阶交互:从 “能绘制” 到 “好操作”​

基础的绘制功能只能满足 “看得见” 的需求,实际开发中还需要 “能操作”—— 比如选中线条修改颜色、按 Delete 键删除线条、鼠标 hover 时高亮组件等。这一部分一起探索覆盖层的进阶交互逻辑,让覆盖层从 “静态画布” 变成 “可交互工具”。​

5.1 交互 1:线条的选中与高亮(鼠标 Hover 效果)​

需求:鼠标移动到覆盖层的线条上时,线条自动变粗、变色(高亮),离开时恢复原状,帮助用户快速识别当前操作的线条。​
实现步骤:​

  • 添加成员变量存储选中状态:在OverlayWidget.h中添加变量,记录当前选中的线条索引和鼠标是否 hover 在线条上:
private:int m_selectedLineIndex; // 当前选中的线条索引(-1表示未选中)int m_hoveredLineIndex;  // 当前hover的线条索引(-1表示无)QColor m_defaultLineColor = Qt::red;   // 线条默认颜色QColor m_hoverLineColor = Qt::blue;    // 线条hover颜色QColor m_selectedLineColor = Qt::green;// 线条选中颜色int m_defaultLineWidth = 2;            // 线条默认宽度int m_hoverLineWidth = 4;              // 线条hover宽度
  • 初始化状态变量:在initOverlayProps中初始化选中 /hover 状态:
void OverlayWidget::initOverlayProps()
{// 其他初始化...m_selectedLineIndex = -1; // 初始未选中m_hoveredLineIndex = -1;  // 初始无hover
}
  • 重写鼠标移动事件,判断 hover 状态:通过mouseMoveEvent实时检测鼠标是否落在线条上,更新m_hoveredLineIndex并触发重绘:
void OverlayWidget::mouseMoveEvent(QMouseEvent *event)
{// 1. 先处理之前的拖动逻辑(若有)if (m_isDrawing) {m_tempLine.setP2(event->pos());update();QWidget::mouseMoveEvent(event);return;}// 2. 检测鼠标是否hover在线条上int hoverIndex = -1;QPointF mousePos = event->pos();// 遍历所有线条,判断鼠标是否在线条附近(阈值5像素)for (int i = 0; i < m_lines.size(); ++i) {QLineF line = m_lines[i];if (distanceToLine(mousePos, line) <= 5.0) {hoverIndex = i;break; // 只高亮最上层的线条(可根据需求调整为多线条高亮)}}// 3. 若hover状态变化,更新并触发重绘if (hoverIndex != m_hoveredLineIndex) {m_hoveredLineIndex = hoverIndex;// 更改鼠标样式(可选,提升交互体验)if (m_hoveredLineIndex != -1) {setCursor(Qt::PointingHandCursor); // 鼠标变为手型} else {setCursor(Qt::ArrowCursor);        // 恢复默认鼠标}update(); // 重绘以显示高亮效果}QWidget::mouseMoveEvent(event);
}
// 手动实现:计算QPoint到QLine(线段)的最短距离,有人说QLineF自带有计算点到线段距离的函数distanceToPoint(),但我试了没有
qreal OverlayWidget::distanceToLine(const QPointF& point, const QLineF& line)
{// 线段起点A、终点B,目标点PQPointF A = line.p1();QPointF B = line.p2();QPointF P = point;// 计算向量AB、AP、BPqreal ABx = B.x() - A.x();qreal ABy = B.y() - A.y();qreal APx = P.x() - A.x();qreal APy = P.y() - A.y();qreal BPx = P.x() - B.x();qreal BPy = P.y() - B.y();// 情况1:P的投影在A外侧(向量AP与AB夹角>90°),距离=AP长度if (APx * ABx + APy * ABy < 0) {return qSqrt(APx*APx + APy*APy);}// 情况2:P的投影在B外侧(向量BP与BA夹角>90°),距离=BP长度if (BPx * (-ABx) + BPy * (-ABy) < 0) {return qSqrt(BPx*BPx + BPy*BPy);}// 情况3:P的投影在线段上,距离=三角形面积*2 / AB长度(叉积公式)qreal area = qAbs(ABx * APy - ABy * APx); // 三角形ABP的面积(绝对值)qreal ABLength = qSqrt(ABx*ABx + ABy*ABy); // 线段AB长度return area / ABLength;
}
  • 修改 paintEvent,根据状态绘制线条:根据 “默认 /hover/ 选中” 状态,动态调整线条的颜色和宽度:
void OverlayWidget::paintEvent(QPaintEvent *event)
{QWidget::paintEvent(event);QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing, true);// 绘制已确定的线条(按状态区分样式)for (int i = 0; i < m_lines.size(); ++i) {QLineF line = m_lines[i];QPen pen;// 判断线条状态:选中 > hover > 默认if (i == m_selectedLineIndex) {pen.setColor(m_selectedLineColor);pen.setWidth(m_hoverLineWidth); // 选中线条用hover宽度} else if (i == m_hoveredLineIndex) {pen.setColor(m_hoverLineColor);pen.setWidth(m_hoverLineWidth);} else {pen.setColor(m_defaultLineColor);pen.setWidth(m_defaultLineWidth);}painter.setPen(pen);painter.drawLine(line);}// 绘制临时线条(保持之前的逻辑)if (m_isDrawing) {QPen penTemp(Qt::blue, 2, Qt::DashLine);painter.setPen(penTemp);painter.drawLine(m_tempLine);}
}
  • 效果:​
    鼠标移动到线条上:线条从 “红色 2px” 变为 “蓝色 4px”,鼠标变为手型,直观提示 “可交互”;​
    鼠标离开线条:自动恢复为默认样式,无操作延迟;​
    后续可基于此扩展 “点击选中线条”“右键菜单修改属性” 等功能。​
    在这里插入图片描述
    注意:

5.2 交互 2:线条的选中与删除(鼠标 + 键盘)​

需求:鼠标左键点击线条选中(绿色 4px),按 Delete 键删除选中线条,或右键点击线条弹出 “删除” 菜单,提升操作灵活性。​

5.2.1 鼠标点击选中线条​

在mousePressEvent中添加选中逻辑,通过鼠标位置判断是否点击线条,更新m_selectedLineIndex:

void OverlayWidget::mousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton && m_isDrawing){m_isDrawing = false;// 将临时线条添加到线条集合(确保线条有一定长度,避免无效线条)if (qAbs(m_tempLine.x2()-m_tempLine.x1()) > 5 || qAbs(m_tempLine.y2()-m_tempLine.y1()) > 5){m_lines.append(m_tempLine);}// 触发重绘(更新已确定线条的显示)update();QWidget::mouseReleaseEvent(event);return;}// 1. 优先处理绘制逻辑(左键按下开始绘制)if (event->button() == Qt::RightButton && !m_isDrawing && m_hoveredLineIndex == -1) {// 遍历线条,判断是否点击在线条上int clickIndex = -1;QPointF mousePos = event->pos();for (int i = 0; i < m_lines.size(); ++i) {if (distanceToLine(mousePos, m_lines[i]) <= 5.0) {clickIndex = i;break;}}// 更新选中状态:点击线条则选中,点击空白则取消选中if (clickIndex != -1) {m_selectedLineIndex = clickIndex;qDebug() << QString("selectlinux") << m_selectedLineIndex;} else {m_selectedLineIndex = -1; // 点击空白,取消选中m_isDrawing = true;       // 开始新的绘制m_tempLine.setP1(event->pos());m_tempLine.setP2(event->pos());}update();event->accept();return;}// 2. 右键点击线条,弹出删除菜单(需添加QMenu头文件)if (event->button() == Qt::RightButton && m_hoveredLineIndex != -1) {QMenu menu(this);QAction *deleteAction = menu.addAction("删除线条");// 连接删除动作的信号槽connect(deleteAction, &QAction::triggered, this, [this]() {if (m_hoveredLineIndex >= 0 && m_hoveredLineIndex < m_lines.size()) {m_lines.removeAt(m_hoveredLineIndex);m_selectedLineIndex = -1; // 删除后取消选中m_hoveredLineIndex = -1;update();qDebug() << QString("delete line index") << m_hoveredLineIndex;}});menu.exec(event->globalPos()); // 在鼠标位置弹出菜单event->accept();return;}QWidget::mousePressEvent(event);
}
5.2.2 键盘事件:按 Delete 键删除选中线条​

重写keyPressEvent,监听 Delete 键,删除当前选中的线条:

// OverlayWidget.h中声明键盘事件
protected:void keyPressEvent(QKeyEvent *event) override;// OverlayWidget.cpp中实现
void OverlayWidget::keyPressEvent(QKeyEvent *event)
{// 只处理Delete键,且有选中线条时if (event->key() == Qt::Key_Delete && m_selectedLineIndex != -1) {m_lines.removeAt(m_selectedLineIndex);qDebug() << "按Delete键删除线条,索引:" << m_selectedLineIndex;m_selectedLineIndex = -1; // 取消选中m_hoveredLineIndex = -1;update();event->accept();return;}QWidget::keyPressEvent(event);
}

效果说明:​
左键点击线条:线条变为 “绿色 4px”,标记为选中;​
按 Delete 键:选中的线条立即删除,界面实时更新;​
右键点击线条:弹出 “删除线条” 菜单,点击后删除线条,适合鼠标操作偏好的用户。​
在这里插入图片描述

5.3 交互 3:进阶穿透交互(区分组件类型)​

之前的 “穿透交互” 只判断 “是否点击绘制内容”,实际场景中可能需要 “点击不同类型的下层组件,执行不同逻辑”—— 比如点击QPushButton触发按钮事件。​
实现步骤:​

  • 添加 “获取下层组件类型” 的辅助函数:通过鼠标坐标,找到下层被遮挡的组件,判断其类型:
// OverlayWidget.h中声明辅助函数
private:QWidget* getUnderlyingWidget(const QPoint& mousePos); // 获取下层组件// OverlayWidget.cpp中实现
QWidget* OverlayWidget::getUnderlyingWidget(const QPoint& mousePos)
{if (!parentWidget()) return nullptr;// 1. 将覆盖层的鼠标坐标转换为父组件(如centralWidget)的坐标QPoint parentPos = mapToParent(mousePos);// 2. 遍历父组件的所有子组件,找到包含该坐标的组件QList<QWidget*> childWidgets = parentWidget()->findChildren<QWidget*>();// 按Z序从高到低遍历(确保找到最上层的下层组件)for (int i = childWidgets.size() - 1; i >= 0; --i) {QWidget* child = childWidgets[i];// 组件必须可见且可交互if (child->isVisible() && child->isEnabled() && child->geometry().contains(parentPos)) {return child;}}return parentWidget(); // 未找到子组件,返回父组件
}
  • 在鼠标事件中根据组件类型处理穿透:点击覆盖层空白区域时,根据下层组件类型执行不同逻辑:
void OverlayWidget::mousePressEvent(QMouseEvent *event)
{// 先判断是否点击绘制内容(线条/临时线条)bool isClickOnDrawContent = false;// 检查是否点击已存在的线条for (auto& line : m_lines) {if (line.distanceToPoint(event->pos()) <= 5.0) {isClickOnDrawContent = true;break;}}// 检查是否点击临时线条(绘制中)if (m_isDrawing && m_tempLine.distanceToPoint(event->pos()) <= 5.0) {isClickOnDrawContent = true;}// 若点击绘制内容,处理覆盖层逻辑;否则根据下层组件类型处理if (isClickOnDrawContent) {// 之前的线条选中/绘制逻辑...} else {// 获取下层组件QWidget* underlyingWidget = getUnderlyingWidget(event->pos());if (underlyingWidget) {// 情况1:下层是QPushButton,触发按钮点击if (qobject_cast<QPushButton*>(underlyingWidget)) {QPushButton* btn = qobject_cast<QPushButton*>(underlyingWidget);btn->click(); // 模拟按钮点击qDebug() << "穿透点击按钮:" << btn->text();}// 情况2:下层是QLabel,在覆盖层添加标注else if (qobject_cast<QLabel*>(underlyingWidget)) {QLabel* label = qobject_cast<QLabel*>(underlyingWidget);// 在标签中心添加“已标注”文本(后续可扩展为自定义标注)m_annotations.append({event->pos(), QString("标注:%1").arg(label->text())});update();qDebug() << "在标签" << label->text() << "上添加标注";}// 情况3:其他组件,直接穿透事件else {event->ignore(); // 让事件自然传递}}}QWidget::mousePressEvent(event);
}
  • 添加标注绘制逻辑:在paintEvent中绘制标注文本:
// OverlayWidget.h中添加标注存储结构
private:struct Annotation {QPointF pos;      // 标注位置QString text;     // 标注文本};QVector<Annotation> m_annotations; // 存储所有标注// paintEvent中添加标注绘制
void OverlayWidget::paintEvent(QPaintEvent *event)
{// 其他绘制逻辑(线条、临时线条)...// 绘制标注(黑色文本,白色背景半透明)if (!m_annotations.isEmpty()) {QPainter painter(this);QFont annotationFont;annotationFont.setPointSize(9);painter.setFont(annotationFont);foreach (auto& anno, m_annotations) {// 绘制背景(避免文本与下层内容重叠)QRect textRect = painter.boundingRect(QRect(), Qt::AlignLeft, anno.text);textRect.adjust(-5, -3, 5, 3); // 扩展边距,提升可读性painter.setBrush(QColor(255, 255, 255, 180)); // 白色半透明背景painter.setPen(Qt::NoPen);painter.drawRect(textRect.translated(anno.pos.x(), anno.pos.y()));// 绘制文本painter.setPen(Qt::black);painter.drawText(anno.pos + QPointF(0, textRect.height()), anno.text);}}
}

这里要注意“mapToParent()”函数,是将本页面的有个组件或者点位的坐标投射到父页面上。有事还会用到另一个函数"mapTo()"也是有类似功效。

六、完整实战案例:工业设备连接图(覆盖层综合应用)​

为了将前面的知识点串联起来,我们实现一个 “工业设备连接图” 项目,包含以下核心功能:​
堆叠组件页面:3 个页面(设备页、传感器页、数据页),支持页面切换;​
覆盖层跨页面连线:设备页的 “设备” 与传感器页的 “传感器” 之间绘制跨页面连接线条;​
组件交互:设备 / 传感器按钮可拖动,线条实时更新;​
线条编辑:选中、删除、修改线条颜色;​
数据联动:点击线条显示设备与传感器的连接状态(如 “正常 / 断开”)。​

6.1 项目结构与界面设计​

6.1.1 界面布局(Qt 设计器)​
  • 主窗口(QMainWindow):​
    顶部工具栏:3 个QPushButton(“设备页面”“传感器页面”“数据页面”),用于切换堆叠组件;​
    中心区域:QStackedWidget(命名为stackedWidget),包含 3 个页面;​
    覆盖层:OverlayWidget(代码创建,覆盖整个中心区域,两个按钮,标识来自覆盖层,并且跟下层连线)。​
    堆叠组件页面内容:​
    页面 0(设备页):2 个QPushButton(btnDevice1“设备 1”、btnDevice2“设备 2”),QLabel“设备状态:正常”;​
    页面 1(传感器页):3 个QPushButton(btnSensor1“传感器 1”、btnSensor2“传感器 2”、btnSensor3“传感器 3”);​
    页面 2(数据页):QTableWidget,显示设备 - 传感器的连接数据(备用)。​
6.1.2 核心类结构​

MainWindow:管理堆叠组件、页面切换、组件拖动(事件过滤器);​
OverlayWidget:负责跨页面连线绘制;​

6.2 核心功能实现​
6.2.1 1. 主界面头文件

跨页面连线的核心是 “获取被覆盖的页面中组件的坐标”——QStackedWidget 隐藏的页面虽不可见,但组件的pos()和size()仍有效,只需将其坐标转换为覆盖层的全局坐标即可。​
在MainWindow中添加 “获取组件全局坐标” 的函数:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QGridLayout>
#include "overlaywidget.h"#include <QTableWidgetItem>
#include <QVector>
#include <QLineF>
#include <QPoint>// 设备-传感器连接数据结构体
struct ConnectionData {QWidget* deviceWidget;    // 设备组件(pageDevice内的按钮)QWidget* sensorWidget;   // 传感器组件(pageSensor内的按钮)QColor lineColor;        // 连线颜色(正常绿/断开红)QString status;          // 连接状态("正常"/"断开")QString deviceName;      // 设备名称(如"设备1")QString sensorName;      // 传感器名称(如"传感器1")
};QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();void init();QPointF getWidgetGlobalPos(QWidget* widget); // 获取组件相对于覆盖层的坐标private slots:void on_btHide_clicked();void on_btShow_clicked();void on_btTest_clicked();void on_btData_clicked();// 表格选中行变化(同步线条选中)​void onTableCurrentRowChanged(int currentRow, int previousRow);// 表格数据修改(同步线条状态)​void onTableItemChanged(QTableWidgetItem *item);private:Ui::MainWindow *ui;//覆盖层OverlayWidget* m_overlayWidget = nullptr;QVector<ConnectionData> m_connections; // 所有设备-传感器连接数据​QWidget* m_currentDraggedBtn = nullptr; // 当前正在拖动的按钮​QPoint m_dragStartPos;           // 拖动起始位置(鼠标全局坐标-按钮坐标)​// 事件过滤器(处理按钮拖动)​bool eventFilter(QObject *watched, QEvent *event) override;void updateOverlayCrossPageLines();// 核心函数:获取组件相对于覆盖层的中心坐标​QPointF getWidgetCenterInOverlay(QWidget* widget);// 初始化表格数据(同步m_connections)​void initTableWidget();// 同步表格数据与连接数据​void syncTableAndConnections();// MainWindow.h中添加信号signals:void crossPageLinesUpdated(const QVector<QLineF>& lines, const QVector<QColor>& colors);
};
#endif // MAINWINDOW_H
6.2.2 2. 主界面cpp文件​

在MainWindow中管理所有主界面的按钮响应和事件:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QLayoutItem>
#include <QGridLayout>
#include <QLabel>
#include <QMessageBox>
#include <QDebug>
#include <QMouseEvent>
#include <QTableWidgetItem>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);connect(ui->stackedWidget, &QStackedWidget::currentChanged, this, &MainWindow::updateOverlayCrossPageLines);init();
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::init()
{// 1. 初始化覆盖层m_overlayWidget = new OverlayWidget(this);m_overlayWidget->setGeometry(0, 0,width(), height());m_overlayWidget->raise(); // 置顶覆盖层m_overlayWidget->show();// 2. 初始化设备-传感器连接数据m_connections.append({ui->btnDevice1, ui->btnSensor1, Qt::green, QString("正常"), QString("Device1"), QString("Sencor1")});m_connections.append({ui->btnDevice2, ui->btnSensor3, Qt::red, QString("断开"), QString("Device2"), QString("Sencor3")});// 3. 初始化表格initTableWidget();// 4. 安装事件过滤器(处理按钮拖动)ui->btnDevice1->installEventFilter(this);ui->btnDevice2->installEventFilter(this);ui->btnSensor1->installEventFilter(this);ui->btnSensor2->installEventFilter(this);ui->btnSensor3->installEventFilter(this);// 6. 连接表格信号(数据联动)//connect(ui->tableWidget, &QTableWidget::currentItemChanged, this, &MainWindow::onTableCurrentRowChanged);connect(ui->tableWidget, &QTableWidget::itemChanged, this, &MainWindow::onTableItemChanged);// 7. 初始更新覆盖层线条updateOverlayCrossPageLines();QList<QWidget*> fromWidget;fromWidget.append(ui->btnDevice1);fromWidget.append(ui->btnDevice2);fromWidget.append(ui->btnSensor1);fromWidget.append(ui->btnSensor2);fromWidget.append(ui->btnSensor3);m_overlayWidget->addConnection(fromWidget);
}// 表格选中行变化:同步覆盖层线条选中
void MainWindow::onTableCurrentRowChanged(int currentRow, int previousRow)
{
//    if (currentRow >= 0 && currentRow < m_connections.size()) {
//        // 通知覆盖层选中对应跨页面线条(需在OverlayWidget中添加setSelectedCrossLine函数)
//        m_overlayWidget->setSelectedCrossLine(currentRow);
//    } else {
//        // 取消选中
//        m_overlayWidget->setSelectedCrossLine(-1);
//    }
}// 表格数据修改:同步连接状态与线条颜色
void MainWindow::onTableItemChanged(QTableWidgetItem *item)
{int row = item->row();int col = item->column();if (row >= 0 && row < m_connections.size()) {// 仅处理“状态”列(第3列)的修改if (col == 3) {QString newStatus = item->text().trimmed();if (newStatus == QString("正常")) {m_connections[row].status = QString("正常");m_connections[row].lineColor = Qt::green;} else if (newStatus == QString("断开")) {m_connections[row].status = QString("断开");m_connections[row].lineColor = Qt::red;}// 同步覆盖层线条updateOverlayCrossPageLines();}}
}// 事件过滤器:处理按钮拖动
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{// 判断是否为目标按钮(设备/传感器按钮)bool isTargetBtn = (watched == ui->btnDevice1 || watched == ui->btnDevice2 ||watched == ui->btnSensor1 || watched == ui->btnSensor2 ||watched == ui->btnSensor3);if (!isTargetBtn) {return QMainWindow::eventFilter(watched, event);}QWidget* btn = qobject_cast<QWidget*>(watched);QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(event);if (!btn || !mouseEvent) {return QMainWindow::eventFilter(watched, event);}// 1. 鼠标按下:记录拖动起始状态if (event->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::LeftButton) {m_currentDraggedBtn = btn;// 计算起始偏移(避免拖动时按钮跳变)m_dragStartPos = mouseEvent->globalPos() - btn->pos();btn->setCursor(Qt::ClosedHandCursor); // 鼠标变为“闭合手”qDebug() << "[MainWindow] 开始拖动按钮:" << btn->objectName();return true;}// 2. 鼠标移动:更新按钮位置if (event->type() == QEvent::MouseMove && m_currentDraggedBtn == btn) {if (mouseEvent->buttons() & Qt::LeftButton) {// 计算新位置(限制在centralWidget内)QPoint newPos = mouseEvent->globalPos() - m_dragStartPos;QRect centralRect = ui->centralwidget->geometry();// 边界限制(避免按钮拖出主窗口)newPos.setX(qMax(0, qMin(newPos.x(), centralRect.width() - btn->width())));newPos.setY(qMax(0, qMin(newPos.y(), centralRect.height() - btn->height())));// 更新按钮位置btn->move(newPos);// 同步更新跨页面线条updateOverlayCrossPageLines();}return true;}// 3. 鼠标释放:结束拖动if (event->type() == QEvent::MouseButtonRelease && mouseEvent->button() == Qt::LeftButton) {if (m_currentDraggedBtn == btn) {m_currentDraggedBtn = nullptr;btn->setCursor(Qt::ArrowCursor); // 恢复鼠标样式//qDebug() << "[MainWindow] 结束拖动按钮:" << btn->objectName();}return true;}return QMainWindow::eventFilter(watched, event);
}// 核心函数:更新覆盖层跨页面线条
void MainWindow::updateOverlayCrossPageLines()
{// 通知覆盖层更新//m_overlayWidget->updateCrossPageLines(crossLines, crossColors, crossStatuses);m_overlayWidget->updatePageShow();
}// 核心函数:获取组件相对于覆盖层的中心坐标
QPointF MainWindow::getWidgetCenterInOverlay(QWidget* widget)
{if (!widget || !m_overlayWidget) {return QPointF();}// 1. 组件相对于自身父组件的坐标QPoint widgetPos = widget->pos();QWidget* parent = widget->parentWidget();// 2. 递归转换到stackedWidget的坐标(因按钮在stackedWidget的子页面中)while (parent && parent != ui->stackedWidget) {widgetPos += parent->pos();parent = parent->parentWidget();}// 3. stackedWidget相对于centralWidget的坐标QPoint stackedPos = ui->stackedWidget->pos();// 4. 转换为覆盖层的坐标(覆盖层父组件是centralWidget)QPointF overlayPos = m_overlayWidget->mapFromParent(stackedPos + widgetPos);// 5. 返回组件中心坐标return overlayPos + QPointF(widget->width()/2.0, widget->height()/2.0);
}// 初始化表格:设置列名与初始数据
void MainWindow::initTableWidget()
{// 设置表格列数与列名ui->tableWidget->setColumnCount(4);QStringList headers = {QString("DevicesName"), QString("SensorName"), QString("LineColor"), QString("ConnectStatus")};ui->tableWidget->setHorizontalHeaderLabels(headers);// 设置表格属性ui->tableWidget->setEditTriggers(QAbstractItemView::DoubleClicked); // 双击可编辑ui->tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); // 选中整行ui->tableWidget->horizontalHeader()->setStretchLastSection(true); // 最后一列拉伸// 同步初始数据syncTableAndConnections();
}// 同步表格数据与连接数据
void MainWindow::syncTableAndConnections()
{// 清空表格ui->tableWidget->setRowCount(0);// 添加所有连接数据到表格for (int i = 0; i < m_connections.size(); ++i) {auto& conn = m_connections[i];ui->tableWidget->insertRow(i);// 设备名称(不可编辑)QTableWidgetItem* deviceItem = new QTableWidgetItem(conn.deviceName);deviceItem->setFlags(deviceItem->flags() & ~Qt::ItemIsEditable);ui->tableWidget->setItem(i, 0, deviceItem);// 传感器名称(不可编辑)QTableWidgetItem* sensorItem = new QTableWidgetItem(conn.sensorName);sensorItem->setFlags(sensorItem->flags() & ~Qt::ItemIsEditable);ui->tableWidget->setItem(i, 1, sensorItem);// 线条颜色(不可编辑,显示颜色名称)QString colorName = conn.lineColor == Qt::green ? QString("绿色") : QString("红色");QTableWidgetItem* colorItem = new QTableWidgetItem(colorName);colorItem->setFlags(colorItem->flags() & ~Qt::ItemIsEditable);ui->tableWidget->setItem(i, 2, colorItem);// 连接状态(可编辑,仅允许“正常”/“断开”)QTableWidgetItem* statusItem = new QTableWidgetItem(conn.status);ui->tableWidget->setItem(i, 3, statusItem);}
}////////////////////////////////////////void MainWindow::on_btHide_clicked()
{ui->stackedWidget->setCurrentIndex(0);m_overlayWidget->raise(); // 切换后重新置顶覆盖层
}void MainWindow::on_btShow_clicked()
{//QMessageBox::about(this, QString("title"), QString("Penetration response successful"));ui->stackedWidget->setCurrentIndex(1);m_overlayWidget->raise(); // 切换后重新置顶覆盖层
}
void MainWindow::on_btData_clicked()
{ui->stackedWidget->setCurrentIndex(2);m_overlayWidget->raise(); // 切换后重新置顶覆盖层
}
void MainWindow::on_btTest_clicked()
{}
6.2.3 3. 覆盖层绘制跨页面线条​头文件

在OverlayWidget中添加跨页面线条存储和绘制逻辑:

#ifndef OVERLAYWIDGET_H
#define OVERLAYWIDGET_H#include <QEvent>
#include <QResizeEvent>
#include <QPaintEvent>
#include <QWidget>
#include <QPair>
#include <QList>
#include <QTimer>
#include <QPoint>
#include <QPointF>
#include <QtMath>
#include <QLine>
#include <QLineF>
#include <QVector>
#include <QColor>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QPushButton>// 标注结构体(线条状态、组件标注)​
struct Annotation {QPointF pos;      // 标注位置​QString text;     // 标注文本​
};class OverlayWidget : public QWidget
{Q_OBJECT
public:explicit OverlayWidget(QWidget *parent = nullptr);void addConnection(QList<QWidget*> fromWidget);public slots:
//    // 接收MainWindow的选中指令(选中指定跨页面线条)​
//    void setSelectedCrossLine(int index);
//    void onUpdateCrossPageLines(const QVector<QLineF>& lines, const QVector<QColor>& colors);void updatePageShow();
protected:// 绘制事件(连线、标注、临时线条)void paintEvent(QPaintEvent *event) override;private:QWidget* m_parent = nullptr;QPushButton* btn1 = nullptr;QPushButton* btn2 = nullptr;// 存储需要连接的组件对QList<QWidget*> m_lstFromWidget;
};#endif // OVERLAYWIDGET_H
6.2.4 4. 覆盖层Cpp文件​

当堆叠组件切换页面时,即使隐藏页面的组件不可见,覆盖层仍需绘制跨页面线条,因此需要在currentChanged信号中更新线条:

#include "overlaywidget.h"#include <QPainter>
#include <QPen>
#include <QtMath>
#include <QtDebug>
#include <QMenu>
#include <QPushButton>
#include <QLabel>
#include <QDebug>
#include <QMenu>
#include <QAction>
#include <QHBoxLayout>const int outR = 10;
const int inR = 6;OverlayWidget::OverlayWidget(QWidget *parent) : QWidget(parent)
{m_parent = parent;this->setParent(parent);// 覆盖层基础配置setStyleSheet("background: transparent;"); // 背景透明setMouseTracking(true);                    // 开启鼠标追踪(未按下也触发move)//setAttribute(Qt::WA_TransparentForMouseEvents, false); // 不忽略鼠标事件// 鼠标事件穿透,确保底层组件可交互setAttribute(Qt::WA_TransparentForMouseEvents);//创建两个按钮,指定当前窗口为父对象if(btn1 == nullptr){btn1 = new QPushButton(QString("btn1"), this);}if(btn2 == nullptr){btn2 = new QPushButton(QString("btn2"), this);}btn1->setFixedSize (80, 30);btn2->setFixedSize (80, 30);btn1->move(10, 20);QPoint absolutePos = btn1->mapToGlobal(QPoint(0, 0));btn2->move(absolutePos.x() + btn1->width()+40, absolutePos.y());btn1->setStyleSheet(QString("color: rgba(66, 66, 66, 1);background-color: rgba(99, 99, 99,0.6);"));btn2->setStyleSheet(QString("color: rgba(66, 66, 66, 1);background-color: rgba(99, 99, 99,0.6);"));}
void OverlayWidget::addConnection(QList<QWidget*> fromWidget)
{foreach(auto widget, fromWidget){if(widget != nullptr)m_lstFromWidget.append(widget);}update();
}void OverlayWidget::updatePageShow()
{update();
}
// 绘制核心:跨页面线条→普通线条→临时线条→标注
void OverlayWidget::paintEvent(QPaintEvent *event)
{QWidget::paintEvent(event);QPainter painter(this);// 启用抗锯齿,使线条更平滑painter.setRenderHint(QPainter::Antialiasing, true);// 设置虚线样式QPen pen(Qt::white, 2, Qt::DashLine);int nSize = m_lstFromWidget.size();QColor color = QColor(117,117,117);pen.setColor(color);int nLabel = 0;QWidget *toWidget = btn1;for(int n = nSize - 1; n >= 0; n--){if(!m_lstFromWidget[n]->isVisible())continue;QWidget *fromWidget = m_lstFromWidget[n];// 只有当两个组件都可见时才绘制连接线if (fromWidget->isVisible() && toWidget->isVisible() && m_parent){// 计算起点QPoint fromPoint = fromWidget->mapTo(m_parent, QPoint(fromWidget->width()/2, fromWidget->height()/2));// 计算终点(转换到覆盖层坐标系)QPoint toPoint = toWidget->mapTo(m_parent, QPoint(toWidget->width(), toWidget->height()/2));// 绘制连接线painter.setPen(pen);painter.drawLine(fromPoint, toPoint);painter.setPen(Qt::NoPen);// 绘制起点圆形标记// 外圆painter.setBrush(Qt::white);painter.drawEllipse(fromPoint, 8, 8);// 内圆painter.setBrush(color);painter.drawEllipse(fromPoint, 5, 5);// 绘制终点圆形标记painter.setBrush(Qt::white);painter.drawEllipse(toPoint, 8, 8);painter.setBrush(color);painter.drawEllipse(toPoint, 5, 5);}}
}

这里有几个点需要说明:
(1)覆盖层要设置忽略鼠标事件,以使得点击或移动被覆盖层的按钮时能响应;

// 鼠标事件穿透,确保底层组件可交互setAttribute(Qt::WA_TransparentForMouseEvents);

(2)当被覆盖的按钮移动时,要通知到覆盖层,使得连线能实时跟着刷新重绘;

// 核心函数:更新覆盖层跨页面线条
void MainWindow::updateOverlayCrossPageLines()
{// 通知覆盖层更新m_overlayWidget->updatePageShow();
}

6.3 功能验证与效果​

运行项目后,可实现以下核心效果:​
页面切换:点击 “设备页面”“传感器页面”,堆叠组件切换页面,覆盖层的跨页面线条始终显示,不随页面隐藏而消失;​
组件拖动:拖动 主界面的“设备 1” ,“传感器2”等按钮,线条实时跟随按钮移动;​
在这里插入图片描述

在这里插入图片描述

七、覆盖层常见问题与解决方案(工程化排查)​

在实际项目中,覆盖层可能出现 “不显示”“交互冲突”“页面切换异常” 等问题,以下是 6 个高频问题的原因分析与解决方案,帮你快速定位并解决问题。​

7.1 问题 1:覆盖层不显示,只看到下层组件​

  • 可能原因:​
    覆盖层的父组件设置错误(如父组件是stackedWidget的子页面,而非centralWidget);​
    覆盖层未调用raise(),层级低于其他组件;​
    覆盖层的styleSheet未设置background: transparent,但windowOpacity设为 0,导致完全透明;​
    覆盖层的geometry设置错误(如x=1000,超出主窗口范围)。​
  • 解决方案:​
    检查父组件:确保覆盖层的父组件是centralWidget或MainWindow,而非堆叠组件的子页面:
// 正确:父组件为centralWidget
m_overlayWidget = new OverlayWidget(ui->centralWidget);
// 错误:父组件为堆叠组件的子页面
// m_overlayWidget = new OverlayWidget(ui->page0);

强制提升层级:创建覆盖层后立即调用raise(),并在页面切换后重新调用:

m_overlayWidget->raise();
connect(ui->stackedWidget, &QStackedWidget::currentChanged, [this]() {m_overlayWidget->raise(); // 页面切换后重新置顶
});

验证透明度设置:确保背景透明且整体不透明:

m_overlayWidget->setStyleSheet("background: transparent;");
m_overlayWidget->setWindowOpacity(0.9); // 0.9表示90%不透明

检查几何区域:打印覆盖层的geometry,确保在主窗口范围内:

qDebug() << "覆盖层几何区域:" << m_overlayWidget->geometry();
qDebug() << "中心区域几何:" << ui->centralWidget->geometry();
// 确保覆盖层的geometry与中心区域一致
m_overlayWidget->setGeometry(ui->centralWidget->geometry());

7.2 问题 2:覆盖层遮挡下层组件,无法点击​

  • 可能原因:​
    覆盖层的Qt::WA_TransparentForMouseEvents属性设为false,且未处理穿透逻辑;​
    覆盖层的mousePressEvent中未调用event->ignore(),导致事件被拦截;​
    覆盖层的windowFlags设置了Qt::Window,成为顶层窗口,遮挡所有组件。​
  • 解决方案:​
    开启条件穿透:在mousePressEvent中判断是否点击绘制内容,非绘制区域则穿透:
void OverlayWidget::mousePressEvent(QMouseEvent *event)
{if (isClickOnDrawContent(event->pos())) {// 处理覆盖层逻辑} else {event->ignore(); // 穿透事件到下层组件}
}

正确设置窗口标志:去除Qt::Window标志,确保覆盖层是子组件:

// 正确:子组件标志
m_overlayWidget->setWindowFlags(Qt::Widget | Qt::FramelessWindowHint);
// 错误:顶层窗口标志
// m_overlayWidget->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);

临时测试:将Qt::WA_TransparentForMouseEvents设为true,验证是否能点击下层组件:

m_overlayWidget->setAttribute(Qt::WA_TransparentForMouseEvents, true);
// 若能点击,说明穿透逻辑未正确实现,需重新处理mousePressEvent

7.3 问题 3:页面切换后,覆盖层线条消失​

  • 可能原因:​
    覆盖层的线条数据存储在堆叠组件的子页面中,页面隐藏时数据被销毁;​
    跨页面线条的坐标计算依赖当前显示页面的组件,隐藏页面的坐标获取错误;​
    页面切换时未触发覆盖层重绘,线条未重新绘制。​
  • 解决方案:​
    全局存储线条数据:将线条数据存储在MainWindow或全局单例中,而非子页面:
// 正确:在MainWindow中存储线条数据
QVector<QLineF> MainWindow::m_globalLines;
// 错误:在Page0Widget中存储线条数据(页面隐藏时可能被销毁)

获取隐藏组件坐标:即使页面隐藏,组件的pos()仍有效,直接计算坐标:

// 无需判断页面是否显示,直接获取组件坐标
QPoint btnPos = ui->btnDevice1->pos();
QPointF btnCenter = btnPos + QPointF(ui->btnDevice1->width()/2, ui->btnDevice1->height()/2);

页面切换时强制重绘:在stackedWidget的currentChanged信号中调用覆盖层的update():

connect(ui->stackedWidget, &QStackedWidget::currentChanged, [this]() {m_overlayWidget->update(); // 强制重绘覆盖层
});

7.4 问题 4:覆盖层绘制出现闪烁​

  • 可能原因:​
    未开启双缓冲绘图,复杂绘制时出现擦除 - 绘制的空白期;​
    频繁调用update(),导致绘制任务堆积;​
    覆盖层的paintEvent中执行了耗时操作(如读取文件、网络请求)。​
  • 解决方案:​
    开启双缓冲:手动实现双缓冲绘图(见 7.3 节);​
    批量更新:用定时器合并更新请求,减少update()调用次数(见 7.2 节);​
    移除耗时操作:确保paintEvent中只执行绘制逻辑,耗时操作移到其他线程:
void OverlayWidget::paintEvent(QPaintEvent *event)
{// 错误:在paintEvent中执行耗时操作// readDataFromFile(); // 正确:只执行绘制逻辑QPainter painter(this);// 绘制...
}

7.5 问题 5:组件拖动时,线条更新卡顿​

  • 可能原因:​
    组件拖动时每秒触发数十次mouseMoveEvent,每次都调用update(),导致绘制频繁;​
    线条更新时执行了全量重绘,而非局部重绘;​
    线条存储在QList中,遍历速度慢。​
  • 解决方案:​
    局部重绘:只重绘线条变化的区域(看前文 7.1 说明);​
    降低更新频率:在mouseMoveEvent中添加 “距离阈值”,只有当鼠标移动超过 5px 时才更新线条:
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{static QPoint lastMousePos;// 鼠标移动超过5px才更新线条if (qAbs((event->pos() - lastMousePos).manhattanLength()) > 5) {updateOverlayLines();lastMousePos = event->pos();}
}

7.6 问题 6:覆盖层在高 DPI 屏幕上绘制模糊​

  • 可能原因:​
    未开启高 DPI 支持,Qt 自动缩放导致绘制模糊;​
    绘制时使用整数坐标,高 DPI 下像素对齐错误;​
    未设置QPainter的devicePixelRatio,导致图像缩放比例错误。​
  • 解决方案:​
    开启高 DPI 支持:在main.cpp中添加高 DPI 配置:
#include <QApplication>
#include <QHighDpiScaleFactorRoundingPolicy>int main(int argc, char *argv[])
{QApplication a(argc, argv);// 开启高DPI支持QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);// 设置缩放策略(按屏幕比例)a.setHighDpiScaleFactorRoundingPolicy(QHighDpiScaleFactorRoundingPolicy::PassThrough);MainWindow w;w.show();return a.exec();
}

使用浮点数坐标:绘制时用QPointF和QLineF,避免整数坐标的像素对齐问题:

// 正确:浮点数坐标
QLineF line(QPointF(100.5, 200.5), QPointF(300.5, 400.5));
// 错误:整数坐标(高DPI下模糊)
// QLine line(QPoint(100, 200), QPoint(300, 400));

设置 QPainter 的设备像素比:在paintEvent中获取屏幕的设备像素比,设置给QPainter:

void OverlayWidget::paintEvent(QPaintEvent *event)
{QWidget::paintEvent(event);QPainter painter(this);// 获取设备像素比(高DPI屏幕可能为2.0或3.0)qreal dpr = devicePixelRatioF();painter.setWindow(0, 0, width() * dpr, height() * dpr);painter.setViewport(0, 0, width(), height());// 后续绘制逻辑...
}

八、总结:覆盖层技术的核心价值与扩展​

通过前面的练习说明,我们从 “基础原理” 到 “工程落地”,完整覆盖了 Qt 覆盖层的使用场景、实现方法、交互逻辑与性能优化。最后,我们梳理覆盖层的运用。​

8.1 覆盖层的核心价值​

  • 解耦复杂绘制与基础界面:将线条、标注等复杂绘制逻辑集中在覆盖层,基础界面(按钮、表格)只负责核心功能,代码耦合度降低 50% 以上;​
  • 突破组件层级限制:覆盖层可显示在所有组件上方,解决 “线条被遮挡”“跨页面连线” 等堆叠组件无法实现的需求;​
  • 灵活的交互扩展:支持鼠标、键盘、穿透交互,可快速实现 “线条编辑”“动态标注”“跨组件联动” 等功能;​
  • 低学习成本:基于 QWidget 和 QPainter,无需动用 QGraphicsView 等复杂框架。​

8.2 覆盖层的扩展方向​

  • 结合 Qt Quick:在 Qt Quick(QML)中,可通过Item作为覆盖层,配合Canvas实现绘制,适合移动端或高交互需求的界面;​
  • 3D 场景覆盖层:在 Qt 3D 中,通过QWidgetOverlay或QQuickWidget在 3D 场景上方添加 2D 覆盖层,实现 “3D 模型标注”“交互控件”;​
  • 多覆盖层分层管理:复杂项目中可创建多个覆盖层(如 “绘制层”“标注层”“交互层”),每层负责单一功能,便于维护;​
  • 硬件加速绘制:对于超大规模绘制(如数千条线条),可使用QOpenGLWidget作为覆盖层,利用 GPU 加速绘制,提升性能 3-5 倍。​

8.3 最终建议​

小项目 / 简单绘制:直接使用 QWidget 作为覆盖层,配合 QPainter 实现,开发效率最高;​
中大规模绘制:使用 QOpenGLWidget 作为覆盖层,开启硬件加速,避免卡顿;​
跨平台 / 移动端:优先使用 Qt Quick 的Canvas覆盖层,适配不同屏幕分辨率;​
长期维护项目:做好分层设计(基础层、覆盖层、数据层),并添加完整的注释和测试用例,便于后续迭代。​

总结:

覆盖层不是 Qt 的 “黑科技”,而是基于基础组件的 “巧思应用”。只要掌握其核心原理和优化方法,就能在工业控制、数据可视化、流程图设计等场景中,快速实现高质量的复杂界面。​

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

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

相关文章

神经网络构成框架-理论学习

一、神经网络的基本组成与分类 1.1 神经网络的核心组成部分 神经网络是现代人工智能的基石&#xff0c;其设计灵感来源于生物神经系统的信息处理方式。作为工程师&#xff0c;了解神经网络的基本组成部分对于构建和优化模型至关重要。一个典型的神经网络主要由以下几个关键部分…

从0开始开发app(AI助手版)-架构及环境搭建

架构选择 前端React Native 后端Firebase 原因 环境准备 安装node 安装JDK 命令行工具&#xff1a;Node.js command prompt命令行查询Javav版本&#xff1a;javac -version使用nrm工具切换淘宝源&#xff1a;npx nrm use taobao安装yarn&#xff0c;替代npm下载工具&#x…

【性能测试】Jmeter工具快速上手-搭建压力测试脚本

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 概念 性能测试是软件测试的重要分支&#xff0c;核心目标是通过模拟真实业务场景和负载压力&#xff0c;评估系统在不同条件下的性能表现&#xff0c;发现系统性…

oracle里的int类型

oracle里的int类型 在 ANSI SQL 标准 中&#xff0c;INTEGER 和 SMALLINT 是定义好的精确数值类型&#xff0c;但它们的 “长度”或“大小”并不是通过 (N) 括号来指定的&#xff08;如 INT(4)&#xff09;&#xff0c;这一点与 MySQL 等数据库的非标准扩展完全不同。 SMALLI…

前端学习之后端java小白(二)-sql约束/建表

一、约束SQL约束&#xff08;Constraints&#xff09;是用于限制表中数据的规则&#xff0c;确保数据的完整性和准确性。以下是主要的SQL约束类型&#xff1a; 主要约束类型&#xff1a; 1. NOT NULL 约束: 确保列不能包含空值 CREATE TABLE users (id INT NOT NULL,name VARC…

OpenCV:图像金字塔

文章目录一、什么是图像金字塔&#xff1f;二、图像金字塔的核心操作&#xff1a;采样与逆采样1. 向下采样&#xff08;pyrDown&#xff09;&#xff1a;从高分辨率到低分辨率步骤1&#xff1a;高斯滤波步骤2&#xff1a;删除偶数行与偶数列OpenCV实战代码效果特点2. 向上采样&…

LVS与Keepalived详解(一)负载均衡集群介绍

文章目录前言一、什么是LVS&#xff1f;二、四层&#xff08;L4&#xff09;负载均衡的最佳解决方案是什么&#xff1f;2.1解决方案分类与对比&#xff08;负载均衡的三种方式介绍&#xff09;2.1.1 硬件负载均衡 (Hardware Load Balancer)2.1.2 软件负载均衡 (Software Load B…

消息队列-kafka完结

基本概念和操作 基本概念 简单概念:::color4 Broker&#xff1a;如果将kafka比喻成数据仓库网络&#xff0c;那么Broker就是网络中的仓库节点&#xff0c;比如快递站&#xff0c;在该节点内可以独立运行&#xff0c;并且多个Broker可以连接起来&#xff0c;构建kafka集群Topic&…

Chromium 138 编译指南 Windows篇:环境变量配置与构建优化(三)

引言配置&#xff0c;往往决定成败。在软件开发的世界里&#xff0c;环境变量就像是一位无声的指挥家&#xff0c;默默地协调着各个组件的协同工作。对于Chromium 138这样一个拥有数千万行代码的超大型项目而言&#xff0c;正确的环境变量配置更是编译成功的关键所在。也许您曾…

LabVIEW加载 STL 模型至 3D 场景 源码见附件

LabVIEW 中 STL 模型的导入与 3D 场景显示&#xff0c;基于示例代码逻辑&#xff0c;结合格式兼容性、功能实现步骤及多样化显示方式&#xff0c;适用于三维可视化温控、机械零件模拟等场景。 1示例代码 NI 社区案例 “Add an STL file to 3D scene using LabVIEW” 提供了经…

硅基计划3.0 Map类Set类

文章目录一、二叉搜索树&#xff08;排序树&#xff09;1. 概念初识2. 模拟实现1. 创建搜索树节点2. 查找指定元素是否存在3. 插入4. 删除二、Map类1. put——设置单词以及其频次2. get——获取单词频次3. getOrDefault——获取单词频次或返回默认值4. remove——删除单词频次信…

LeetCode 刷题【73. 矩阵置零】

73. 矩阵置零 自己做 解&#xff1a;标记消除 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {vector<bool> x(matrix.size(), false); //要置0的行vector<bool> y(matrix[0].size(), false); //…

Unity学习----【进阶】TextMeshPro学习(一)--基础知识点

来源于唐老狮的视频教学&#xff0c;仅作记录和感悟记录&#xff0c;方便日后复习或者查找 一.导入TextMeshPro 对于新创建的工程&#xff0c;可以直接在这里导入TMP必要的资源&#xff08;上面&#xff09;&#xff0c;以及TMP的实例和扩展&#xff08;下面&#xff09; 导入之…

BigDecimal(用于处理超出double范围的浮点数)

BigDecimal 是 Java 中 java.math 包提供的高精度十进制浮点数类&#xff0c;专为解决基本类型&#xff08;float/double&#xff09;的精度缺陷而设计&#xff0c;广泛用于金融、科学计算等对精度要求极高的场景。以下从核心特性、使用方法、常见问题对比、注意事项等方面详细…

Nginx 优化

文章目录1、隐藏版本号2、修改用户与组3、缓存时间4、日志切割5、连接超时6、更改进程数7、配置网页8、防盗链1、隐藏版本号 隐藏nginx的版本号&#xff0c;为了防止恶意用户利用已知漏洞进行攻击 ## 查看版本号 curl -I http://192.168.10.23方法一&#xff1a;修改配置文件…

基于多模态与主动学习的车船飞机图像识别系统研究与应用技术方案

技术方案 一、技术背景与研究现状 图像识别是计算机视觉的核心任务之一&#xff0c;随着深度学习的发展&#xff0c;基于 卷积神经网络&#xff08;CNN&#xff09; 与 视觉Transformer&#xff08;ViT&#xff09; 的图像分类方法已成为主流。 根据《图像分类技术选型——截止…

Word2Vec词嵌入技术和动态词嵌入技术

Word2Vec&#xff08;Word to Vector&#xff09;是 2013 年由 Google 团队提出的无监督词嵌入模型&#xff0c;是一种静态词嵌入技术&#xff0c;核心目标是将自然语言中的离散词汇映射为低维、稠密的实数向量&#xff08;即 “词向量”&#xff09;&#xff0c;让向量空间的距…

Netty从0到1系列之Netty逻辑架构【上】

文章目录一、Netty逻辑架构【上】1.1 网络通信层1.1.1 BootStrap & ServerBootStrap1. ✅核心方法链与配置2. ✅ 架构与流程3. ✅ 底层实现与原理分析4. ✅ 实践经验与总结1.1.2 Channel1.2 事件调度层1.2.1 事件调度层概述1.2.2 EventLoop【事件循环】1.2.3 EventLoopGrou…

Spring Cloud 高频面试题详解(含代码示例与深度解析)

文章目录Spring Cloud 高频面试题详解&#xff08;含代码示例与深度解析&#xff09;1. 什么是 Spring Cloud&#xff1f;它与 Spring Boot 有什么关系&#xff1f;2. 服务发现&#xff1a;Eureka 和 Nacos 的区别与选型&#xff1f;Eureka 示例与原理Eureka vs Nacos 对比表3.…

Ascend310B重构驱动run包

在Atlas 200I AI加速模块(Ascend310B)移植过程中如需要将自己编译的Image、dt.img及内核模块打包到启动镜像包中需要对"Ascend-hdk-310b-npu-driver-soc_<version>_linux-aarch64.run"(下面统称驱动run包)进行重构。下面将介绍如何重构run包。 重构驱动run包需…