PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示(二)
PyQt 实现飞控仪表盘:姿态、高度、速度,飞行数据实时显示(二)
- 一、说明
- 二、源码分享
-
- 1、效果展示
- 2、源码分享
-
- 2.1、alt_widget.py
- 2.2、asi_widget.py
- 2.3、hsi_widget.py
- 2.4、main.py
- 2.5、库安装
- 2.6、源码及资源下载
- 三、实现原理
-
- 1、 核心作用与特点
-
- 1.1、 虚拟坐标系
- 1.2 、图形项容器
- 1.3、 事件传播与信号
- 2、 在仪表示例中的关键用法
-
- 2.1、 创建与设置
- 2.2 、添加图形项
- 2.3、 场景更新与渲染
- 2.4 、场景变换与视图适配
- 2.5 、坐标转换
- 3、 常用方法总结
- 4、性能与最佳实践
- 5、总结
一、说明
由于篇幅限制此篇只显示3个仪表,剩余见下一篇
二、源码分享
1、效果展示



2、源码分享
2.1、alt_widget.py
import math
from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
from PyQt6.QtGui import QTransform
class Alt(QGraphicsView):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
# 固定原始场景尺寸240*240,统一坐标系
self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
self.setScene(self.m_scene)
self.m_scene.clear()
# 彻底关闭滚动条、边框、留白边距
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setFrameStyle(0)
self.setViewportMargins(0, 0, 0, 0)
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
# SVG图元对象
self.m_itemFace_1 = None
self.m_itemFace_2 = None
self.m_itemFace_3 = None
self.m_itemHand_1 = None
self.m_itemHand_2 = None
self.m_itemCase = None
# 仪表参数
self.m_altitude = 0.0
self.m_pressure = 28.0
# 缩放系数
self.m_scaleX = 1.0
self.m_scaleY = 1.0
# 基础常量
self.m_originalAltCtr = QPointF(120.0, 120.0)
self.m_originalHeight = 240
self.m_originalWidth = 240
# Z层级(和C++完全一致,渲染堆叠顺序)
self.m_face1Z = -50
self.m_face2Z = -40
self.m_face3Z = -30
self.m_hand1Z = -20
self.m_hand2Z = -10
self.m_caseZ = 10
self.init()
def reinit(self):
if self.m_scene:
self.m_scene.clear()
self.init()
def update(self):
self.updateView()
# 对外接口:设置高度
def setAltitude(self, altitude: float):
self.m_altitude = altitude
# 对外接口:设置气压基准,限幅28.0 ~ 31.5
def setPressure(self, pressure: float):
self.m_pressure = pressure
if self.m_pressure < 28.0:
self.m_pressure = 28.0
if self.m_pressure > 31.5:
self.m_pressure = 31.5
def resizeEvent(self, event):
super().resizeEvent(event)
self.reinit()
def init(self):
self.reset()
w = self.width()
h = self.height()
self.m_scaleX = w / self.m_originalWidth
self.m_scaleY = h / self.m_originalHeight
# 加载所有SVG图层,原始240画布,不手动缩放
self.m_itemFace_1 = QGraphicsSvgItem("images/alt/alt_face_1.svg")
self.m_itemFace_1.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemFace_1.setZValue(self.m_face1Z)
self.m_itemFace_1.setTransformOriginPoint(self.m_originalAltCtr)
self.m_scene.addItem(self.m_itemFace_1)
self.m_itemFace_2 = QGraphicsSvgItem("images/alt/alt_face_2.svg")
self.m_itemFace_2.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemFace_2.setZValue(self.m_face2Z)
self.m_scene.addItem(self.m_itemFace_2)
self.m_itemFace_3 = QGraphicsSvgItem("images/alt/alt_face_3.svg")
self.m_itemFace_3.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemFace_3.setZValue(self.m_face3Z)
self.m_itemFace_3.setTransformOriginPoint(self.m_originalAltCtr)
self.m_scene.addItem(self.m_itemFace_3)
self.m_itemHand_1 = QGraphicsSvgItem("images/alt/alt_hand_1.svg")
self.m_itemHand_1.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemHand_1.setZValue(self.m_hand1Z)
self.m_itemHand_1.setTransformOriginPoint(self.m_originalAltCtr)
self.m_scene.addItem(self.m_itemHand_1)
self.m_itemHand_2 = QGraphicsSvgItem("images/alt/alt_hand_2.svg")
self.m_itemHand_2.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemHand_2.setZValue(self.m_hand2Z)
self.m_itemHand_2.setTransformOriginPoint(self.m_originalAltCtr)
self.m_scene.addItem(self.m_itemHand_2)
self.m_itemCase = QGraphicsSvgItem("images/alt/alt_case.svg")
self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemCase.setZValue(self.m_caseZ)
self.m_scene.addItem(self.m_itemCase)
# Qt原生自动居中、等比例放大,替代旧centerOn
self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
self.updateView()
def reset(self):
# 清空图元指针
self.m_itemFace_1 = None
self.m_itemFace_2 = None
self.m_itemFace_3 = None
self.m_itemHand_1 = None
self.m_itemHand_2 = None
self.m_itemCase = None
# 重置参数默认值
self.m_altitude = 0.0
self.m_pressure = 28.0
def updateView(self):
# 复刻C++ std::ceil(m_altitude + 0.5) 四舍五入取整
altitude_int = math.ceil(self.m_altitude + 0.5)
# 完全一致的旋转角度计算公式
angleH1 = self.m_altitude * 0.036
angleH2 = (altitude_int % 1000) * 0.36
angleF1 = (self.m_pressure - 28.0) * 100.0
angleF3 = self.m_altitude * 0.0036
# 分配旋转角度
self.m_itemHand_1.setRotation(angleH1)
self.m_itemHand_2.setRotation(angleH2)
self.m_itemFace_1.setRotation(-angleF1)
self.m_itemFace_3.setRotation(angleF3)
self.m_scene.update()
2.2、asi_widget.py
from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
class Asi(QGraphicsView):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
# 固定原始场景尺寸240*240
self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
self.setScene(self.m_scene)
self.m_scene.clear()
# 关闭滚动条、边框、边距
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setFrameStyle(0)
self.setViewportMargins(0, 0, 0, 0)
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
# SVG图元
self.m_itemFace = None
self.m_itemHand = None
self.m_itemCase = None
# 空速参数
self.m_airspeed = 0.0
# 缩放基础参数
self.m_scaleX = 1.0
self.m_scaleY = 1.0
self.m_originalAsiCtr = QPointF(120.0, 120.0)
self.m_originalHeight = 240
self.m_originalWidth = 240
# Z渲染层级(和C++完全一致)
self.m_faceZ = -20
self.m_handZ = -10
self.m_caseZ = 10
self.init()
def reinit(self):
if self.m_scene:
self.m_scene.clear()
self.init()
def update(self):
self.updateView()
# 对外接口:空速限幅 0 ~ 235
def setAirspeed(self, airspeed: float):
self.m_airspeed = airspeed
if self.m_airspeed < 0.0:
self.m_airspeed = 0.0
if self.m_airspeed > 235.0:
self.m_airspeed = 235.0
def resizeEvent(self, event):
super().resizeEvent(event)
self.reinit()
def init(self):
self.reset()
w = self.width()
h = self.height()
self.m_scaleX = w / self.m_originalWidth
self.m_scaleY = h / self.m_originalHeight
# 加载SVG图层,原始240画布,不手动缩放
self.m_itemFace = QGraphicsSvgItem("images/asi/asi_face.svg")
self.m_itemFace.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemFace.setZValue(self.m_faceZ)
self.m_scene.addItem(self.m_itemFace)
self.m_itemHand = QGraphicsSvgItem("images/asi/asi_hand.svg")
self.m_itemHand.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemHand.setZValue(self.m_handZ)
self.m_itemHand.setTransformOriginPoint(self.m_originalAsiCtr)
self.m_scene.addItem(self.m_itemHand)
self.m_itemCase = QGraphicsSvgItem("images/asi/asi_case.svg")
self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemCase.setZValue(self.m_caseZ)
self.m_scene.addItem(self.m_itemCase)
# Qt原生自动居中、等比例适配
self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
self.updateView()
def reset(self):
self.m_itemFace = None
self.m_itemHand = None
self.m_itemCase = None
self.m_airspeed = 0.0
def updateView(self):
# 复刻C++分段角度计算公式
if self.m_airspeed < 40.0:
angle = 0.9 * self.m_airspeed
elif self.m_airspeed < 70.0:
angle = 36.0 + 1.8 * (self.m_airspeed - 40.0)
elif self.m_airspeed < 130.0:
angle = 90.0 + 2.0 * (self.m_airspeed - 70.0)
elif self.m_airspeed < 160.0:
angle = 210.0 + 1.8 * (self.m_airspeed - 130.0)
else:
angle = 264.0 + 1.2 * (self.m_airspeed - 160.0)
self.m_itemHand.setRotation(angle)
self.m_scene.update()
2.3、hsi_widget.py
from PyQt6.QtWidgets import QGraphicsView, QWidget, QGraphicsScene
from PyQt6.QtSvgWidgets import QGraphicsSvgItem
from PyQt6.QtCore import QPointF, Qt, QRectF
class Hsi(QGraphicsView):
def __init__(self, parent: QWidget = None):
super().__init__(parent)
# 固定原始画布尺寸240×240
self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
self.setScene(self.m_scene)
self.m_scene.clear()
# 彻底关闭滚动条、控件边框、留白边距
self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.setFrameStyle(0)
self.setViewportMargins(0, 0, 0, 0)
self.setTransformationAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
self.setResizeAnchor(QGraphicsView.ViewportAnchor.NoAnchor)
# SVG图元对象
self.m_itemFace = None
self.m_itemCase = None
# 航向参数
self.m_heading = 0.0
# 基础常量
self.m_scaleX = 1.0
self.m_scaleY = 1.0
self.m_originalHsiCtr = QPointF(120.0, 120.0)
self.m_originalHeight = 240
self.m_originalWidth = 240
# Z渲染层级(和C++完全一致)
self.m_faceZ = -20
self.m_caseZ = 10
self.init()
def reinit(self):
if self.m_scene:
self.m_scene.clear()
self.init()
def update(self):
self.updateView()
# 对外接口:设置航向角
def setHeading(self, heading: float):
self.m_heading = heading
def resizeEvent(self, event):
super().resizeEvent(event)
self.reinit()
def init(self):
self.reset()
w = self.width()
h = self.height()
self.m_scaleX = w / self.m_originalWidth
self.m_scaleY = h / self.m_originalHeight
# 加载SVG,原始240画布,不手动缩放矩阵
self.m_itemFace = QGraphicsSvgItem("images/hsi/hsi_face.svg")
self.m_itemFace.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemFace.setZValue(self.m_faceZ)
self.m_itemFace.setTransformOriginPoint(self.m_originalHsiCtr)
self.m_scene.addItem(self.m_itemFace)
self.m_itemCase = QGraphicsSvgItem("images/hsi/hsi_case.svg")
self.m_itemCase.setCacheMode(QGraphicsSvgItem.CacheMode.NoCache)
self.m_itemCase.setZValue(self.m_caseZ)
self.m_scene.addItem(self.m_itemCase)
# Qt原生自动居中、等比例放大,替代旧centerOn
self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
self.updateView()
def reset(self):
self.m_itemFace = None
self.m_itemCase = None
self.m_heading = 0.0
def updateView(self):
# 航向刻度盘反向旋转
self.m_itemFace.setRotation(-self.m_heading)
self.m_scene.update()
2.4、main.py
import sys
import math
from PyQt6.QtWidgets import QApplication, QMainWindow, QWidget, QGridLayout
from PyQt6.QtCore import QTimer, QElapsedTimer
from adi_widget import Adi
from alt_widget import Alt
from asi_widget import Asi
from hsi_widget import Hsi
from tc_widget import Tc
from nav_widget import Nav
from vsi_widget import Vsi
class MainWindow(QMainWindow):
# 全局常量统一管理
REFRESH_INTERVAL = 30
GRID_SPACING = 6
def __init__(self):
super().__init__()
self.setWindowTitle("多仪表独立布局仿真 3行3列")
self.resize(1400, 1000)
central_widget = QWidget()
self.setCentralWidget(central_widget)
grid = QGridLayout(central_widget)
grid.setSpacing(self.GRID_SPACING)
# 3列均分
for col in range(3):
grid.setColumnStretch(col, 1)
# 3行均分
for row in range(3):
grid.setRowStretch(row, 1)
# ====================== 9个独立仪表实例 w0~w8(包含w6 w7) ======================
# 第一行
self.w0 = Tc() # 0行0列
self.w1 = Nav() # 0行1列
self.w2 = Adi() # 0行2列
# 第二行
self.w3 = Alt() # 1行0列
self.w4 = Asi() # 1行1列
self.w5 = Hsi() # 1行2列
# 第三行(新增 w6 w7 w8)
self.w6 = Vsi() # 2行0列 垂直速度表
self.w7 = QWidget() # 2行1列 转弯协调仪
self.w8 = QWidget() # 2行2列 HSI航向仪
# 网格摆放
grid.addWidget(self.w0, 0, 0)
grid.addWidget(self.w1, 0, 1)
grid.addWidget(self.w2, 0, 2)
grid.addWidget(self.w3, 1, 0)
grid.addWidget(self.w4, 1, 1)
grid.addWidget(self.w5, 1, 2)
grid.addWidget(self.w6, 2, 0)
grid.addWidget(self.w7, 2, 1)
grid.addWidget(self.w8, 2, 2)
# 计时与定时器
self.time_counter = QElapsedTimer()
self.time_counter.start()
self.real_time = 0.0
self.timer = QTimer(self)
self.timer.setInterval(self.REFRESH_INTERVAL)
self.timer.timeout.connect(self.timer_event)
self.timer.start()
def timer_event(self):
# 计算帧时间步长
step_ms = self.time_counter.restart()
self.real_time += step_ms / 1000.0
# TC仪表共用动态参数
slip_skid = 1.0 * math.sin(self.real_time / 10.0)
turn_rate = 7.0 * math.sin(self.real_time / 10.0)
# w0 TC
self.w0.setSlipSkid(slip_skid * 15.0)
self.w0.setTurnRate(turn_rate)
self.w0.update()
# Nav仪表(w1)动态参数,消除固定000文字
heading = 360.0 * math.sin(self.real_time / 40.0)
adf = -360.0 * math.sin(self.real_time / 50.0)
dev_h = 1.0 * math.sin(self.real_time / 20.0)
dme = 99.0 * math.sin(self.real_time / 100.0)
heading_bug = heading * 0.7
course = heading * 0.5
self.w1.setHeading(heading)
self.w1.setHeadingBug(heading_bug)
self.w1.setCourse(course)
self.w1.setBearing(adf, True)
self.w1.setDeviation(dev_h, True)
self.w1.setDistance(dme, True)
self.w1.update()
# ADI w2
roll = 180.0 * math.sin(self.real_time / 10.0)
pitch = 90.0 * math.sin(self.real_time / 20.0)
self.w2.setPitch(pitch)
self.w2.setRoll(roll)
self.w2.update()
# ALT w3
altitude = 9000.0 * math.sin(self.real_time / 40.0) + 9000.0
pressure = 2.0 * math.sin(self.real_time / 20.0) + 30.0
self.w3.setAltitude(altitude)
self.w3.setPressure(pressure)
self.w3.update()
# ASI w4
airspeed = 125.0 * math.sin(self.real_time / 40.0) + 125.0
self.w4.setAirspeed(airspeed)
self.w4.update()
# HSI w5
self.w5.setHeading(heading)
self.w5.update()
# ===================== 新增w6 VSI垂直速度 =====================
climb_rate = 1800 * math.sin(self.real_time / 20.0)
self.w6.setClimbRate(climb_rate)
self.w6.update()
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())
2.5、库安装

2.6、源码及资源下载
见文章顶部绑定资源
三、实现原理
QGraphicsScene 是 PyQt6 图形视图框架 (QGraphicsView, QGraphicsItem) 的核心组件之一,它充当一个虚拟的画布,用于管理和渲染大量的 2D 图形项(QGraphicsItem)。在本文的仪表盘示例中,我们正是通过 QGraphicsScene 来组织和管理所有 SVG 仪表元件(背景、指针、刻度、文字等)的。
1、 核心作用与特点
1.1、 虚拟坐标系
QGraphicsScene 使用一个虚拟的、与设备无关的坐标系(默认为浮点精度)。你可以为场景设置任意大小的矩形区域作为其边界(通过构造函数或 setSceneRect)。在我们的代码中,每个仪表都创建了一个固定尺寸的场景:
-
Tc (转弯协调仪):
QRectF(0, 0, 240, 240) -
Nav (导航仪):
QRectF(0, 0, 300, 300) -
Adi (姿态指示器):
QRectF(0, 0, 240, 240)
这个虚拟坐标系是图形项定位、变换和碰撞检测的基础。
1.2 、图形项容器
QGraphicsScene 是一个容器,可以添加、删除、查找和管理成千上万的 QGraphicsItem 对象。它负责:
-
维护图形项列表(通过
addItem()、removeItem()、items()等方法)。 -
处理图形项的 Z 值(深度)排序,决定绘制顺序(
setZValue())。 -
提供场景级别的查询,如
itemAt()、items()(按点、矩形、多边形等查找)。
1.3、 事件传播与信号
场景接收来自视图 (QGraphicsView) 的事件(如鼠标点击、移动、键盘事件),并将其转发给位于相应位置的图形项。它还提供了一系列信号,用于通知场景内容的变化,例如:
-
changed():场景内容(如图形项添加、删除、移动、变换)发生变化时发射。 -
selectionChanged():场景中图形项的选择状态改变时发射。
2、 在仪表示例中的关键用法
2.1、 创建与设置
# 创建场景,并指定其矩形区域(原点在左上角,宽高分别为 240 和 240)
self.m_scene = QGraphicsScene(QRectF(0, 0, 240, 240), self)
self.setScene(self.m_scene) # 将场景设置给 QGraphicsView
-
参数
parent:通常传入self(即QGraphicsView),这样场景的生命周期将由视图管理。 -
场景矩形 (
sceneRect):定义了场景的逻辑边界。图形项可以放置在此矩形内或之外,但视图通常只会显示场景矩形内的部分(除非手动调整)。
2.2 、添加图形项
我们使用 QGraphicsSvgItem(用于 SVG 矢量图)和 QGraphicsTextItem(用于文本)作为图形项,并通过 addItem() 将它们添加到场景中。
self.m_itemBack = QGraphicsSvgItem("images/tc/tc_back.svg")
self.m_itemBack.setZValue(self.m_backZ) # 设置 Z 值,控制绘制顺序
self.m_scene.addItem(self.m_itemBack) # 将项添加到场景
Z 值管理:通过 setZValue() 控制项的堆叠顺序。值越小,越先绘制(在底层);值越大,越后绘制(在顶层)。在仪表中,我们精心安排了 Z 值:
-
背景 (
m_backZ = -70):最底层。 -
指针/刻度 (
m_markZ = -30):中间层。 -
外壳/表盘 (
m_caseZ = 10):最顶层,覆盖其他元素边缘。
2.3、 场景更新与渲染
当图形项的状态(如位置、旋转、可见性)发生变化时,需要通知场景进行重绘。
-
局部更新:调用图形项自身的
update()方法,标记该项的矩形区域为脏区,触发局部重绘。 -
全局更新:调用
self.m_scene.update()(如Nav.updateView末尾),强制重绘整个场景或所有脏区。
在我们的代码中,updateView() 方法会更新所有图形项的变换(旋转、平移),然后调用 self.m_scene.update() 以确保界面刷新。
2.4 、场景变换与视图适配
QGraphicsScene 本身不负责显示,显示由 QGraphicsView 完成。视图通过 setScene() 与场景关联。为了使场景内容自适应视图大小并保持比例,我们使用了:
# 在 init() 中,加载所有项后调用
self.fitInView(self.m_scene.sceneRect(), Qt.AspectRatioMode.KeepAspectRatio)
fitInView() 是 QGraphicsView 的方法,它会自动计算变换矩阵,将整个场景矩形适配到视图的视口内,并保持宽高比(KeepAspectRatio)。这是实现仪表等比例缩放、居中显示的关键。
2.5 、坐标转换
由于场景使用虚拟坐标,而视图显示在屏幕像素坐标中,经常需要进行坐标转换:
-
场景坐标 → 视图坐标:
QGraphicsView.mapFromScene() -
视图坐标 → 场景坐标:
QGraphicsView.mapToScene()
在 Nav.repositionText() 中,我们使用了 self.viewportTransform() 获取视图的变换矩阵,从而计算当前缩放比例,动态调整文本位置和字体大小,确保文本始终位于仪表上的正确位置。
3、 常用方法总结
| 方法 | 说明 | 示例 |
|---|---|---|
addItem(item) |
向场景添加一个图形项。 | self.m_scene.addItem(self.m_itemBack) |
removeItem(item) |
从场景移除一个图形项。 | self.m_scene.removeItem(self.m_itemBack) |
clear() |
移除场景中所有图形项。 |
self.m_scene.clear()(在 reinit() 中使用) |
items() |
返回场景中所有图形项的列表(可选排序)。 | items = self.m_scene.items() |
itemAt(pos, deviceTransform) |
返回位于指定场景位置的顶层图形项。 | item = self.m_scene.itemAt(QPointF(100,100)) |
setSceneRect(rect) |
设置场景的矩形区域。 | self.m_scene.setSceneRect(QRectF(0,0,500,500)) |
sceneRect() |
返回当前场景矩形。 | rect = self.m_scene.sceneRect() |
update(rect=QRectF()) |
安排场景矩形区域(默认整个场景)重绘。 | self.m_scene.update() |
invalidate(rect, layers) |
使场景的指定区域和图层无效,强制重绘。 | self.m_scene.invalidate() |
4、性能与最佳实践
-
批量操作:当需要添加/删除大量图形项时,可以考虑使用
QGraphicsScene的begin()/end()块(或QGraphicsScene.blockSignals(True))来暂时阻止信号发射,提高性能。 -
缓存模式:对于静态或较少变化的图形项(如仪表背景),可以设置缓存模式以提高渲染性能:
self.m_itemBack.setCacheMode(QGraphicsSvgItem.CacheMode.DeviceCoordinateCache) -
场景矩形优化:将
sceneRect设置为刚好包含所有图形项的最小矩形,可以减少不必要的绘制和事件处理。 - 避免频繁 clear():在动态更新的应用中,如果图形项频繁变化,尽量复用已有的图形项(更新其属性),而不是清空场景后重新添加。
5、总结
在本文的 PyQt6 仪表盘项目中,QGraphicsScene 扮演了后台舞台的角色:
- 它定义了每个仪表的虚拟画布尺寸(240×240 或 300×300)。
- 它托管了所有 SVG 图元和文本项,并通过 Z 值管理它们的绘制顺序。
- 它与
QGraphicsView配合,通过fitInView实现了自适应缩放和居中。 - 它提供了坐标转换、事件转发、场景更新等基础设施,使得复杂的 2D 图形界面能够高效、灵活地构建和更新。
理解 QGraphicsScene 是掌握 PyQt6 图形视图框架的关键一步。通过将图形项组织在场景中,你可以轻松实现复杂的交互式图形应用,如本文的航空仪表盘、绘图软件、流程图编辑器等。
