QGraphicsView(视图)、QGraphicsScene(场景)与 QGraphicsItem(图元)共同构成 MVC(Model-View-Controller)模式的图形渲染体系。
一、MVC模式简介
1. 角色分工:
MVC 的核心是数据(Model)- 视图(View)- 控制器(Controller) 分离,三者在 Qt 图形框架中的对应关系:
- QGraphicsItem(图元)= Model(模型):图元是显示的基本单元,它可以是一个圆、一个多边形、一条直线,或者是一个图片,它承载着图形的核心数据与状态(比如坐标、形状、颜色、是否可交互、像素数据等),是 "要渲染的内容本身",不关心自己如何被显示、在哪显示。
- QGraphicsView(视图)= View(视图):负责将场景中的内容 "可视化呈现" 给用户,相当于 "窗口 / 取景器"------ 它不存储图形数据,只负责渲染、缩放、平移、接收用户输入(鼠标 / 键盘),并把输入传递给场景。
- QGraphicsScene(场景)= 核心中介(Controller + 数据容器) :场景是 "控制器 + 数据桥梁" 的复合角色:
- 作为 Controller:转发 View 的用户输入到对应的 Item,协调 Item 的交互(比如碰撞检测、事件分发);
- 作为容器:管理所有 Item 的存储、布局、层级,为 View 提供统一的渲染数据源。
-
核心协作逻辑
-
数据层:创建各种Item(如矩形、直线、椭圆、图片、自定义图形),定义它们的形状、行为(比如点击后的响应);
-
管理层:把 Item 添加到Scene后,Scene负责维护这些 Item 的集合,处理 Item 之间的关系(比如叠层、碰撞、集合位置);
-
展示层:把 Scene 绑定到View,View 从 Scene 中 "取出" Item 的数据,按照设定的显示方式,比如特定的放大比例和坐标位置等,渲染到屏幕上;用户在View操作 (比如点击、拖拽、鼠标滚轮)时,View 再把事件交给 Scene,Scene 再分发给对应的 Item处理,并将处理结果按需在View反馈显示。
-
核心优势(MVC 分离的价值)
- 解耦:Item 只关心 "自身数据 / 行为",View 只关心 "怎么显示",Scene 只关心 "协调 / 管理";比如换一个 View(如缩放、旋转视图、移动),Item 和 Scene 无需修改;
- 复用:一个 Scene 可以绑定多个 View(比如同一套图形数据,同时在不同窗口以不同缩放比例显示);
- 易扩展:自定义 Item 只需重写数据 / 行为逻辑,无需关心渲染和事件分发。
详细的知识见:https://blog.csdn.net/xulibo5828/article/details/145464434
二、一些使用范例
1. 基础框架
QGraphicsView的父类的父类是QFrame,所以它支持使用show()方法独立显示。
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem
app = QApplication()
# 创建一个矩形Item
item = QGraphicsRectItem(0, 0, 100, 100)
# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加矩形
scene.addItem(item)
# 创建视图
view = QGraphicsView()
# 设置视图的场景
view.setScene(scene)
# 显示视图
view.show()
app.exec()

可以使用view = QGraphicsView(scene, parent),在创建view的时候指定场景和父对象,也可以使用view = QGraphicsView(parent)先创建,在使用中动态绑定场景:view.setScene(scene)
2. 在别的窗口部件上显示QGraphicsView
python
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QWidget, QVBoxLayout
app = QApplication()
# 创建一个矩形Item
item = QGraphicsRectItem(0, 0, 100, 100)
# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加矩形
scene.addItem(item)
# 创建视图
view = QGraphicsView()
# 设置视图的场景
view.setScene(scene)
# 创建一个窗口
form = QWidget()
layout = QVBoxLayout(form)
# 在布局中添加视图
layout.addWidget(view)
form.show()
app.exec()

3. 多个视图显示同一场景
python
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QWidget, QVBoxLayout
app = QApplication()
# 创建一个窗口
form = QWidget()
layout = QVBoxLayout(form)
# 创建一个矩形Item
item = QGraphicsRectItem(0, 0, 100, 100)
# 创建一个场景
scene = QGraphicsScene(form)
# 在场景中添加矩形
scene.addItem(item)
# 创建视图
view1 = QGraphicsView(form)
view2 = QGraphicsView(form)
# 设置视图的场景
view1.setScene(scene)
view2.setScene(scene)
view2.scale(0.5, 0.5) # 缩放0.5倍
view2.rotate(45) # 旋转45度
# 在布局中添加视图
layout.addWidget(view1)
layout.addWidget(view2)
form.show()
app.exec()

4. 设定属性
python
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QPainter
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QWidget, QVBoxLayout, \
QGraphicsItem
app = QApplication()
# 创建一个窗口
form = QWidget()
form.resize(400, 300)
layout = QVBoxLayout(form)
# 创建一个图元
item = QGraphicsRectItem(0, 0, 100, 100)
# 设置图元属性
item.setBrush(Qt.red) # 设置填充色
item.setPen(QColor(Qt.green)) # 设置边框颜色
item.setPos(100, 100) # 设置位置
item.setRotation(45) # 设置旋转角度
item.setFlag(QGraphicsItem.ItemIsMovable) # 可拖动
# 创建一个场景
scene = QGraphicsScene(form)
scene.addItem(item)
# 设置场景属性
scene.setSceneRect(0, 0, 350, 250) # 设置场景范围
scene.setBackgroundBrush(Qt.green) # 设置背景色
# 创建一个视图
view = QGraphicsView(scene, form)
# 设置视图属性
view.setGeometry(0, 0, 350, 250) # 设置视图范围
view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭水平滚动条
view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭垂直滚动条
view.setRenderHints(QPainter.Antialiasing) # 开启抗锯齿
view.setDragMode(QGraphicsView.ScrollHandDrag) # 拖拽平移(鼠标图标变成手形
layout.addWidget(view)
form.show()
app.exec()

5、应用系列QTransform(变换)
python
from PySide6.QtGui import QTransform
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
app = QApplication([])
# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加一个矩形
scene.addRect(0, 0, 100, 100)
# 创建视图
view = QGraphicsView()
# 设置视图的场景
view.setScene(scene)
# 定义一个系列变换
transformations1 = [
lambda t: t.rotate(45),
lambda t: t.scale(0.5, 0.5)
]
# 定义另一个系列变换
transformations2 = [
lambda t: t.rotate(30),
lambda t: t.scale(0.3, 0.3)
]
# 创建一个QTransform 对象用于测试动态应用变换
transform = QTransform()
for func in transformations1: # 应用第一个系列变换,也可以选择 transformations2,就是另一个系列
func(transform)
# 将 QTransform 对象应用到视图
view.setTransform(transform)
view.show()
app.exec()
6. 动态调整场景参数
python
from PySide6.QtCore import QTimer
from PySide6.QtGui import QTransform
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene
app = QApplication([])
# 创建一个场景
scene = QGraphicsScene()
# 在场景中添加一个矩形
scene.addRect(0, 0, 20, 20)
# 创建视图
view1 = QGraphicsView()
# 设置视图的场景
view1.setScene(scene)
# 创建一个变换对象
transform1 = QTransform()
d = 0
# 旋转函数
def rotater(view, transform, degree):
global d
d += degree
if d > 360:
d = 0
transform.rotate(degree)
view.setTransform(transform)
timer = QTimer()
timer.timeout.connect(lambda: rotater(view1, transform1, 10))
timer.start(100)
view1.show()
app.exec()

7. 将图片设置为显示内容
python
from PySide6.QtCore import QPropertyAnimation, QPointF, QEasingCurve, QRect
from PySide6.QtGui import QTransform, QPixmap
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsRectItem, QGraphicsPixmapItem
app = QApplication([])
# 创建一个场景
scene1 = QGraphicsScene()
# 在场景中添加一个图片
pixmap = QPixmap("../cat.jpg") # 替换为实际的图片路径
pixmap_item = QGraphicsPixmapItem(pixmap) # 创建 QGraphicsPixmapItem 对象
scene1.addItem(pixmap_item) # 添加到场景中
# 创建视图
view = QGraphicsView()
# 设置视图的场景
view.setScene(scene1)
view.show()
app.exec()

8. 动态更新场景pixmap
python
import sys
from PySide6.QtCore import Signal, Qt
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, \
QPushButton
class MyAnimationView(QGraphicsView):
pixFile = Signal(str) # 定义一个信号,用于传递图片路径
def __init__(self,parent=None):
super().__init__()
self.setGeometry(0, 0, 400, 300)
self.pixmap = QPixmap() # 初始化一个空的 QPixmap 对象
self.pixmap_item = QGraphicsPixmapItem() # 初始化一个空的 QGraphicsPixmapItem 对象
self.pixmap_item.setPixmap(self.pixmap) # 设置图片
self.scene = QGraphicsScene() # 创建一个场景
self.scene.addItem(self.pixmap_item) # 添加到场景中
self.setScene(self.scene) # 设置视图的场景
self.btn = QPushButton("设置图片",self)
self.settings() # 设置
self.signal_slot() # 连接信号和槽
# 设置
def settings(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭水平滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭垂直滚动条
self.setRenderHints(QPainter.Antialiasing) # 开启抗锯齿
# self.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio) # 保持宽高比适配
# 信号和槽的连接
def signal_slot(self):
# 定义一个槽函数,用于处理图片路径信号
def on_signal_pixFile(pixFile):
self.pixmap.load(pixFile)
self.pixmap_item.setPixmap(self.pixmap)
self.viewport().update()
self.fitInView(self.scene.sceneRect().adjusted(-10, -10, 10, 10), Qt.KeepAspectRatio) # 保持宽高比适配
self.pixFile.connect(on_signal_pixFile)
# 定义一个槽函数,用于处理按钮点击信号
def on_btn_clicked():
self.pixFile.emit("../cat.jpg")
self.btn.clicked.connect(on_btn_clicked)
if __name__ == "__main__":
app = QApplication([])
view = MyAnimationView()
view.show()
sys.exit(app.exec())

9. 另一个有更多设置的动态更新
python
import sys
from PySide6.QtCore import Signal, Qt, QTimer
from PySide6.QtGui import QPixmap, QPainter
from PySide6.QtWidgets import QApplication, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, \
QPushButton
class MyAnimationView(QGraphicsView):
pixFile = Signal(str) # 定义一个信号,用于传递图片路径
clock = Signal() # 定义一个信号,用于周期性更新
def __init__(self,parent=None):
super().__init__()
self.setGeometry(0, 0, 400, 300)
self.pixmap = QPixmap() # 初始化一个空的 QPixmap 对象
self.pixmap_item = QGraphicsPixmapItem() # 初始化一个空的 QGraphicsPixmapItem 对象
self.scene = QGraphicsScene() # 创建一个场景
self.scene.setBackgroundBrush(Qt.black) # 设置背景为黑色
# self.pixmap_item.setOpacity(0.5) # 设置透明度为 0.5
self.pixmap_scale = 0.001 # 初始化缩放比例为 0.001
self.pixmap_item.setScale(self.pixmap_scale) # 设置缩放比例
self.scale_step = 0.0
self.scene.addItem(self.pixmap_item) # 添加到场景中
self.setScene(self.scene) # 设置视图的场景
self.btn = QPushButton("设置图片",self)
self.settings() # 设置
self.signal_slot() # 连接信号和槽
# 设置
def settings(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭水平滚动条
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) # 关闭垂直滚动条
self.setRenderHints(QPainter.Antialiasing) # 开启抗锯齿
# 信号和槽的连接
def signal_slot(self):
# 定义一个槽函数,用于处理图片路径信号
def on_signal_pixFile(pixFile):
self.pixmap.load(pixFile) # 加载图片
self.pixmap_item.setPixmap(self.pixmap) # 设置图像项的图像
self.pixmap_scale = 0.001 # 重置缩放比例
self.scene.setSceneRect(self.frameRect()) # 设置场景矩形为显示窗口矩形
self.pixFile.connect(on_signal_pixFile) # 连接信号和槽
# 定义一个槽函数,用于处理按钮点击信号
def on_btn_clicked():
self.pixFile.emit("../cat.jpg")
self.pixmap_scale = 0.001
self.btn.clicked.connect(on_btn_clicked) # 点击按钮加载图片
# 定义一个槽函数,用于处理周期性更新信号
def on_signal_clock():
w_pixmap, h_pixmap = self.pixmap_item.sceneBoundingRect().width(), self.pixmap_item.sceneBoundingRect().height() # 获取图像项的宽高(非原始像素,而是缩放后在场景中的宽高)
w_scene, h_scene = self.scene.sceneRect().width(), self.scene.sceneRect().height() # 获取场景的宽高
if w_pixmap > w_scene or h_pixmap > h_scene: # 如果图像项超出场景范围,则不再缩放
self.scale_step = 0.0
else:
try:
self.scale_step = min(self.scene.sceneRect().width() / self.pixmap_item.boundingRect().width(), # 比例阶梯值
self.scene.sceneRect().height() / self.pixmap_item.boundingRect().height()) * 0.02
# print(self.scale_step)
except ZeroDivisionError:
self.scale_step = 0.0
self.pixmap_scale += self.scale_step # 缩放比例递增
self.pixmap_item.setScale(self.pixmap_scale) # 设置图像项的缩放比例
x, y = self.scene.sceneRect().center().x() - w_pixmap / 2, self.scene.sceneRect().center().y() - h_pixmap / 2 # 计算图像项的中心位置(相对于场景中心)
self.pixmap_item.setPos(x, y) # 设置图像项的位置
self.viewport().update() # 更新视图
self.clock.connect(on_signal_clock) # 连接信号和槽
if __name__ == "__main__":
app = QApplication([])
view = MyAnimationView()
view.show()
timer = QTimer()
timer.start(10)
timer.timeout.connect(view.clock.emit)
sys.exit(app.exec())

三、常用的方法和属性
- QGraphicsScene(场景)
- 核心属性
| 属性名 | 作用 | 设定 / 获取方法 |
|---|---|---|
sceneRect |
场景的矩形范围(超出范围的 Item 仍可存在,但默认视图只显示该范围) | 设定:setSceneRect(x, y, width, height) / setSceneRect(QRectF)获取:sceneRect() |
backgroundBrush |
场景背景(颜色 / 纹理) | 设定:setBackgroundBrush(QBrush)获取:backgroundBrush() |
foregroundBrush |
场景前景(叠加在所有 Item 上的遮罩) | 设定:setForegroundBrush(QBrush)获取:foregroundBrush() |
selectionArea |
当前选中的区域(用于批量选 Item) | 设定:setSelectionArea(QPainterPath)获取:selectionArea() |
- 图元管理
| 方法 | 作用 |
|---|---|
addItem(item) |
添加图元到场景 |
removeItem(item) |
从场景移除图元(不会删除图元对象) |
clear() |
清空所有图元 |
items() |
返回场景中所有图元(列表) |
itemAt(scene_pos, device) |
获取指定场景坐标下的图元 |
selectedItems() |
返回选中的图元 |
- 场景控制
| 方法 | 作用 |
|---|---|
setSceneRect(x, y, w, h) |
设置场景可视范围(超出部分可滚动) |
sceneRect() |
获取场景范围 |
invalidate() |
标记场景需要重绘 |
update() |
强制重绘场景 |
| viewport().update() | 强制重绘场景视口 |
- 事件与交互
| 方法 | 作用 |
|---|---|
mousePressEvent(event) |
重写鼠标按下事件 |
mouseMoveEvent(event) |
重写鼠标移动事件 |
keyPressEvent(event) |
重写键盘按下事件 |
setFocus() |
让场景获得焦点(响应键盘事件) |
- 核心方法
(1)Item 管理(最常用)
from PySide6.QtWidgets import QGraphicsScene, QGraphicsRectItem
from PySide6.QtCore import QRectF
# 1. 创建场景
scene = QGraphicsScene()
# 设置场景范围(x=0, y=0, 宽=800, 高=600)
scene.setSceneRect(0, 0, 800, 600)
# 设置背景为浅灰色
scene.setBackgroundBrush(Qt.lightGray)
# 2. 添加 Item
rect_item = QGraphicsRectItem(QRectF(100, 100, 200, 150))
scene.addItem(rect_item)
# 3. 查找 Item
# 根据坐标找 Item
items_at_pos = scene.items(150, 150) # 返回该坐标下所有 Item(从顶层到底层)
# 根据类型找 Item
rect_items = scene.items(Qt.AscendingOrder, type=QGraphicsRectItem.Type)
# 4. 移除 Item
scene.removeItem(rect_item)
# 清空所有 Item
scene.clear()
(2)事件与交互
# 1. 发送鼠标事件到场景(模拟用户操作)
from PySide6.QtGui import QMouseEvent, QCursor
scene.sendMouseEvent(QMouseEvent(...))
# 2. 碰撞检测(判断 Item 之间是否重叠)
colliding_items = scene.collidingItems(rect_item) # 返回与 rect_item 碰撞的所有 Item
# 3. 选中 Item
scene.setSelectionArea(scene.itemsBoundingRect()) # 选中场景内所有 Item
selected_items = scene.selectedItems() # 获取当前选中的所有 Item
- QGraphicsView(视图)
视图是场景的 "显示窗口",核心能力是渲染场景、控制视图变换(缩放 / 平移)、处理用户输入。
核心属性
| 属性名 | 作用 | 设定 / 获取方法 |
|---|---|---|
scene |
视图绑定的场景 | 设定:setScene(QGraphicsScene)获取:scene() |
viewportUpdateMode |
视图更新模式(影响渲染性能) | 设定:setViewportUpdateMode(QGraphicsView.ViewportUpdateMode)可选值:FullViewportUpdate(全屏更新)、MinimalViewportUpdate(最小范围更新)等 |
dragMode |
拖拽模式(控制鼠标拖拽行为) | 设定:setDragMode(QGraphicsView.DragMode)可选值:- NoDrag:无拖拽- ScrollHandDrag:拖拽时平移视图(按住鼠标左键)- RubberBandDrag:拖拽时框选 Item |
transform |
视图的变换矩阵(缩放、旋转、平移) | 设定:setTransform(QTransform)获取:transform() |
| fitInView | 场景自动适配视图满幅显示 | view.fitInView(rect: QRectF, mode: Qt.AspectRatioMode,保持比例),可选值:Qt.IgnoreAspectRatio,拉伸。view.fitInView(self.scene.sceneRect().adjusted(-10, -10, 10, 10), Qt.KeepAspectRatio):10是边距留白 |
alignment |
场景在视图中的对齐方式 | 设定:setAlignment(Qt.AlignmentFlag)可选值:Qt.AlignCenter(居中)、Qt.AlignTopLeft(左上)等 |
- 核心方法
(1)视图控制(缩放 / 平移 / 重置)
from PySide6.QtWidgets import QGraphicsView, QWidget
from PySide6.QtCore import Qt
from PySide6.QtGui import QTransform
# 1. 创建视图并绑定场景
view = QGraphicsView(scene, parent=QWidget())
view.setGeometry(0, 0, 800, 600) # 设置视图大小
view.setDragMode(QGraphicsView.ScrollHandDrag) # 拖拽视图平移
view.setAlignment(Qt.AlignCenter) # 场景在视图中居中显示
# 2. 缩放视图(最常用)
view.scale(1.5, 1.5) # 放大1.5倍(x/y轴)
view.scale(0.8, 0.8) # 缩小到0.8倍
# 3. 平移视图(手动控制)
view.translate(50, 30) # 向右平移50px,向下平移30px
# 4. 旋转视图
view.rotate(30) # 顺时针旋转30度
# 5. 重置视图变换(恢复默认)
view.resetTransform()
# 6. 适配视图(让场景完整显示在视图中)
view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) # 保持宽高比适配
(2)渲染与刷新
# 1. 刷新视图(强制重绘)
view.viewport().update()
# 2. 设置抗锯齿(让图形边缘更平滑)
view.setRenderHint(QPainter.Antialiasing) # 开启抗锯齿
view.setRenderHint(QPainter.SmoothPixmapTransform) # 图片变换平滑
# 3. 获取视图坐标与场景坐标的映射
# 视图坐标 → 场景坐标
scene_pos = view.mapToScene(100, 100) # 视图上(100,100)对应场景的坐标
# 场景坐标 → 视图坐标
view_pos = view.mapFromScene(200, 200) # 场景上(200,200)对应视图的坐标
3. QGraphicsItem(图形项)
图形项是场景中的 "可交互元素",核心能力是自定义绘制、响应事件、变换自身位置 / 大小。
注意:QGraphicsItem 是抽象类,实际使用时常用其子类(如
QGraphicsRectItem、QGraphicsEllipseItem),或自定义子类重写paint()和boundingRect()。
- 核心属性
| 属性名 | 作用 | 设定 / 获取方法 |
|---|---|---|
pos |
Item 在场景中的位置(锚点默认是 Item 中心) | 设定:setPos(x, y) / setPos(QPointF)获取:pos() |
boundingRect |
Item 的边界矩形(碰撞检测、视图裁剪的依据) | 必须重写(自定义 Item 时):boundingRect() -> QRectF |
shape |
Item 的形状(比 boundingRect 更精确的碰撞检测) | 可选重写:shape() -> QPainterPath |
flags |
Item 的交互标志 | 设定:setFlags(QGraphicsItem.GraphicsItemFlag)可选值:- ItemIsMovable:允许拖拽移动- ItemIsSelectable:允许选中- ItemIsFocusable:允许获取焦点- ItemSendsGeometryChanges:位置变化时发送信号 |
transform |
Item 的变换矩阵(自身缩放 / 旋转 / 平移) | 设定:setTransform(QTransform)获取:transform() |
- 核心方法
(1)自定义 Item
python
import math
import sys
from PySide6.QtCore import QRectF, Qt, QPointF
from PySide6.QtGui import QPen, QBrush, QPolygonF, QPainterPath, QColor
from PySide6.QtWidgets import QGraphicsItem, QApplication, QGraphicsScene, QGraphicsView
from PySide6.QtWidgets import QGraphicsItem
from PySide6.QtGui import QPainter, QPen, QBrush
from PySide6.QtCore import QRectF, Qt
class CircleItem(QGraphicsItem):
def __init__(self):
super().__init__()
# 1. 必须重写:定义 Item 的边界矩形(视图据此判断是否需要绘制)
def boundingRect(self):
# 边界要包含所有绘制内容(包括描边),避免裁剪
return QRectF(-50, -50, 100, 100) # 中心在(0,0),宽100,高100
# 2. 必须重写:自定义绘制逻辑
def paint(self, painter, option, widget):
# 设置画笔(描边)
painter.setPen(QPen(Qt.red, 2))
# 设置画刷(填充)
painter.setBrush(QBrush(Qt.yellow))
# 绘制圆形
painter.drawEllipse(self.boundingRect())
# 选中时绘制高亮边框
if self.isSelected():
painter.setPen(QPen(QColor(0, 255, 255, 180), 2, Qt.DashLine))
painter.drawRect(self.boundingRect())
# 3. 可选重写:自定义形状(用于精确碰撞检测)
def shape(self):
path = QPainterPath()
path.addEllipse(self.boundingRect())
return path
# 4. 可选重写:响应鼠标事件
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.setBrush(QBrush(Qt.green)) # 点击后变色
super().mousePressEvent(event)
def setBrush(self, brush):
self.brush = brush
self.update()
class TriangleItem(QGraphicsItem):
def __init__(self, size=50, parent=None):
super().__init__(parent)
self._is_rotating = True
self.size = size
# 启用交互标志
self.setFlags(
QGraphicsItem.ItemIsMovable |
QGraphicsItem.ItemIsSelectable |
QGraphicsItem.ItemIsFocusable
)
# 必须重写:返回图元的边界矩形(用于碰撞检测、重绘)
def boundingRect(self):
return QRectF(-self.size/2, -self.size/2, self.size, self.size)
# 必须重写:绘制图元
def paint(self, painter, option, widget):
# 绘制三角形
painter.setPen(QPen(Qt.black, 2))
painter.setBrush(QBrush(Qt.cyan))
points = [
QPointF(0, -self.size/2), # 上顶点
QPointF(-self.size/2, self.size/2), # 左下
QPointF(self.size/2, self.size/2) # 右下
]
painter.drawPolygon(QPolygonF(points))
# 选中时绘制高亮边框
if self.isSelected():
painter.setPen(QPen(Qt.red, 1, Qt.DashLine))
painter.drawRect(self.boundingRect())
# 重写鼠标按下事件:右键按下开始旋转
def mousePressEvent(self, event):
if event.button() == Qt.RightButton:
self._is_rotating = True
# 计算鼠标相对于椭圆中心的初始角度(弧度转角度)
center = self.boundingRect().center()
mouse_pos = self.mapFromScene(event.scenePos())
dx = mouse_pos.x() - center.x()
dy = mouse_pos.y() - center.y()
self._rotate_start_angle = math.atan2(dy, dx) * 180 / math.pi - self.rotation()
else:
# 左键按下发给父类处理(实现移动)
super().mousePressEvent(event)
# 重写鼠标移动事件:右键拖动时旋转
def mouseMoveEvent(self, event):
if self._is_rotating and event.buttons() & Qt.RightButton:
# 计算当前鼠标相对于椭圆中心的角度
center = self.boundingRect().center()
mouse_pos = self.mapFromScene(event.scenePos())
dx = mouse_pos.x() - center.x()
dy = mouse_pos.y() - center.y()
# 避免除以0(鼠标在中心时不旋转)
if dx == 0 and dy == 0:
return
current_angle = math.atan2(dy, dx) * 180 / math.pi
# 设置旋转角度
self.setRotation(current_angle - self._rotate_start_angle)
else:
# 非旋转状态下发给父类处理(实现移动)
super().mouseMoveEvent(event)
# 重写鼠标释放事件:结束旋转
def mouseReleaseEvent(self, event):
if event.button() == Qt.RightButton:
self._is_rotating = False
else:
super().mouseReleaseEvent(event)
# 使用自定义图元
triangle = TriangleItem(100)
circle = CircleItem()
circle.setPos(400, 300) # 设置在场景中的位置
circle.setFlags(QGraphicsItem.ItemIsMovable | QGraphicsItem.ItemIsSelectable) # 可移动、可选中
app = QApplication(sys.argv)
# 使用自定义 Item
scene = QGraphicsScene()
scene.addItem(circle)
scene.addItem(triangle)
view = QGraphicsView(scene)
view.show()
sys.exit(app.exec())

(2)Item 变换与交互
# 1. 移动 Item
custom_item.moveBy(20, 20) # 相对当前位置移动
custom_item.setPos(450, 350) # 绝对位置移动
# 2. 缩放 Item
custom_item.setScale(1.2) # 整体缩放1.2倍
# 3. 旋转 Item
custom_item.setRotation(45) # 顺时针旋转45度
# 4. 隐藏/显示 Item
custom_item.hide()
custom_item.show()
# 5. 获取 Item 所在场景
scene = custom_item.scene()
4 . 完整示例代码
python
import sys
from PySide6.QtWidgets import (QApplication, QMainWindow, QGraphicsView,
QGraphicsScene, QGraphicsRectItem, QGraphicsEllipseItem)
from PySide6.QtCore import Qt, QRectF
from PySide6.QtGui import QBrush, QPen, QPainter
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("QGraphicsView/Scene/Item 示例")
self.setGeometry(100, 100, 800, 600)
# 1. 创建场景
self.scene = QGraphicsScene()
self.scene.setSceneRect(0, 0, 800, 600)
self.scene.setBackgroundBrush(QBrush(Qt.lightGray))
# 2. 添加预设 Item
# 矩形 Item(可移动、可选中)
rect_item = QGraphicsRectItem(QRectF(100, 100, 200, 150))
rect_item.setPen(QPen(Qt.blue, 3))
rect_item.setBrush(QBrush(Qt.cyan, Qt.DiagCrossPattern))
rect_item.setFlags(rect_item.flags() | QGraphicsRectItem.ItemIsMovable |
QGraphicsRectItem.ItemIsSelectable)
self.scene.addItem(rect_item)
# 椭圆 Item(可旋转、可选中)
ellipse_item = QGraphicsEllipseItem(QRectF(400, 200, 180, 180))
ellipse_item.setPen(QPen(Qt.red, 2))
ellipse_item.setBrush(QBrush(Qt.yellow))
ellipse_item.setFlags(ellipse_item.flags() | QGraphicsEllipseItem.ItemIsMovable |
QGraphicsEllipseItem.ItemIsSelectable)
self.scene.addItem(ellipse_item)
# 3. 创建视图
self.view = QGraphicsView(self.scene, self)
self.view.setRenderHint(QPainter.Antialiasing) # 抗锯齿
self.view.setDragMode(QGraphicsView.ScrollHandDrag) # 拖拽平移视图
self.view.setViewportUpdateMode(QGraphicsView.MinimalViewportUpdate) # 优化性能
self.setCentralWidget(self.view)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
四、高级特性
-
碰撞检测
检测图元是否与其他图元碰撞
def check_collision(item):
# 获取碰撞的所有图元
colliding_items = item.collidingItems()
if colliding_items:
print(f"图元 {item} 与 {len(colliding_items)} 个图元碰撞")
for coll_item in colliding_items:
coll_item.setBrush(QBrush(Qt.red)) # 碰撞的图元标红 -
视图缩放 / 旋转
缩放视图(滚轮事件)
def wheelEvent(self, event):
zoom_factor = 1.1 # 缩放系数
if event.angleDelta().y() > 0:
# 放大
self.view.scale(zoom_factor, zoom_factor)
else:
# 缩小
self.view.scale(1/zoom_factor, 1/zoom_factor)旋转视图
self.view.rotate(45) # 顺时针旋转45度
-
图元分组
创建分组(组内图元作为整体移动/变换)
group = self.scene.createItemGroup([rect_item, ellipse_item])
取消分组
self.scene.destroyItemGroup(group)
-
动画(QPropertyAnimation)
from PySide6.QtCore import QPropertyAnimation, QEasingCurve
给图元添加平移动画
anim = QPropertyAnimation(rect_item, b"pos")
anim.setDuration(2000) # 时长2秒
anim.setStartValue(QPointF(-100, -50))
anim.setEndValue(QPointF(200, 100))
anim.setEasingCurve(QEasingCurve.InOutBounce) # 弹跳曲线
anim.start()
五、总结
使用MVC 模式进行PySide6 2D 图形开发,通过「场景 - 视图 - 图元」的分层架构,实现了高效的图形渲染与交互。
该框架适用于流程图、游戏界面、数据可视化、CAD 预览、视觉AI项目等需要复杂 2D 交互的场景。