35. 图形视图绘图框架

一、Graphics/View绘图框架

  在 PySide6 中,Graphics/View 绘图框架主要由 图像视图QGraphicsScene)、图像场景QGraphicsView)、图形项QGraphicsItem)构成。

【1】、图像视图

  图像视图类 QGraphicsView 提供了绘制图像的视图控件,用于显示图像场景中的内容。如果图像视图的范围大于图像场景的范围,则图像场景在图像视图中间部分显示。如果图像场景的范围大于图像视图的范围,则视图控件自动提供滚动条和滚动区。

  QGraphicsView 类是视图控件,可以接受鼠标和键盘的输入并转换为场景事件,而且可以进行坐标转换后传递给可视的图像场景。

【2】、图像场景

  图像场景类 QGraphicsScene 提供了绘制图像的场景。图像场景是一个不可见的、抽象的容器,可以向图像场景中添加图形项,并可以获取图像场景中的各个图形项。QGraphicsScene 类提供了大量的图形项接口,可以管理各个图形项及其状态,并可以将场景事件传递给各个图形项。

【3】、图形项

  图形项 就是一些基本的图形元件。图形项的基类为 QGraphicsItem,PySide6 也提供了标准的图形项类,例如 矩形类 QGraphicsRectItem椭圆类 QGraphicsEllipseItem文本类 QGraphicsTextItem

  QGraphicsItem 类支持鼠标事件、键盘事件、拖放操作,也可以使用 QGraphicsItemGroup 类对图形元件进行组合。

  图像场景图形项的容器,可以在 图像场景绘制多个图形项,每个 图形项 就是一个实例对象,这些图形项可以被选择、拖动。图像视图显示图像场景的视图控件。一个 图像场景 可以有多张 图像视图,一张 图像视图 可以显示图像场景的部分区域或全部区域。

  我们可以在终端中使用 pip安装 PySide6 模块。默认是从国外的主站上下载,因此,我们可能会遇到网络不好的情况导致下载失败。我们可以在 pip指令后通过 -i 指定国内镜像源下载

pip install pyside6 -i https://mirrors.aliyun.com/pypi/simple

  国内常用的 pip 下载源列表:

二、Graphics/View的坐标系

  Graphics/View 坐标系基于 笛卡儿坐标系,图项在场景中的位置和几何形状通过 x 坐标和 y 坐标表示。当使用没有变换的视图观察场景时,场景中的一个单位对应屏幕上的一个像素。

  Graphics/View 架构中有三种坐标系统,分别是 图像视图坐标图像场景坐标图形项坐标

  • 图像视图坐标 是窗口界面的物理坐标,其 左上角为坐标原点
  • 图像场景坐标 类似于 QPainter 的逻辑坐标,一般以 场景的中心为原点
  • 图形项坐标局部逻辑坐标,通常以 图形项的中心为原点

【1】、图像视图坐标

  图像视图坐标窗口控件的坐标,与设备坐标系相同。视图坐标的单位是像素。QGraphicsView 左上角的坐标为 (0,0)。所有的鼠标事件、拖曳事件最开始都使用视图坐标。因为要和图形项交互,所以需要转换为图像场景坐标。

【2】图像场景坐标

  图像场景坐标 是所有 图形项的基础坐标。图像场景坐标类系似于 QPainter逻辑坐标系,一般以 图像场景的中心坐标系原点(需要将图像场景的矩形范围设置为(-a,-b,2a,2b),否则图像场景的中心点未必是坐标系原点)。场景坐标 描述了顶层图形项的位置,而且构成了从图像视图到图像场景的所有场景事件的基础,每个图形项在场景上都有场景坐标和边界矩形。

【3】、图形项坐标

  图形项 使用自己的 局部坐标,通常以 图形项的中心原点。图形项的原点也是各种坐标转换的中心。图形项的鼠标事件使用局部坐标,创建图形项、绘制图形项也使用局部坐标,QGraphicsSceneQGraphicsView 会自动进行坐标转换。

  一个图形项的位置是其中心点在父坐标系的坐标。如果一个图形项没有父图形项,则图形项的位置就是图像场景的坐标。如果一个图形项有父图形项,则父图形项进行坐标转换时子图形项也进行坐标转换。

  子图项的坐标与父图项的坐标相关,如果子图项无变换,则子图项坐标和父图项坐标之间的区别与它们的父图项的坐标相同。例如,如果一个无变换的子图项精确地位于父图项的中心点,则父子图项的坐标系统是相同的。如果子图项的位置是 (100, 0),子图项上的点 (0, 100) 就是父图项上的点 (100, 100)。即使图项的位置和变换与父图项相关,子图项的坐标也不会被父图项的变换影响,虽然父图项的变换会隐式地变换子图项。例如,即使父图项被翻转和缩放,子图项上的点 (0, 100) 仍旧是父图项上的点 (100, 100)。如果调用 QGraphicsItem类的 paint() 函数重绘图项,应以图项坐标系为基准。

  在 Graphics/View 框架中,经常需要在不同种坐标间进行变换。

【1】、视图到场景

QGraphicsView.mapToScene(path:QPainterPath) -> QPainterPath
QGraphicsView.mapToScene(point:QPoint) -> QPointF
QGraphicsView.mapToScene(polygon:QPolygon) -> QPolygonF
QGraphicsView.mapToScene(rect:QRect) -> QPolygonF
QGraphicsView.mapToScene(x:float, y:float) -> QPointF
QGraphicsView.mapToScene(x:float, y:float, width:float height:float) -> QPolygonF

【2】、场景到视图

QGraphicsView.mapFromScene(path:QPainterPath) -> QPainterPath
QGraphicsView.mapFromScene(point:QPointF) -> QPoint
QGraphicsView.mapFromScene(polygon:QPolygonF) -> QPolygon
QGraphicsView.mapFromScene(rect:QRectF) -> QPolygon
QGraphicsView.mapFromScene(x:float, y:float) -> QPoint
QGraphicsView.mapFromScene(x:float, y:float, width:float, height:float) -> QPolygon

【3】、场景到图项

QGraphicsItem.mapFromScene(path:QPainterPath) -> QPainterPath
QGraphicsItem.mapFromScene(point:QPointF) -> QPointF
QGraphicsItem.mapFromScene(polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapFromScene(rect:QRectF) -> QPolygonF
QGraphicsItem.mapFromScene(x:float, y:float) -> QPointF
QGraphicsItem.mapFromScene(x:float, y:float, width:float, height:float) -> QPolygonF

【4】、图项到场景

QGraphicsItem.mapToScene(path:QPainterPath) -> QPainterPath
QGraphicsItem.mapToScene(point:QPointF) -> QPointF
QGraphicsItem.mapToScene(polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapToScene(rect:QRectF) -> QPolygonF
QGraphicsItem.mapToScene(x:float, y:float) -> QPointF
QGraphicsItem.mapToScene(x:float, y:float, width:float, height:float) -> QPolygonF

【5】、子图项到父图项

QGraphicsItem.mapToParent(path:QPainterPath) -> QPainterPath
QGraphicsItem.mapToParent(point:QPointF) -> QPointF
QGraphicsItem.mapToParent(polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapToParent(rect:QRectF) -> QPolygonF
QGraphicsItem.mapToParent(x:float, y:float) -> QPointF
QGraphicsItem.mapToParent(x:float, y:float, width:float, height:float) -> QPolygonF

【6】、父图项到子图项

QGraphicsItem.mapFromParent(path:QPainterPath) -> QPainterPath
QGraphicsItem.mapFromParent(point:QPointF) -> QPointF
QGraphicsItem.mapFromParent(polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapFromParent(rect:QRectF) -> QPolygonF
QGraphicsItem.mapFromParent(x:float, y:float) -> QPointF
QGraphicsItem.mapFromParent(x:float, y:float, width:float, height:float) -> QPolygonF

【7】、本图项到其它图项

QGraphicsItem.mapToItem(item:QGraphicsItem, path:QPainterPath) -> QPainterPath
QGraphicsItem.mapToItem(item:QGraphicsItem, point:QPointF) -> QPointF
QGraphicsItem.mapToItem(item:QGraphicsItem, polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapToItem(item:QGraphicsItem, rect:QRectF) -> QPolygonF
QGraphicsItem.mapToItem(item:QGraphicsItem, x:float, y:float) -> QPointF
QGraphicsItem.mapToItem(item:QGraphicsItem, x:float, y:float, width:float, height:float) -> QPolygonF

【8】、其它图项到本图项

QGraphicsItem.mapFromItem(item:QGraphicsItem, path:QPainterPath) -> QPainterPath
QGraphicsItem.mapFromItem(item:QGraphicsItem, point:QPointF) -> QPointF
QGraphicsItem.mapFromItem(item:QGraphicsItem, polygon:QPolygonF) -> QPolygonF
QGraphicsItem.mapFromItem(item:QGraphicsItem, rect:QRectF) -> QPolygonF
QGraphicsItem.mapFromItem(item:QGraphicsItem, x:float, y:float) -> QPointF
QGraphicsItem.mapFromItem(item:QGraphicsItem, x:float, y:float, width:float, height:float) -> QPolygonF

  在场景中处理图项时,经常需要在场景到图项、图项到图项、视图到场景间进行坐标和图形转换。

  当在 QGraphicsView 的视口中单击鼠标时,应该通过调用 QGraphicsView.mapToScence()QGraphicsScene.itemAt() 函数来获知光标下是场景中的哪个图项。

  如果想获知一个图项在视口中的位置,应该先在图项上调用 QGraphicsItem.mapToScene() 函数,然后调用 QGraphicsView.mapFromScene() 函数。

  如果想获知在一个视图中有哪些图项,应该把 QPainterPath 传递到 mapToScene() 函数,然后再把映射后的路径传递到 QGraphicsScene.items() 函数。可以调用 QGraphicsItem.mapToScene() 函数与 QGraphicsItem.mapFromScene() 函数在图项与场景之间进行坐标与形状的映射,也可以在子图项与其父图项之间通过 QGraphicsItem.mapToParent()QGraphicsItem.mapFromItem() 函数进行映射。

对于视图与图项之间的映射,应该先从视图映射到场景,然后再从场景映射到图项。

三、视图控件

  视图控件 QGraphicsView 用于 显示场景中的图项,当场景超过视图区域时,视图会提供滚动条。视图控件 QGraphicsView 继承自 QAbstractScrollArea,视图控件根据场景的尺寸提供滚动区,当视图尺寸小于场景尺寸时会提供滚动条。

  用 QGraphicsView 类创建视图控件对象的方法如下所示:

QGraphicsView(parent:QWidget=None)
QGraphicsView(scene:QGraphicsScene, parent:QWidget=None)

  其中,参数 parent 是继承自 QWidget 的窗口或控件。参数 scene场景实例对象,用于 设置视图控件中的场景

  QGraphicsView 类的常用方法如下:

# 实例方法
setScene(scene:QGraphicsScene) -> None                                          # 设置场景
scene() -> QGraphicsScene                                                       # 获取场景

setSceneRect(rect:Union[QRectF, QRect]) -> None                                 # 设置场景在视图中的范围
setSceneRect(x:float, y:float, width:float, height:float) -> None               # 设置场景在视图中的范围
sceneRect() -> QRectF                                                           # 获取场景在视图中的范围

setAlignment(alignment:Qt.AlignmentFlag) -> None                                # 设置场景全部可见时的对齐对齐
alignment() -> Qt.AlignmentFlag                                                 # 获取场景全部可见时的对齐对齐

# 设置视图前景画刷
setForegroundBrush(brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> None
foregroundBrush() -> QBrush                                                     # 获取视图前景画刷

# 设置视图背景画刷
setBackgroundBrush(brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> None
backgroundBrush() -> QBrush                                                     # 获取视图背景画刷

centerOn(item:QGraphicsItem) -> None                                            # 使某个图项位于视图控件中心
centerOn(pos:Union[QPointF, QPoint, QPainterPath.Element]) -> None              # 使某个点位于视图控件中心
centerOn(x:float, y:float) -> None                                              # 使某个点位于视图控件中心

# 确保指定的矩形区域可见,可见时按指定的边框显示。如不可见,滚动到最近的点
ensureVisible(item:QGraphicsItem, xmargin:int=50, ymargin:int=50) -> None
ensureVisible(rect:Union[QRectF, QRect], xmargin:int=50, ymargin:int=50) -> None
ensureVisible(x:float, y:float, width:float, height:float, xmargin:int=50, ymargin:int=50) -> None

# 以合适方式使矩形区域可见
fitInView(item:QGraphicsItem, aspectRatioMode:Qt.AspectRatioMode=Qt.AspectRatioMode.IgnoreAspectRatio) -> None
fitInView(rect:Union[QRectF, QRect], aspectRatioMode:Qt.AspectRatioMode=Qt.AspectRatioMode.IgnoreAspectRatio) -> None
fitInView(x:float, y:float, width:float, height:float, aspectRatioMode:Qt.AspectRatioMode=Qt.AspectRatioMode.IgnoreAspectRatio) -> None

# 从source(视图)把图像复制到target(其它设备,如QImage)上
render(painter:QPainter, target:Union[QRectF, QRect], source:QRect, aspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio)

resetCachedContent() -> None                                                    # 重置缓存
setCacheMode(mode:QGraphicsView.CacheMode) -> None                              # 设置缓存模式

setDragMode(mode:QGraphicsView.DragMode) -> None                                # 设置鼠标拖拽模式

setRubberBandSelectionMode(mode:Qt.ItemSelectionMode) -> None                   # 设置用鼠标框选模式
rubberBandRect() -> QRect                                                       # 获取用鼠标框选的范围   

setInteractive(allowed:bool) -> None                                            # 设置是否允许用户交互
isInteractive() -> bool                                                         # 获取是否允许用户交互


setOptimizationFlag(flag:QGraphicsView.OptimizationFlag, enabled:bool=true) -> None # 设置优化显示标识
setOptimizationFlags(flags:QGraphicsView.OptimizationFlag) -> None              # 设置优化显示标识

setRenderHint(hint:QPainter.RenderHint, enabled:bool=true) -> None              # 设置提高绘图质量标识
setRenderHints(hints:QPainter.RenderHint) -> None                               # 设置提高绘图质量标识

setResizeAnchor(anchor:QPainter.ViewportAnchor) -> None                         # 设置视图控件改变尺寸时的锚点
resizeAnchor() -> QPainter.ViewportAnchor                                       # 获取锚点

setTransform(matrix:QTransform, combine:bool=false) -> None                     # 用交换矩阵变换视图
transform() -> QTransform                                                       # 获取视图变换矩阵
isTransformed() -> bool                                                         # 判断视图是否被变换
resetTransform() -> None                                                        # 重置视图变换矩阵
setTransformationAnchor(anchor:QGraphicsView.ViewportAnchor) -> None            # 设置变换时的锚点

setViewportUpdateMode(mode:QGraphicsView.ViewportUpdateMode) -> None            # 设置刷新模式

scale(sx:float, sy:float) -> None                                               # 缩放视图
rotate(angle:float) -> None                                                     # 旋转视图
shear(sh:float, sv:float) -> None                                               # 错切视图
translate(dx:float, dy:float) -> None                                           # 平移视图

# 虚拟方法
drawForeground(painter:QPainter, rect:Union[QRectF, QRect]) -> None             # 重写该函数,在显示背景和图项前绘制背景
drawBackground(painter:QPainter, rect:Union[QRectF, QRect]) -> None             # 重写该函数,在显示前景和图项前绘制背景

setupViewport(viewport:QWidget) -> None                                         # 重写该函数,设置视图的视口

# 槽方法
updateScene(rects:Sequence[QRectF]) -> None                                     # 更新场景
updateSceneRect(rect:Union[QRectF, QRect]) -> None                              # 更新场景范围

# 使指定的场景区域进行更新和重新绘制,相当于对指定区域进行update()操作
invalidateScene(rect:Union[QRectF, QRect], layers:QGraphicsScene.SceneLayer=QGraphicsScene.AllLayers) -> None

  QGraphicsView 获取图项的方法如下:

itemAt(pos:QPoint) -> QGraphicsItem
itemAt(x:int, y:int) -> QGraphicsItem

items() -> list[QGraphicsItem]
items(pos:QPoint) -> list[QGraphicsItem]

items(x:int, y:int) -> list[QGraphicsItem]
items(x:int, y:int, width:int, height:int, mode:Qt.ItemSelectionMode==Qt.IntersectsItemShape)

items(rect:QRect, mode:Qt.ItemSelectionMode=Qt.IntersectsItemShape)
items(path:QPainterPath, mode:Qt.ItemSelectionMode=Qt.IntersectsItemShape) -> list[QGraphicsItem]
items(polygon:Union[QPolygon, Sequence[QPoint], QRect], mode:Qt.ItemSelectionMode=Qt.IntersectsItemShape)

  视图控件 QGraphicsView 只有一个信号 rubberBandChanged()

rubberBandChanged(viewportRect:QRect, fromScenePoint:QPointF, toScenePoint:QPointF) # 当框选的范围发生改变时发送信号

  用 setAlignment(alignment:Qt.Alignment) 方法 设置场景在视图控件全部可见时的对齐方式,参数 alignmengQt.Alignment 类型的枚举值,可以取值如下:

Qt.Alignment.AlignLeft
Qt.Alignment.AlignRight
Qt.Alignment.AlignHCenter
Qt.Alignment.AlignJustify
Qt.Alignment.AlignTop
Qt.Alignment.AlignBottom
Qt.Alignment.AlignVCenter
Qt.Alignment.AlignBaseline
Qt.Alignment.AlignCenter                                                        # 默认值

  用 setCacheMode(mode:QGraphicsView.CacheMode) 方法可以 设置缓存模式,参数 modeQGraphicsView.CacheMode 类型的枚举值,可以取值如下:

QGraphicsView.CacheMode.CacheNone                                               # 没有缓存
QGraphicsView.CacheMode.CacheBackground                                         # 缓存背景

  用 setDragMode(mode:QGraphicsView.DragMode) 方法设置 在视图控件中按住鼠标左键选择图项时的拖拽模式,参数 modeQGraphicsView.DragMode 类型的枚举值,可以取值如下:

QGraphicsView.DragMode.NoDrag                                                   # 忽略鼠标事件
QGraphicsView.DragMode.ScrollHandDrag                                           # 在交互或非交互模式下,光标变成手的形状,拖动鼠标会移动整个场景
QGraphicsView.DragMode.RubberBandDrag                                           # 在交互模式下,可以框选图项

  用 setRubberBandSelectionMode(mode:Qt.ItemSelectionMode) 方法 设置框选图项时,图项是否能被选中,其中参数 modeQt.ItemSelectionMode 类型的枚举值,可以取值如下:

Qt.ItemSelectionMode.ContainsItemShape
Qt.ItemSelectionMode.IntersectsItemShape
Qt.ItemSelectionMode.ContainsItemBoundingRect
Qt.ItemSelectionMode.IntersectsItemBoundingRect

  用 setOptimizationFlag(flag:QGraphicsView.OptimizationFlag, enabled:bool=True) 方法 设置视图控件优化显示标识,参数 flagQGraphicsView.OptimizationFlag 类型的枚举值,可以取值如下:

QGraphicsView.OptimizationFlag.DontSavePainterState                             # 不保存绘图状态
QGraphicsView.OptimizationFlag.DontAdjustForAntialiasing                        # 不调整反锯齿
QGraphicsView.OptimizationFlag.IndirectPainting                                 # 间接绘制

  用 setResizeAnchor(anchor:QGraphicsView.ViewportAnchor) 方法 设置视图尺寸发生改变时的锚点,用 setTransformationAnchor(anchor:QGraphicsView.ViewportAnchor) 方法 设置对视图进行坐标变换时的锚点,锚点的作用是定位场景在视图控件中的位置。其中参数 anchorQGraphicsView.ViewportAnchor 类型的枚举值,可以取值如下:

QGraphicsView.ViewportAnchor.NoAnchor                                           # 没有锚点,场景位置不变
QGraphicsView.ViewportAnchor.AnchorViewCenter                                   # 场景在视图控件的中心点作为锚点
QGraphicsView.ViewportAnchor.AnchorUnderMouse                                   # 光标所在的位置作为锚点

  用 setViewportUpdateMode(mode:QGraphicsView.ViewportUpdateMode) 方法 设置视图刷新模式,参数 modeQGraphicsView.ViewportUpdateMode 类型的枚举值,可以取值如下:

QGraphicsView.ViewportUpdateMode.FullViewportUpdate
QGraphicsView.ViewportUpdateMode.MinimalViewportUpdate
QGraphicsView.ViewportUpdateMode.SmartViewportUpdate
QGraphicsView.ViewportUpdateMode.BoundingRectViewportUpdate
QGraphicsView.ViewportUpdateMode.NoViewportUpdate

  可以用槽函数 updateScene(rects:Sequence[QRectF])updateSceneRect(rect:Union[QRectF,QRect])invalidateScene(rect:Union[QRectF,QRect],layers:QGraphicsScene.SceneLayers=QGraphicsScene.AllLayers) 方法 只刷新指定的区域,参数 layersQGraphicsScene.SceneLayers 类型的枚举值,可以取值如下:

QGraphicsScene.SceneLayers.ItemLayer
QGraphicsScene.SceneLayers.BackgroundLayer
QGraphicsScene.SceneLayers.ForegroundLayer
QGraphicsScene.SceneLayers.AllLayers

  用 itemAt() 方法可以 获得光标位置处的一个图项,如果有多个图项,则获得最上面的图项。用 items() 方法可以 获得多个图项列表,图项列表中的图项按照z值从顶到底的顺序排列。可以用矩形、多边形或路径获取其内部的图项,例如 items(QRect,mode=Qt.IntersectsItemShape) 方法,参数 modeQt.IntersectsItemShape 类型的枚举值,可以取值如下:

Qt.IntersectsItemShape.ContainsItemShape                                        # 图项完全在选择框内部
Qt.IntersectsItemShape.IntersectsItemShape                                      # 图项在选择框内部和与选择框相交
Qt.IntersectsItemShape.ContainsItemBoundingRect                                 # 图项的边界矩形完全在选择框内部
Qt.IntersectsItemShape.IntersectsItemBoundingRect                               # 图项的边界矩形完全在选择框内部和与选择框交叉

  由于 QGraphicsView 继承自 QWidget,因此 QGraphicsView 提供了拖拽功能。Graphics/View 框架也为场景、图项提供拖拽支持。当视图控件接收到拖拽事件,Graphics/View 框架会将拖拽事件翻译成 QGraphicsSceneDragDropEvent 事件时,再发送到场景,场景接管事件,再把事件发送到光标下接受拖拽的第一个图项。为了开启图项拖拽功能,需要在图项上创建一个 QDrag 对象。

四、场景

  场景 QGraphicsScene图项的容器用于存放和管理图项QGraphicsScene 继承自 QObject,用 QGraphicsScene 类创建场景实例对象的方法如下:

QGraphicsScene(parent:QObject=None)
QGraphicsScene(scencRect:Union[QRectF, QRect], parent:QObject=None)
QGraphicsScene(x:float, y:float, width:float, height:float, parent:QObject=None)

  其中 parent 是继承自 QObject 的实例对象,QRectQRectF 定义场景的范围。用场景范围来确定视图的默认可滚动区域,场景主要使用它来管理图形项索引。如果未设置或者设置为无效的矩形则 sceneRect() 方法将返回自创建场景以来场景中所有图形项的最大边界矩形,即当在场景中添加或移动图形项时范围会增大,但不会减小。

  场景 QGraphicsScene 中添加和移除图项的方法如下:

# 实例方法
addItem(item:QGraphicsItem) -> None                                             # 添加图项
removeItem(item:QGraphicsItem) -> None                                          # 移除图项

# 添加椭圆
addEllipse(rect:Union[QRectF, QRect], pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> QGraphicsEllipseItem

addEllipse(x:float, y:float, width:float, h:float, pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap])

# 添加线
addLine(line:Union[QLineF, QLine], pen::Union[QPen, Qt.PenStyle, QColor]) -> QGraphicsLineItem
addLine(x1:float, y1:float, x2:float, y2:float, pen:Union[QPen, Qt.PenStyle, QColor]) -> QGraphicsLineItem

# 添加路径
addPath(path:QPainterPath, pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> QGraphicsPathItem

addPixmap(pixmap:Union[QPixmap, QImage, str]) -> QGraphicsPixmapItem            # 添加图像

# 添加多边形
addPolygon(polygon:Union[QPolygonF, Sequence[QPointF], QPolygon, QRectF], pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> QGraphicsPolygonItem

# 添加矩形
addRect(rect:Union[QRectF, QRect], pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> QGraphicsRectItem

addRect(x:float, y:float, width:float, height:float, pen:Union[QPen, Qt.PenStyle, QColor], brush:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> QGraphicsRectItem

addSimpleText(text:str, font:Union[QFont, str]) -> QGraphicsSimpleTextItem      # 添加简单文本
addText(text:str, font:Union[QFont, str]) -> QGraphicsTextItem                  # 添加文本

addWidget(widget:QWidget, wFlags:Qt.WindowFlags()) -> QGraphicsProxyWidget      # 添加图形控件

# 槽方法
clear() -> None                                                                 # 清空所有图项

  场景 QGraphicsScene 中获取图项的方法如下:

itemAt(pos:Union[QPointF, QPoint, QPainterPath.Element], deviceTransform:QTransform) -> QGraphicsItem
itemAt(x:float, y:float, deviceTransform:QTransform) -> QGraphicsItem

items(order:Qt.SortOrder=Qt.DescendingOrder) -> list[QGraphicsItem]

items(path:QPainterPath, mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape, order:Qt.SortOrder=Qt.DescendingOrder, deviceTransform:QTransform=QTransform()) -> list[QGraphicsItem]

items(pos:Union[QPolygonF, Sequence[QPointF], QPolygon, QRectF], mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape, order:Qt.SortOrder=Qt.SortOrder.DescendingOrder, deviceTransform:QTransform=QTransform()) -> list[QGraphicsItem]

items(rect:Union[QRectF, QRect], mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape, order:Qt.SortOrder=Qt.SortOrder.DescendingOrder, deviceTransform:QTransform=QTransform()) -> list[QGraphicsItem]

items(x:float, y:float, width:float, height:float, mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape, order:Qt.SortOrder=Qt.SortOrder.DescendingOrder, deviceTransform:QTransform=QTransform()) -> list[QGraphicsItem]

  场景 QGraphicsScene 中其它常用的方法如下:

# 实例方法
setSceneRect(rect:Union[QRectF, QRect]) -> None                                 # 设置场景范围
setSceneRect(x:float, y:float, width:float, height:float) -> None               # 设置场景范围
sceneRect() -> QRectF                                                           # 获取场景范围

width() -> float                                                                # 获取场景宽度
height() -> float                                                               # 获取场景高度

# 获取碰撞的图项列表
collidingItems(item:QGraphicsItem, mode:Qt.ItemSelectionModeQt.ItemSelectionMode.IntersectsItemShape) -> list[QGraphicsItem]

createItemGroup(items:Sequence[QGraphicsItem]) -> QGraphicsItemGroup            # 创建图项组
destroyItemGroup(group:QGraphicsItemGroup) -> None                              # 打散图项组

hasFocus() -> bool                                                              # 获取场景是否获取焦点
clearFocus() -> None                                                            # 清除场景焦点

# 刷新指定的区域
invalidate(x:float, y:float, width:float, height:float, layers:QGraphicsScene.SceneLayer=QGraphicsScene.SceneLayer.AllLayers) -> None

update(x:float, y:float, width:float, height:float) -> None                     # 刷新区域

isActive() -> bool                                                              # 场景用视图显示且视图活跃时返回True

itemsBoundingRect() -> QRectF                                                   # 获取图像的矩形区域

mouseGrabberItem() -> QGraphicsItem                                             # 获取光标抓取的图项

# 将指定区域的图形复制到其它设备的指定区域上
render(painter:QPainter, target:QRectF:QRectF(), source:QRectF=QRectF(), aspectRatioMode:Qt.AspectRatioMode=Qt.AspectRatioMode.KeepAspectRatio) -> None

selectedItems() -> list[QGraphicsItem]                                          # 获取选中的图项列表

setActivePanel(item:QGraphicsItem) -> None                                      # 将场景中的图项设置成活跃图项
activePanel() -> QGraphicsItem                                                  # 获取活跃的图项

setActiveWindow(widget:QGraphicsWidget) -> None                                 # 将场景的视图控件设置成活跃控件

setForegroundBrush(brush:Union[QBrush, QColor, Qt.GlobalColor, QGradient]) -> None  # 设置前景画刷
foregroundBrush() -> QBrush                                                      # 获取前景画刷

setBackgroundBrush(brush:Union[QBrush, QColor, Qt.GlobalColor, QGradient]) -> None  # 设置背景画刷
backgroundBrush() -> QBrush                                                     # 获取背景画刷

setFocus(focusReason:Qt.FocusReason=Qt.OtherFocusReason) -> None                # 使场景获得焦点
setFocusItem(item:QGraphicsItem, focusReason:Qt.FocusReason=Qt.OtherFocusReason) -> None    # 使某个图项获得焦点
focusItem() -> QGraphicsItem                                                    # 获取有焦点的图项

setFocusOnTouch(enabled:bool) -> None                                           # 在平板上通过手碰撞获得焦点

setItemIndexMethod(method:QGraphicsScene.ItemIndexMethod) -> None               # 设置图项搜索方法

setBspTreeDepth(depth:int) -> None                                              # 设置BSP树搜索深度

setMinimumRenderSize(minSize:float) -> None                                     # 图项变化后,尺寸小于设置的尺寸时不渲染

# 将绘图路径内的图项选中,外部的图项取消选中。对于需要选中的图项,必须标记为QGraphicsItem.GraphicsItemFlag.ItemIsSelectable
setSelectionArea(path:QPainterPath, deviceTransform:QTransform) -> None
setSelectionArea(path:QPainterPath, selectionOperation:Qt.ItemSelectionOperation=Qt.ItemSelectionOperation.ReplaceSelection, mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape, deviceTransform:QTransform=QTransform())

selectionArea() -> QPainterPath                                                 # 获取选择区域内的绘图路径

setStickyFocus(enabled:bool) -> None                                            # 单击背景或单击不接受焦点的图项时,是否失去焦点

setFont(font:QFont) -> None                                                     # 设置字体
setPalette(palette:QPalette) -> None                                            # 设置调色板
setStyle(style:QStyle) -> None                                                  # 设置样式

views() -> list[QGraphicsView]                                                  # 获取场景关联的视图控件列表

advance() -> None                                                               # 通知图项可移动

# 虚拟函数
drawForeground(painter:QPainter, rect:QRectF) -> None                           # 重写该函数,绘制前景
drawBackground(painter:QPainter, rect:QRectF) -> None                           # 重写该函数,绘制背景

# 查找一个新的图形控件,以使键盘焦点对准Tab键和Shift+Tab键,如果可以找到则返回true,否则返回false
# 如果next为true则此函数向前搜索,否则向后搜索
focusNextPrevChild(next:bool) -> bool

# 槽方法
# 刷新指定的区域
invalidate(rect:Union[QRectF, QRect], layers:QGraphicsScene.SceneLayer=QGraphicsScene.SceneLayer.AllLayers) -> None

update(rect=Union[QRectF, QRect]) -> None                                       # 更新区域

clearSelection() -> None                                                        # 清空选中 

  场景 QGraphicsScene 的信号如下:

# 图项大的焦点发生改变或者焦点从一个图项转移到另一个图项时发送信号
focusItemChanged(newFocus:QGraphicsItem, oldFocus:QGraphicsItem, reason:Qt.FocusReason)

changed(region:list[QRectF])                                                    # 场景中的内容发生改变时发送信号
sceneRectChanged(rect:QRectF)                                                   # 场景的范围发生改变时发送信号
selectionChanged()                                                              # 场景中选中的图项发生改变时发送信号

  场景中添加从 QGraphicsItem 继承的子类的方法是 addItem(item:QGraphicsItem)。另外还可以添加一些标准的图项,用 addEllipse()addLine()addPath()addPixmap()addPolygon()addRect()addSimpleText()addText()addWidget() 方法可以添加椭圆、直线、绘图路径、图像、多边形、矩形、简单文本、文本和控件,并返回图项。

  其中用 addWidget(widget:QWidget, type:Qt.WindowType)方法可以 将一个控件以代理控件的方法添加到场景中,并返回代理控件,按照添加顺序,后添加的图项会在先添加图项的前端。用 removeItem(item:QGraphicsItem) 方法可以 从场景中移除图项,用 clear() 方法可以 移除所有的图项

  用 itemAt(pos:Union[QPointF, QPoint, QPainterPath.Element], deviceTransform:QTransform)itemAt(x:float, y:float, deviceTransform:QTransform) 方法可以 获得某个位置处 z 值最大的图项,参数 deviceTransform 表示 变换矩阵,可以取 graphicsView.transform()

  用 items() 方法可以 获得某个位置的图项列表,例如 items(point:QPoint,mode:Qt.ItemSelectionMode, order:Qt.SortOrder=Qt.SortOrder.DescendingOrder, deviceTransform:QTransform),其中参数 modeQt.ItemSelectionMode 类型的枚举值,可以取值如下:

Qt.ItemSelectionMode.ContainsItemShape                                          # 完全包含
Qt.ItemSelectionMode.IntersectsItemShape                                        # 完全包含和交叉
Qt.ItemSelectionMode.ContainsItemBoundingRect                                   # 完全包含边界矩形
Qt.ItemSelectionMode.IntersectsItemBoundingRect                                 # 完全包含矩形边界和交叉边界

  参数 orderQt.SortOrder 类型的枚举值,它用于 指顶图项 z 值的顺序,可以取值如下:

Qt.SortOrder.DescendingOrder                                                    # 降序
Qt.SortOrder.AscendingOrder                                                     # 升序

  用 createItemGroup(group:Sequence[QGraphicsItem])方法可以 将多个图项定义成组,并返回 QGraphicsItemGroup 对象,可以把组内的图项当成一个图项进行操作,组内的图项可以同时进行缩放、平移和旋转操作。可以用 QGraphicsItemGroupaddToGroup(item:QGraphicsItem) 方法添加图项,用 removeFromGroup(item:QGraphicsItem) 方法 移除图项

  用 setItemIndexMethod(method:QGraphicsScene.ItemIndexMethod) 方法 设置在场景中搜索图项位置的方法,其中参数 methodQGraphicsScene.ItemIndexMethod 类型的枚举值,可以取值如下:

QGraphicsScene.ItemIndexMethod.BspTreeIndex                                     # BSP树方法,适合静态场景
QGraphicsScene.ItemIndexMethod.NoIndex                                          # 适合动态场景

  场景分为 背景层图项层前景层,分别用 QGraphicsScene.SceneLayer.BackgroundLayerQGraphicsScene.SceneLayer.ItemLayerQGraphicsScene.SceneLayer.ForegroundLayer 表示,这三层可用 QGraphicsScene.SceneLayer.AllLayers 表示。

  用 invalidate(rect:Union[QRectF, QRect], layers:QGraphicsScene.SceneLayers=QGraphicsScene.SceneLayers.AllLayers) 方法或 invalidate(x:float, y:float, width:float, height:float, layers:QGraphicsScene.SceneLayers=QGraphicsScene.SceneLayers.AllLayers) 方法 将指定区域的指定层设置为失效后再重新绘制,以达到更新指定区域的目的,也可用视图控件的 invalidateScene(rect:Union[QRectF, QRect], layers:QGraphicsScene.SceneLayer) 方法达到相同的目的。也可以用 update(rect:Union[QRectF, QRect])update(x:float, y:float, width:float, height:float) 方法更新指定的区域。

  用 addWidget(widget:QWidget, type:Qt.WindowType) 方法可以 将一个控件或窗口嵌入到场景中,该方法返回 QGraphicsProxyWidget,或先创建一个 QGraphicsProxyWidget 实例,手动嵌入控件。

  用 setActivePanel(item:QGraphicsItem) 方法 激活场景中的图项,参数若是 None,场景将停用任何当前活动的图项。如果场景当前处于非活动状态,则图项将保持不活动状态,直到场景变为活动状态为止。用 setActiveWindow(widget:QGraphicsWidget) 方法 激活场景中的视图控件,参数如是 None,场景将停用任何当前活动的视图控件。

五、图项

  QGraphicsItem 类是 QGraphicsScene 中所有图项的基类,用于编写自定义图项,包括定义图项的几何形状、碰撞检测、绘图实现,以及通过其事件处理程序进行图项的交互,继承自 QGraphicsItem 的类有 QAbstractGraphicsShapeItemQGraphicsEllipseItemQGraphicsItemGroupQGraphicsLineItemQGraphicsPathItemQGraphicsPixmapItemQGraphicsPolygonItemQGraphicsRectItemQGraphicsSimpleTextItem。图项支持鼠标拖放、滚轮、右键菜单、按下、释放、移动、双击以及键盘等事件,进行分组和碰撞检测,还可以给图项设置数据。

5.1、QGraphicsItem图项

  用 QGraphicsItem 类创建图项实例对象的方法如下:

QGraphicsItem(parent:QGraphicsItem=None)

  其中 parentQGraphicsItem 的实例,在图项间形成父子关系。

  QGraphicsItem 类的常用方法如下:

# 实例方法
setCacheMode(mode:QGrahpicsItem.CacheMode, cacheSize:QSize=QSize()) -> None     # 设置图项的缓冲模式

childItems() -> list[QGraphicsItem]                                             # 获取子项列表
childrenBoundingRect() -> QRectF                                                # 获取子项的边界矩形

childItems() -> list[QGraphicsItem]                                             # 获取能发生碰撞的图项列表

grabKeyboard() -> None                                                          # 接受键盘的所有事件
ungrabKeyboard() -> None                                                        # 不接受键盘的所有事件

grabMouse() -> None                                                             # 接受鼠标的所有事件
ungrabMouse() -> None                                                           # 不接受鼠标的所有事件

isAncestorOf(child:QGraphicsItem) -> bool                                       # 获取图项是否是指定图项的父辈

isPanel() -> bool                                                               # 获取图项是否是面板
isWidget() -> bool                                                              # 获取图项是否是图形控件QGraphicsWidget

isWindow() -> bool                                                              # 获取图形控件的窗口类型是Qt.WindowType.Window

setSelected(selected:bool) -> None                                              # 设置图项是否被选中
isSelected() -> bool                                                            # 获取图项是否被选中

isUnderMouse() -> bool                                                          # 获取图项是否在光标下

setParentItem(parent:QGraphicsItem) -> None                                     # 设置父图项
parentItem() -> QGraphicsItem                                                   # 获取父图项

setPos(pos:Union[QPointF, QPoint]) -> None                                      # 设置在父图项坐标系中的位置
setPos(x:float, y:float) -> None                                                # 设置在父图项坐标系中的位置
pos() -> QPointF                                                                # 获取图项在父图项坐标系中的位置

setX(x:float) -> None                                                           # 设置图项在父图项坐标系中的x坐标
x() -> float                                                                    # 获取图项在父图项坐标系中的x坐标

setY(y:float) -> None                                                           # 设置图项在父图项坐标系中的y坐标
y() -> float                                                                    # 获取图项在父图项坐标系中的y坐标

setZValue(z:float) -> None                                                      # 设置图项的z值
zValue() -> float                                                               # 获取图项的z值

setRotation(angle:float) -> None                                                # 设置沿z轴顺时针旋转角度
setScale(scale:float) -> None                                                   # 设置图项的缩放系数

moveBy(dx:float, dy:float) -> None                                              # 移动图项

setTransform(matrix:QTransform, combine:bool=false) -> None                     # 设置矩阵变化
resetTransform() -> None                                                        # 重置变换
transform() -> QTransform                                                       # 获取变换矩阵

setTransformations(transformations:Sequence[QGraphicsTransform]) -> None        # 设置变化矩阵

setTransformOriginPoint(origin:Union[QPointF, QPoint]) -> None                  # 设置变换的中心点
setTransformOriginPoint(ax:float, ay:float) -> None                             # 设置变换的中心点
transformOriginPoint() -> QPointF                                               # 获取变换的原点

scene() -> QGraphicsScene                                                       # 获取图项所在的场景
sceneBoundingRect() -> QRectF                                                   # 获取场景的范围
scenePos() -> QPointF                                                           # 获取在场景中的位置
sceneTransform() -> QTransform                                                  # 获取变换矩阵

setAcceptDrops(on:bool) -> None                                                 # 设置是否接受鼠标释放事件
setAcceptedMouseButtons(buttons:Qt.MouseButtons) -> None                        # 设置可接受的鼠标按钮

setActive(active:bool) -> None                                                  # 设置是否活跃
isActive() -> bool                                                              # 获取图项是否活跃

setEnabled(enabled:bool) -> None                                                # 设置是否可用
isEnabled() -> bool                                                             # 获取图项是否可用

setCursor(cursor:Union[QCursor,Qt.CursorShape]) -> None                         # 设置光标形状
unsetCursor() -> None                                                           # 重置光标形状

setData(key:int, value:Any) -> None                                             # 给图项设置数据
data(key:int) -> Any                                                            # 获取图项存储的数据

setFlag(flag:QGraphicsItem.GraphicsItemFlag, enabled:bool=true) -> None         # 设置图项的标志

setFocus(focusReason:Qt.FocusReason=Qt.FocusReason.OtherFocusReason) -> None    # 设置焦点
clearFocus() -> None                                                            # 清除焦点

setGroup(group:QGraphicsItemGroup) -> None                                      # 将图项加入组中
group() -> QGraphicsItemGroup                                                   # 获取图项所在的组

setOpacity(opacity:float) -> None                                               # 设置图项的不透明度

setPanelModality(panelModality:QGraphicsItem.PanelModality) -> None             # 设置面板的模式

setToolTip(toolTip:str) -> None                                                 # 设置提示信息

setVisible(visible:bool) -> None                                                # 设置图项的可见性
isVisible() -> bool                                                             # 获取图项的可见性

show() -> None                                                                  # 显示图项
hide() -> None                                                                  # 隐藏图项,子项也隐藏

stackBefore(sibling:QGraphicsItem) -> None                                      # 在指定的图项前插入

topLevelWidget() -> QGraphicsWidget                                             # 获取顶层图项控件
topLevelItem() -> QGraphicsItem                                                 # 获取顶层图项

update(rect:Union[QRectF, QRect]=QRectF()) -> None                              # 更新指定的区域
update(x:float, y:float, width:float, height:float) -> None                     # 更新指定的区域

# 虚拟方法
abstract paint(painter:QPainter, option:QStyleOptionGraphicsItem, widget:QWidget=None) -> None  # 重写该函数,绘制图形
abstract boundingRect() -> QRectF                                               # 重写该函数,返回边界矩形
itemChange(change:GraphicsItemChange, value:Any) -> Any                         # 重写该函数,以便在图项的状态发生改变时做出响应

# 重写该函数,用于简单动画,由场景的aadvance()函数调用。phase参数为0时,通知图项即将运动,phase参数为1时,通知图项可以运动
advance(phase:int) -> None

# 获取是否能与指定的图项发生碰撞
collidesWithItem(other:QGraphicsItem, mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape) -> bool

# 获取是否能指定的路径发生碰撞
collidesWithPath(path:QPainterPath, mode:Qt.ItemSelectionMode=Qt.ItemSelectionMode.IntersectsItemShape) -> bool

contains(point:QPointF) -> bool                                                 # 获取图项是否包含某个点

shape() -> QPainterPath                                                         # 重写该函数,返回图形的绘图路径

  用户需要从 QGraphicsItem 类继承并创建自己的子图项类,需要在子类中重写 paint(painter:QPainter, option:QStyleOptionGraphicsItem, widget:QWidget=None) 函数和 boundingRect() 函数,boundingRect() 函数的返回值是 图项的范围 QRectF,用于确定图项的边界。paint()中绘制的图形不能超过边界矩形。

  paint() 函数会被视图控件调用,需要在 paint() 函数中用 QPainter 绘制图形,图形是在图项的局部坐标系中绘制的。QPainter钢笔宽度 初始值是 1画刷的颜色QPalette.window线条的颜色QPalette.text。参数 QStyleOptionGraphicsItem绘图选项,参数 QWidget指将绘图绘制到哪个控件上,如果为 None,则绘制到缓存上。

  用 setFlag(flag:QGraphicsItem.GraphicsItemFlag,enabled:bool=True) 方法 设置图项的标志,参数 flagQGraphicsItem.GraphicsItemFlag 类型的枚举值,可以取值如下:

QGraphicsItem.GraphicsItemFlag.ItemIsMovable                                    # 可移动
QGraphicsItem.GraphicsItemFlag.ItemIsSelectable                                 # 可选择
QGraphicsItem.GraphicsItemFlag.ItemIsFocusable                                  # 可获得焦点
QGraphicsItem.GraphicsItemFlag.ItemClipsToShape                                 # 剪切自己的图形,在图项之外不能接收鼠标拖放和悬停事件
QGraphicsItem.GraphicsItemFlag.ItemClipsChildrenToShape                         # 剪切子类的图形,子类不能在该图项之外绘制
QGraphicsItem.GraphicsItemFlag.ItemIgnoresTransformations                       # 忽略来自父图项或视图控件的坐标变换,例如文字可以保持水平或竖直,文字比例不缩放
QGraphicsItem.GraphicsItemFlag.ItemDoesntPropagateOpacityToChildren             # 使用自己的透明设置,不使用父图项的透明设置
QGraphicsItem.GraphicsItemFlag.ItemStacksBehindParent                           # 放置于父图项的后面而不是前面
QGraphicsItem.GraphicsItemFlag.ItemHasNoContents                                # 图项中不绘制任何图形,调用paint()方法也不起任何作用
QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges                         # 该标志使itemChange()函数可以处理图项几何图项的改变
QGraphicsItem.GraphicsItemFlag.ItemAcceptsInputMethod                           # 图项支持亚洲语言
QGraphicsItem.GraphicsItemFlag.ItemNegativeZStacksBehindParent                  # 如果图项的z值是负值,则自动放置于父图项的后面
QGraphicsItem.GraphicsItemFlag.ItemIsPanel                                      # 图项是面板,面板可被激活和获得焦点,在同一时间只有一个面板能被激活,如果没有面板,则激活所有非面板图项
QGraphicsItem.GraphicsItemFlag.ItemSendsScenePositionChanges                    # 该标志是itemChange()函数可以处理图项在视图控件中的位置变化事件
QGraphicsItem.GraphicsItemFlag.ItemContainsChildrenInShape                      # 该标志说明图项的所有子图项的形状范围内绘制

  重写 itemChange(change:QGraphicsItem.GraphicsItemChange, value:Any )函数可以 在图项的状态发生改变 时及时做出反应,用于代替图项的信号,其中 value 值根据状态 change 确定,状态参数 changeQGraphicsItem.GraphicsItemChange 类型的枚举值,可以取值如下所示:

QGraphicsItem.GraphicsItemChange.ItemEnabledChange                              # 图项的激活状态(setEnable())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemEnabledHasChanged                          # 图项的激活状态已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemPositionChange                             # 图项的位置(setPos()、moveBy())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemPositionHasChanged                         # 图项的位置已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemTransformChange                            # 图项的变换矩阵(setTransform())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemTransformHasChanged                        # 图项的变换矩阵已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemRotationChange                             # 图项即将产生旋转(setRotation())时发送通知
QGraphicsItem.GraphicsItemChange.ItemRotationHasChanged                         # 图项已经产生旋转时发送通知

QGraphicsItem.GraphicsItemChange.ItemScaleChange                                # 图项即将进行缩放(setScale())时发送通知
QGraphicsItem.GraphicsItemChange.ItemScaleHasChanged                            # 图项已经进行了缩放时发送通知

QGraphicsItem.GraphicsItemChange.ItemTransformOriginPointChange                 # 图项变换原点(setTransformOriginPoint())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemTransformOriginPointHasChanged             # 图项变换原点已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemSelectedChange                             # 图项选中状态即将改变(setSelected())时发送通知
QGraphicsItem.GraphicsItemChange.ItemSelectedHasChanged                         # 图项的选中状态已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemVisibleChange                              # 图项的可见性(setVisible())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemVisibleHasChanged                          # 图项的可见性已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemParentChange                               # 图项的父图项(setParentItem())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemParentHasChanged                           # 图项的父图项已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemChildAddedChange                           # 图项中即将添加子图项时发送通知
QGraphicsItem.GraphicsItemChange.ItemChildRemovedChange                         # 图项中已经添加子图项时发送通知

QGraphicsItem.GraphicsItemChange.ItemSceneChange                                # 图项即将加入到场景(addItem())中或即将从场景中移除(removeItem())时发送通知
QGraphicsItem.GraphicsItemChange.ItemSceneHasChanged                            # 图项已经加人到场景中或即将从场景中移除时发送通知

QGraphicsItem.GraphicsItemChange.ItemCursorChange                               # 图项的光标形状(setCursor())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemCursorHasChanged                           # 图项的光标形状已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemToolTipChange                              # 图项的提示信息(setToolTip())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemToolTipHasChanged                          # 图项的提示信息已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemFlagsChange                                # 图项的标识(setFlag())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemFlagsHaveChanged                           # 图项的标识即将发生改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemZValueChange                               # 图项的z值(setZValue())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemZValueHasChanged                           # 图项的z值即将改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemOpacityChange                              # 图项的不透明度(setOpacity())即将改变时发送通知
QGraphicsItem.GraphicsItemChange.ItemOpacityHasChanged                          # 图项的不透明度已经改变时发送通知

QGraphicsItem.GraphicsItemChange.ItemScenePositionHasChanged                    # 图项所在的场景的位置已经发生改变时发送通知

要使 itemChange() 函数能处理几何位置改变的通知,需要首先通过 setFlag() 方法给图项设置 QGraphicsItem.GraphicsItemFlag.ItemSendsGeometryChanges 标志,另外也不能在 itemChange() 函数中直接改变几何位置,否则会陷入死循环。

  用 setCacheMode(mode:QGraphicsItem.CacheMode, cacheSize:QSize=Default(QSize)) 方法 设置图项的缓冲模式,可以加快渲染速度。参数 modeQGraphicsItem.CacheMode 类型的枚举值,可以取值如下:

# 默认值,没有缓冲,每次都调用paint()方法重新绘制
QGraphicsItem.CacheMode.NoCache 

# 为图形项的逻辑(本地)坐标启用缓存,第一次绘制该图形项时,它将自身呈现到高速缓存中,然后对于以后的每次显示都重新使用该高速缓存
QGraphicsItem.CacheMode.ItemCoordinateCache

# 对绘图设备的坐标启用缓存,此模式适用于可以移动但不能旋转、缩放或剪切的图项
QGraphicsItem.CacheMode.DeviceCoordinateCache

  场景中有多个图项时,根据图项的 z 值确定哪个图项先绘制,z 值越大会越先绘制,先绘制的图项会放到后绘制的图项的后面。用 setZValue(value:float) 方法 设置图项的 z 值,用 zValue() 方法 获取 z 值。用场景的 addItem() 方法 添加图项 时,图项的初始 z值都是 0.0,这时图项依照添加顺序来显示。如果一个图项有多个子项,则会先显示父图项,再显示子图项。可以用 stackBefore(item:QGraphicsItem) 方法 将图项放到指定图项的前面

  碰撞检测需要重写 shape() 函数来 返回图项的精准轮廓,可以使用默认的 collidesWithItem(item:QGraphicsItem, mode:Qt.ItemSelectionMode=Qt.IntersectsItemShape)定义外形,如果图项的轮廓很复杂,碰撞检测会消耗较长时间。也可重写 collidesWithItem() 函数,提供一个新的图项和轮廓碰撞方法。

  用 setPanelModality(modality:QGraphicsItem.PanelModality) 方法 设置图项的面板模式,图项是面板时会阻止对其他面板的输入,但不阻止对子图项的输入,参数 modalityQGraphicsItem.PanelModality 类型的枚举值,可以取值如下:

QGraphicsItem.PanelModality.NonModal                                            # 默认值,不阻止对其他面板的输入
QGraphicsItem.PanelModality.PanelModal                                          # 阻止对父辈面板的输入
QGraphicsItem.PanelModality.SceneModal                                          # 阻止对场景中所有面板的输入

  可以通过 QGraphicsItem.setAcceptDrops() 方法 设置图项是否支持拖拽功能,还需要重写 QGraphicsItemdragEnterEvent()dragMoveEvent()dropEvent()dragLeaveEvent() 函数。

5.2、标准图项

  除了可以自定义图项外,还可以往场景中添加标准图项,标准图项有 QGraphicsLineItemQGraphicsRectItemQGraphicsPolygonItemQGraphicsEllipseItemQGraphicsPathItemQGraphicsPixmapItemQGraphicsSimpleTextItemQGraphicsTextItem ,分别用场景的 addLine()addRect()addPolygon()addEllipse()addPath()addPixmap()addSimpleText()addText() 方法 直接往场景中添加这些标准图项,并返回指向这些场景的变量,也可以先创建这些标准场景的实例对象,然后用 场景addItem()方法 将标准图项添加到场景中

  这些标准图项的继承关系如图下所示,它们直接或间接继承自 QGraphicsItem,因此也会继承 QGraphicsItem 的方法和属性。

标准图项的继承关系

【1】、直线图项 QGraphicsLineItem

  用 QGraphicsLineItem 创建直线对象的方法如下所示:

QGraphicsLineItem(parent:QGraphicsItem=None)
QGraphicsLineItem(line:Union[QLineF, QLine], parent:QGraphicsItem=None)
QGraphicsLineItem(x1:float, y1:float, x2:float, y2:float, parent:QGraphicsItem=None)

  其中 parent 是继承自 QGraphicsItem 的实例对象。

  QGraphicsLineItem 主要方法是设置直线和钢笔。

setLine(line:Union[QLineF,QLine]) -> None                                       # 设置直线
setLine(x1:float,y1:float,x2:float,y2:float) -> None                            # 设置直线
line() -> QLineF                                                                # 获取线条

setPen(pen:Union[QPen,Qt.PenStyle,QColor]) -> None                              # 设置钢笔
pen() -> QPen                                                                   # 获取钢笔

【2】、矩形图项 QGrphicsRectItem

  用 QGrphicsRectItem 创建矩形对象的方法如下所示:

QGrphicsRectItem(parent:QGraphicsItem=None)
QGraphicsRectItem(rect:Union[QRect, QRectF], parent:QGraphicsItem=None)
QGraphicsRectItem(x:float, y:float, width:float, height:float, parent:QGraphicsItem=None)

  其中 parent 是继承自 QGraphicsItem 的实例对象。

  QGraphicsRectItem 的主要方法如下:

setRect(rect:Union[QRectF, QRect]) -> None
setRect(x:float, y:float, width:float, height:float) -> None
rect() -> QRectF

setPen(pen:Union[QPen, Qt.PenStyle, QColor]) -> None
pen() -> QPen

setBrush(brush:Union[QBrush, Qt.BrushStyle, QColor, Qt.GlobalColor, QGradient, QImage, QPixmap]) -> None
brush() -> QBrush

【3】、多边形图项 QGraphicsPolygonItem

  用 QGraphicsPolygonItem 创建多边形对象的方法如下所示。

QGraphicsPolygonItem(parent:QGraphicsItem=None)
QGraphicsPolygonItem(polygon:Union[QPolygonF, Sequence[QPointF], QPolygon, QRectF], parent:QGraphicsItem=None)

  QGraphicsPolygonItem 的主要方法如下:

setPolygon(polygon:Union[QPolygonF, Sequence[QPointF], QPolygon, QRectF]) -> None
polygon() -> QPolygonF

setFillRule(rule:Qt.FillRule) -> None
fillRule() -> Qt.FillRule

setPen(pen:Union[QPen,Qt.PenStyle,QColor]) -> None
pen() -> QPen

setBrush(brush:Union[QBrush, Qt.BrushStyle, QColor, Qt.GlobalColor, QGradient, QImage, QPixmap]) -> None
brush() -> QBrush

【4】、椭圆图项 QGraphicsEllipseItem

  QGraphicsEllipseItem 可以创建椭圆、圆和扇形对象,创建扇形时需要指定起始角和跨度角,起始角和跨度角需乘以 16 表示角度。用 QGraphicsEllipseItem 类创建椭圆的方法如下所示。

QGraphicsEllipseItem(parent:QGraphicsItem=None)
QGraphicsEllipseItem(rect:Union[QRect, QRectF], parent:QGraphicsItem=None)
QGraphicsEllipseItem(x:float, y:float, width:float, height:float, parent:QGraphicsItem=None)

  QGraphicsEllipseItem 的主要方法如下:

setRect(rect:Union[QRectF, QRect]) -> None
setRect(x:float, y:float, width:float, height:float) -> None
rect() -> QRectF

setSpanAngle(angle:int) -> None
spanAngle() -> int

setStartAngle(angle:int) -> None
startAngle() -> int

setPen(pen:Union[QPen, Qt.PenStyle, QColor]) -> None
pen() -> QPen

setBrush(brush:Union[QBrush, Qt.BrushStyle, QColor, Qt.GlobalColor, QGradient, QImage, QPixmap]) -> None
brush() -> QBrush

【5】、路径图项 QGraphicsPathItem

  QGraphicsPathItemQPainterPath 绘图路径绘制图项。用 QGraphicsPathItem 创建图项的方法如下所示。

QGraphicsPathItem(parent:QGraphicsItem=None)
QGraphicsPathItem(path:QPainterPath, parent:QGraphicsItem=None)

  QGraphicsPathItem 的主要方法如下:

setPath(path:QPainterPath) -> None
path() -> QPainterPath

setPen(pen:Union[QPen, Qt.PenStyle, QColor]) -> None
pen() -> QPen

setBrush(brush:Union[QBrush, Qt.BrushStyle, QColor, Qt.GlobalColor, QGradient, QImage, QPixmap]) -> None
brush() -> QBrush

【6】、图像图项 QGraphicsPixmapItem

  QGraphicsPixmapItem 用于 绘制图像。用 QGraphicsPixmapItem 创建图项的方法如下所示。

QGraphicsPixmapItem(parent:QGraphicsItem=None)
QGraphicsPixmapItem(pixmap:Union[QPixmap, QImage, str], parent:QGraphicsItem=None)

  QGraphicsPixmapItem 的主要方法如下:

setOffset(offset:Union[QPointF, QPoint, QPainterPath.Element]) -> None
setOffset(x:float, y:float) -> None
offset() -> QPointF

setPixmap(pixmap:Union[QPixmap, QImage, str]) -> None
pixmap() -> QPixmap

setShapeMode(QGraphicsPixmapItem.ShapeMode) -> None
setTransformationMode(mode:Qt.TransformationMode) -> None 

  用 setTransformationMode(mode:Qt.TransformationMode) 方法 设置图像是否光滑变换,其中参数 modeQt.TransformationMode 类型的枚举值,可以取值如下:

Qt.TransformationMode.FastTransformation                                        # 快速变换
Qt.TransformationMode.SmoothTransformation                                      # 光滑变换

  用 setShapeMode(mode:QGraphicsPixmapItem.ShapeMode) 方法 设置计算形状的方法,其中参数 modeQGraphicsPixmapItem.ShapeMode 类型的枚举值,可以取如下:

QGraphicsPixmapItem.ShapeMode.MaskShape                                         # 通过调用QPixmap.mask()方法计算形状
QGraphicsPixmapItem.ShapeMode.BoundingRectShape                                 # 通过轮廓确定形状
QGraphicsPixmapItem.ShapeMode.HeuristicMaskShape                                # 通过调用QPixmap.createHeuristicMask()方法确定形状

【7】、纯文本图项 QGraphicsSimpleTextItem

  QGraphicsSimpleTextItem 用于 绘制纯文本,可以设置文本的轮廓和填充颜色。

QGraphicsSimpleTextItem(parent:QGraphicsItem=None)
QGraphicsSimpleTextItem(text:str, parent:QGraphicsItem=None)

  QGraphicsSimpleTextItem 的主要方法如下:

setText(text:str) -> None
text() -> str 

setFont(font:Union[QFont, str, Sequence[str]]) -> None
font() -> QFont

setPen(pen:QPen) -> None                                                        # 绘制文本的轮廓
setBrush(brush:QBrush)                                                          # 设置文本的填充色

【8】、文本图项 QGraphicsTextItem

  用 QGraphicsTextItem 可以 绘制带格式和可编辑的文本,还可以有超链接。用 QGraphicsTextItem 创建图项实例的方法如下所示。

QGraphicsTextItem(parent:QGraphicsItem=None)
QGraphicsTextItem(text:str, parent:QGraphicsItem=None)

  QGraphicsTextItem 的常用方法如下:

adjustSize() -> None                                                            # 调整到合适的尺寸

openExternalLinks() -> bool
setOpenExternalLinks(on:bool)

setDefaultTextColor(Union[QColor, Qt.GlobalColor, QGradient]) -> None

setDocument(document:QTextDocument) -> None
setFont(font:QFont) -> None

setHtml(html:str) -> None
toHtml() -> str 

setPlainText(text:str) -> None
toPlainText() -> str

setTabChangesFocus(on:bool) -> None

setTextCursor(cursor:QTextCursor) -> None

setTextInteractionFlags(Qt.TextInteractionFlag)
setTextWidth(float)

setTextInteractionFlags(Qt.TextInteractionFlag)                                 # 设置文本是否可以交互操作

  用 setTextInteractionFlags(flag:Qt.TextInteractionFlag) 方法设置 文本是否可以交互操作,参数 flagQt.TextInteractionFlag 类型的枚举值,可以取值如下:

Qt.TextInteractionFlag.NoTextInteraction
Qt.TextInteractionFlag.TextSelectableByMouse
Qt.TextInteractionFlag.TextSelectableByKeyboard
Qt.TextInteractionFlag.LinksAccessibleByMouse
Qt.TextInteractionFlag.LinksAccessibleByKeyboard
Qt.TextInteractionFlag.TextEditable

# 指Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.TextSelectableByKeyboard | Qt.TextInteractionFlag.TextEditable
Qt.TextInteractionFlag.TextEditorInteraction

# 指Qt.TextInteractionFlag.TextSelectableByMouse | Qt.TextInteractionFlag.LinksAccessibleByMouse | Qt.TextInteractionFlag.LinksAccessibleByKeyboard
Qt.TextInteractionFlag.TextBrowserInteraction

  新建一个 graphicsView.py 文件,定义了视图控件的子类。

from PySide6.QtWidgets import QGraphicsView
from PySide6.QtCore import Signal, QPointF
from PySide6.QtGui import QMouseEvent

# 视图控件的子类
class MyGraphicsView(QGraphicsView):
    press_point = Signal(QPointF)                                               # 自定义信号,参数是鼠标被按下时鼠标在视图的位置
    move_point = Signal(QPointF)                                                # 自定义信号,参数是鼠标移动时鼠标在视图的位置
    release_point = Signal(QPointF)                                             # 自定义信号,参数是鼠标被释放时鼠标在视图的位置

    def __init__(self, parent=None):
        super().__init__(parent)

    def mousePressEvent(self, event:QMouseEvent):
        """鼠标单击信号"""
        self.press_point.emit(event.position())                                 # 发送信号,参数是光标位置
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event:QMouseEvent):
        """鼠标移动信号"""
        self.move_point.emit(event.position())                                  # 发送信号,参数是光标位置
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event:QMouseEvent):
        """鼠标释放信号"""
        self.release_point.emit(event.position())                               # 发送信号,参数是光标位置
        super().mouseReleaseEvent(event)

  新建一个 ui.py 文件,用来存放 UI 相关的代码。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsScene, QGraphicsView, QGraphicsItem
from PySide6.QtWidgets import QToolBar
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtCore import QRectF

from graphicsView import MyGraphicsView

class MyUi:
    def setupUi(self, window:QWidget):
        self.shape = {"直线": False, "矩形": False, "椭圆": False, "圆": False}  # 用于记录哪个绘图按钮被选中
        self.temp:Optional[QGraphicsItem] = None                                # 用于指向鼠标移动时产生的临时图形项

        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建垂直布局

        self.toolBar = QToolBar()                                               # 3.创建工具栏,并添加到垂直布局中
        layout.addWidget(self.toolBar)

        self.graphicsView = MyGraphicsView()                                    # 4.创建一个GraphicsView视图窗口,并添加到垂直布局中
        layout.addWidget(self.graphicsView)

        self.draw_line_action = self.toolBar.addAction("直线")                  # 5.为工具栏添加动作
        self.draw_rectangle_action = self.toolBar.addAction("矩形")
        self.draw_ellipse_action = self.toolBar.addAction("椭圆")
        self.draw_circle_action = self.toolBar.addAction("圆")
        self.toolBar.addSeparator()                                             # 6.添加分割线
        self.stop_action = self.toolBar.addAction("停止")
        self.delete_action = self.toolBar.addAction("删除")
        self.clear_action = self.toolBar.addAction("清空")

        self.graphicsView.setViewportUpdateMode(QGraphicsView.ViewportUpdateMode.FullViewportUpdate)    # 7.设置刷新模式

        scene_rect = QRectF(window.width() / 2, window.height() / 2, window.width(), window.height())
        self.graphicsScene = QGraphicsScene(scene_rect)                         # 8.创建图项场景

        self.graphicsView.setScene(self.graphicsScene)                          # 9.图项视图设置图项场景

  新建一个 widget.py 文件,用来存放业务逻辑相关的代码。

import sys
import math

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QGraphicsItem
from PySide6.QtCore import QPointF, QRectF, QLineF

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        self.__ui.graphicsView.press_point.connect(self.mouse_press)
        self.__ui.graphicsView.move_point.connect(self.mouse_move)
        self.__ui.graphicsView.release_point.connect(self.mouse_release)

        self.__ui.draw_line_action.triggered.connect(self.draw_line_action_triggered)
        self.__ui.draw_rectangle_action.triggered.connect(self.draw_rectangle_action_triggered)
        self.__ui.draw_ellipse_action.triggered.connect(self.draw_ellipse_action_triggered)
        self.__ui.draw_circle_action.triggered.connect(self.draw_circle_action_triggered)

        self.__ui.stop_action.triggered.connect(self.stop_action_triggered)
        self.__ui.delete_action.triggered.connect(self.delete_action_triggered)
        self.__ui.clear_action.triggered.connect(self.__ui.graphicsScene.clear)
        self.__ui.clear_action.triggered.connect(self.__ui.graphicsScene.update)

    def mouse_press(self, point:QPointF):
        self.__press_mouse = self.__ui.graphicsView.mapToScene(point.toPoint()) # 1.映射成场景坐标

    def mouse_move(self, point:QPointF):
        self.__move_mouse = self.__ui.graphicsView.mapToScene(point.toPoint())  # 1.映射成场景坐标
        self.drawing_graphics_while_moving(self.__press_mouse, self.__move_mouse)    # 2.鼠标移动时绘制图项

    def mouse_release(self, point:QPointF):
        # 1.鼠标释放时,如果有图项,则将图项设置为可选中状态标志和可获得焦点标志,然后清空临时图项
        if self.__ui.temp:     
            self.__ui.temp.setFlags(QGraphicsItem.GraphicsItemFlag.ItemIsSelectable | QGraphicsItem.GraphicsItemFlag.ItemIsFocusable)
            self.__ui.temp = None

        graphics_rect = self.__ui.graphicsScene.itemsBoundingRect()             # 2.获取图像的矩形区域
        if graphics_rect.width() > self.width() or graphics_rect.height() > self.height():
            self.__ui.graphicsScene.setSceneRect(graphics_rect)                 # 3.设置场景的矩形区域

    def draw_line_action_triggered(self):
        self.__ui.shape = {"直线": True, "矩形": False, "椭圆": False, "圆": False}

    def draw_rectangle_action_triggered(self):
        self.__ui.shape = {"直线": False, "矩形": True, "椭圆": False, "圆": False}

    def draw_ellipse_action_triggered(self):
        self.__ui.shape = {"直线": False, "矩形": False, "椭圆": True, "圆": False}

    def draw_circle_action_triggered(self):
        self.__ui.shape = {"直线": False, "矩形": False, "椭圆": False, "圆": True}

    def stop_action_triggered(self):
        self.__ui.shape = {"直线": False, "矩形": False, "椭圆": False, "圆": False}

    def delete_action_triggered(self):
        if len(self.__ui.graphicsScene.selectedItems()):
            for item in self.__ui.graphicsScene.selectedItems():                # 1.获取选中的图项,然后删除
                self.__ui.graphicsScene.removeItem(item)

    def drawing_graphics_while_moving(self, p1:QPointF, p2:QPointF):
        if self.__ui.temp:                                                      # 2.在鼠标移动过程中,如果变量已经指向图形项,则需要把图形项移除
            self.__ui.graphicsScene.removeItem(self.__ui.temp)

        if self.__ui.shape["直线"]:
            self.__ui.temp = self.__ui.graphicsScene.addLine(QLineF(p1, p2))    # 绘制直线
        elif self.__ui.shape["矩形"]:
            self.__ui.temp = self.__ui.graphicsScene.addRect(QRectF(p1, p2))    # 绘制矩形
        elif self.__ui.shape["椭圆"]:
            self.__ui.temp = self.__ui.graphicsScene.addEllipse(QRectF(p1, p2)) # 绘制椭圆
        elif self.__ui.shape["圆"]:
            r = math.sqrt((p1.x() - p2.x()) ** 2 + (p1.y() - p2.y()) ** 2)
            point1 = QPointF(p1.x() - r, p1.y() - r)
            point2 = QPointF(p1.x() + r, p1.y() + r)
            self.__ui.temp = self.__ui.graphicsScene.addEllipse(QRectF(point1, point2))


if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

六、代理控件

  通过场景的 addWidget(widget:QWidget, flags:Qt.WindowFlags) 方法可以 把一个控件或窗口加入到场景中,并返回代理类控件 QGraphicsProxyWidget。代理类控件可以将 QWidget 类控件加入到场景中,可以先创建 QGraphicsProxyWidget 控件,然后用场景的 addItem(widget:QGraphicsProxyWidget) 方法 把代理控件加入到场景中

  代理控件 QGraphicsProxyWidget 继承自图形控件 QGraphicsWidget,它们之间的继承关系如图下所示。

图形控件与代理控件与图形布局的继承关系

  用 QGraphicsProxyWidget 类创建代理实例对象的方法如下:

QGraphicsProxyWidget(parent:QGraphicsItem=None, flats:Qt.WindowFlags)

  其中 parentQGraphicsItem 的实例。

  QGraphicsProxyWidget添加控件 的方法是 setWidget(widget:QWidget)QWidget 不能有 WA_PaintOnScreen 属性,也不能是包含其他程序的控件。我们还可以使用 widget() 方法可以 获取代理控件中的控件。代理控件和其内部包含的控件在状态方面保持同步,例如可见性、激活状态、字体、调色板、光标形状、窗口标题、几何尺寸、布局方向等。

  新建一个 frame.py 文件。

import os

from PySide6.QtGui import QPaintEvent
from PySide6.QtWidgets import QFrame
from PySide6.QtGui import QPixmap, QPainter

class MyFrame(QFrame):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.file_name = ""

    def paintEvent(self, event:QPaintEvent):
        """重写paintEvent()方法,完成绘图"""
        if os.path.exists(self.file_name):                                      # 1.判断文件是否存在
            pixmap = QPixmap(self.file_name)                                    # 2.创建QPixmap对象
            painter = QPainter(self)                                            # 3.创建QPainter对象
            painter.drawPixmap(self.rect(), pixmap)                             # 4.绘制图片
        super().paintEvent(event)                                               # 5.调用父类的paintEvent()方法

  新建一个 pixmapWidget.py 文件。

from typing import Optional
from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QToolBar
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtGui import Qt, QIcon

from frame import MyFrame

class MyPixmapWidget(QWidget):
    def __init__(self, parent:Optional[QWidget]=None):
        super().__init__(parent)                                                # 1.调用父类的初始化方法

        self.resize(600, 400)                                                   # 2.设置控件的大小

        layout = QVBoxLayout(self)                                              # 3.创建垂直布局

        self.toolBar = QToolBar()                                               # 4.创建工具栏,并添加到垂直布局中
        layout.addWidget(self.toolBar)

        self.frame = MyFrame()                                                  # 5.创建一个QFrame对象
        layout.addWidget(self.frame)

        self.toolBar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon) # 6.设置工具栏按钮的样式

        # 7.创建工具栏按钮
        self.open_image_file_action = self.toolBar.addAction(QIcon("assets/images/1.ico"), "打开图片文件")

  修改 ui.py 文件的内容。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsProxyWidget
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtGui import Qt, QTransform


from pixmapWidget import MyPixmapWidget

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        self.view = QGraphicsView()                                             # 3.创建一个视图控件,并添加到垂直布局中
        layout.addWidget(self.view)

        self.scene = QGraphicsScene()                                           # 4.创建一个场景
        self.view.setScene(self.scene)                                          # 5.在视图中设置场景

        self.proxy = QGraphicsProxyWidget(None, Qt.WindowType.Window)           # 6.创建一个代理控件
        self.scene.addItem(self.proxy)                                          # 7.在场景中添加代理控件图项

        self.pixmap = MyPixmapWidget()                                          # 8.创建一个自定义的图片控件
        self.proxy.setWidget(self.pixmap)                                       # 9.代理控件中设置控件

        self.proxy.setTransform(QTransform().shear(1, -0.5))                    # 10.代理控件的错切变换

  修改 widget.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QFileDialog

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        self.__ui.pixmap.open_image_file_action.triggered.connect(self.choose_file) # 3.打开图片文件动作被点击时触发信号

    def choose_file(self):
        # 1.打开文件选择对话框
        file_name, filter = QFileDialog.getOpenFileName(self, "打开图片", "assets/images", "*.png *.jpg *.jpeg *.gif")
        if file_name:
            self.__ui.pixmap.frame.file_name = file_name                        # 2.更新文件名
            self.__ui.pixmap.frame.update()                                     # 3.更新控件


if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

七、图形控件

  场景中除了可以直接添加图项外,也可以添加常用的控件,甚至是对话框,场景中的控件也可以进行布局。

7.1、QGraphicsWidget图形控件

  图形控件 QGraphicsWidget 的继承关系如图下所示,它继承自 QGraphicsObjectQGraphicsLayoutItem,间接继承自 QObjectQGraphicsItem,因此它可以直接添加到场景中。

QGraphicsWidget的继承关系

  QGraphicsWidget 是图形控件的基类,继承 QGraphicsWidget 的类有 QGraphicsProxyWidgetQtCharts.QChartQtCharts.QLegendQtCharts.QPolarChartQWidget 继承自 QObjectQPaintDevice。在 QGraphicsWidget 中可以放置其它代理控件和布局,因此 QGraphicsWidget 可以作为场景中的容器使用。

  用 QGraphicsWidget 类创建图形控件的方法如下:

QGraphicsWidget(parent:QGraphicsItem=None, fags:Qt.WindowFlags=Default(Qt.WindowFlags))

  其中 parentQGraphicsItem 的实例。

  图形控件 QGraphicsWidget 的常用方法下所示:

# 实例方法
setAttribute(attribute:Qt.WidgetAttribute, on:bool=true) -> None                # 设置属性
testAttribute(attribute:Qt.WidgetAttribute) -> bool                             # 测试是否设置了某种属性

setLayout(layout:QGraphicsLayout) -> None                                       # 设置布局
layout() -> QGraphicsLayout                                                     # 获取布局

setLayoutDirection(direction:Qt.LayoutDirection) -> None                        # 设置布局方向
layoutDirection() -> Qt.LayoutDirection                                         # 获取布局方向

setAutoFillBackground(enabled:bool) -> None                                     # 设置自动填充背景

setContentsMargins(margins:Union[QMaginsF, QMargins]) -> None                   # 设置窗口内的控件到边框的最小距离
setContentsMargins(left:float, top:float, right:float, bottom:float) -> None    # 设置窗口内的控件到边框的最小距离

setFocusPolicy(policy:Qt.FocusPolicy) -> None                                   # 设置获取焦点的策略

setFont(font:QFont) -> None                                                     # 设置字体

setGeometry(rect:Union[QRectF, QRect]) -> None                                  # 设置控件的位置和尺寸
setGeometry(x:float, y:float, width:float, height:float) -> None                # 设置控件的位置和尺寸

setPalette(palette:QPalette) -> None                                            # 设置调色板

setStyle(style:QStyle) -> None                                                  # 设置样式

setWindowFlags(flags:Qt.WindowType) -> None                                     # 设置窗口的标识

setWindowFrameMargins(margins:Union[QMaginsF, QMargins]) -> None                # 设置边框距
setWindowFrameMargins(left:float, top:float, right:float, bottom:float) -> None # 设置边框距

setWindowTitle(title:str) -> None                                               # 设置窗口标题

rect() -> QRectF                                                                # 获取图形控件的窗口范围

resize(size:QSizeF) -> None                                                     # 设置窗口尺寸
resize(width:float, height:float) -> None                                       # 设置窗口尺寸
size() -> QSizeF                                                                # 获取窗口尺寸

focusWidget() -> QGraphicsWidget                                                # 获取焦点控件

isActiveWindow() -> bool                                                        # 获取是否是活跃控件

addAction(action:QAction) -> None                                               # 图形控件中添加动作
addActions(actions:Sequence[QAction]) -> None                                   # 图形控件中添加多个动作

insertAction(before:QAction, action:QAction) -> None                            # 在指定动作之前添加动作
insertActions(before:QAction, actions:Sequence[QAction]) -> None                # 在指定动作之前添加多个动作

actions() -> list[QAction]                                                      # 获取所有动作

removeAction(action:QAction) -> None                                            # 删除动作

# 静态方法
static setTabOrder(first:QGraphicsWidget, second:QGraphicsWidget) -> None       # 设置按Tab键获取焦点的顺序

# 继承于QGraphicsItem的方法
itemChange(change:QGraphicsItem.GraphicsItemChange, value:Any) -> Any           # 重写该函数,作为信号使用
abstract paint(painter:QPainter, option:QStyleOptionGraphicsItem, widget:QWidget=None) -> None  # 重写该函数,绘制图形
abstract boundingRect() -> QRectF                                               # 重写该函数,返回图形的边界矩形
shape() -> QPainterPath                                                         # 重写该函数,返回图形的路径

# 基于于QGraphicsLayoutItemd的虚拟方法
updateGeometry() -> None                                                        # 更新控件

  QGraphicsWidget 的常用的信号如下:

geometryChanged()                                                               # 当几何尺寸发生改变时发送信号
layoutChanged()                                                                 # 当几何布局发生改变时发送信号

  QGraphicsWidgetQGraphicsObject 继承的信号如下:

opacityChanged()
parentChanged()
rotationChanged()
scaleChanged()
visibleChanged()
xChanged()
yChanged()
zChanged()

  其中用 setAttribute(attribute:Qt.WidgetAttribute, on:bool=True) 方法 设置窗口的属性,参数 attributeQt.WidgetAttribute 类型的枚举值,可以取值如下:

Qt.WidgetAttribute.WA_SetLayoutDirection
Qt.WidgetAttribute.WA_RightToLeft
Qt.WidgetAttribute.WA_SetStyle
Qt.WidgetAttribute.WA_Resized
Qt.WidgetAttribute.WA_SetPalette
Qt.WidgetAttribute.WA_SetFont
Qt.WidgetAttribute.WA_WindowPropagation

7.2、图形控件的布局

  图形控件可以添加布局,图形控件的布局有 3 种,分别为 QGraphicsLinearLayoutQGraphicsGridLayoutQGraphicsAnchorLayout,它们都继承自 QGraphicsLayoutItem

图形控件布局类的继承关系

7.2.1、线性布局

  线性布局 QGraphicsLinearLayou t类似于 QHLayoutBoxQVLayoutBox,布局内的图形控件 线性分布。用 QGraphicsLinearLayout 创建线性布局的方法如下:

QGraphicsLinearLayout(parent:QGraphicsLayoutItem=None)
QGraphicsLinearLayout(oriention:Qt.Oriention, parent:QGraphicsLayoutItem=None)

  其中 parentQGraphicsLayoutItem 的实例;Qt.Orientation 确定布局的方法,可以取 Qt.Orientation.HorizontalQt.Orientation.Vertical,默认是 水平方向

QGraphicsLinearLayout 类的常用方法如下:

addItem(item:QGraphicsLayoutItem) -> None                                       # 添加图形控件、代理控件或布局
insertItem(index:int, item:QGraphicsLayoutItem) -> None                         # 根据索引插入图形控件或布局

addStretch(stretch:int=1) -> None                                               # 在末尾添加拉伸系数
insertStretch(index:int, stretch:int=1) -> None                                 # 根据索引插入拉伸系数

setStretchFactor(item:QGraphicsLayoutItem, stretch:int) -> None                 # 设置图形控件的拉伸系数
stretchFactor(item:QGraphicsLayoutItem) -> int                                  # 获取图形控件的拉伸系数

setSpacing(spacing:int) -> None                                                 # 设置图形控件之间的间距
setItemSpacing(index:int, spacing:int) -> None                                  # 根据索引设置间距

setOrientation(orientation:Qt.Orientation) -> None                              # 设置布局方向

setAlignment(item:QGraphicsLayoutItem, alignment:Qt.Alignment) -> None          # 设置图形控件的对齐方式

removeItem(item:QGraphicsLayoutItem) -> None                                    # 移除指定的图形或布局

# 继承于QGraphicsLayout的虚拟方法
count() -> int                                                                  # 获取弹性控件和布局个数
abstract itemAt(index:int) -> QGraphicsLayoutItem                               # 根据索引获取图形控件或布局
abstract removeAt(index:int) -> None                                            # 根据索引移除图形控件或布局

# 继承于QGraphicsLayoutItem的虚拟方法
setGeometry(rect:Union[QRectF, QRect]) -> None                                  # 设置布局的位置和尺寸

  修改 ui.py 文件的内容。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene
from PySide6.QtWidgets import QGraphicsWidget, QGraphicsProxyWidget
from PySide6.QtWidgets import QGraphicsLinearLayout
from PySide6.QtWidgets import QPushButton
from PySide6.QtGui import Qt

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        view = QGraphicsView()                                                  # 3.创建一个视图,并添加到垂直布局中
        layout.addWidget(view)

        scene = QGraphicsScene()                                                # 4.创建一个场景
        view.setScene(scene)                                                    # 5.将场景添加到视图中

        widget = QGraphicsWidget()                                              # 6.创建一个图形控件
        scene.addItem(widget)                                                   # 7.将图形控件添加到场景中

        # 8.设置图形控件的属性
        widget.setFlags(QGraphicsWidget.GraphicsItemFlag.ItemIsMovable | QGraphicsWidget.GraphicsItemFlag.ItemIsSelectable)

        linear_layout = QGraphicsLinearLayout(Qt.Orientation.Vertical, widget)  # 9.创建一个线性布局

        # 10.创建具体的控件
        button1 = QPushButton("Button1")
        button2 = QPushButton("Button2")
        button3 = QPushButton("Button3")
        button4 = QPushButton("Button4")

        # 11.创建一个代理控件,然后设置控件
        proxy_widget1 = QGraphicsProxyWidget()
        proxy_widget1.setWidget(button1)

        proxy_widget2 = QGraphicsProxyWidget()
        proxy_widget2.setWidget(button2)

        proxy_widget3 = QGraphicsProxyWidget()
        proxy_widget3.setWidget(button3)

        proxy_widget4 = QGraphicsProxyWidget()
        proxy_widget4.setWidget(button4)

        # 12.线性布局中添加控件
        linear_layout.addItem(proxy_widget1)
        linear_layout.addItem(proxy_widget2)
        linear_layout.addItem(proxy_widget3)
        linear_layout.addItem(proxy_widget4)

        linear_layout.setSpacing(10)                                            # 13.设置图形控件之间的间距

        # 14.设置图形控件的拉伸系数
        linear_layout.setStretchFactor(proxy_widget3, 1)
        linear_layout.setStretchFactor(proxy_widget4, 2)

  修改 widget.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication, QWidget

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束

7.2.2、栅格布局

  栅格布局 QGraphicsGridLayout 由多行和多列构成,一个图形控件可以占用一个节点,也可以占用多行和多列。用 QGraphicsGridLayout 创建格栅布局的方法如下所示:

QGraphicsGridLayout(parent:QGraphicsLayoutItem=None)

  其中 parentQGraphicsLayoutItem 的实例或图形控件。

  QGraphicsGridLayout 类的常用方法如下:

# 在指定的位置添加图形控件
addItem(item:QGraphicsLayoutItem, row:int, column:int, alignment:Qt.Alignment=Qt.Alignment()) -> None

# 添加图形控件,可占据多行多列
addItem(item:QGraphicsLayoutItem, row:int, column:int, rowSpan:int, columnSpan:int, alignment:Qt.Alignment=Qt.Alignment()) -> None

rowCount() -> int                                                               # 获取行数
columnCount() -> int                                                            # 获取列数

itemAt(row:int, column:int) -> QGraphicsLayoutItem                              # 获取指定行和列处的图形控件或布局

removeItem(item:QGraphicsLayoutItem) -> None                                    # 移除指定的图形控件或布局

setAlignment(item:QGraphicsLayoutItem, alignment:Qt.Alignment) -> None          # 设置控件的对齐方式
setRowAlignment(row:int, alignment:Qt.Alignment) -> None                        # 设置行对齐方式
setColumnAlignment(column:int, alignment:Qt.Alignment) -> None                  # 设置列对齐方式

setRowFixedHeight(row:int, height:float) -> None                                # 设置行的固定高度
setRowMinimumHeight(row:int, height:float) -> None                              # 设置行的最小高度
setRowMaximumHeight(row:int, height:float) -> None                              # 设置行的最大高度
setRowPreferredHeight(row:int, height:float) -> None                            # 设置指定行的高度
setRowStretchFactor(row:int, stretch:int) -> None                               # 设置指定行的拉伸系数

setColumnFixedWidth(column:int, width:float) -> None                            # 设置列的固定宽度
setColumnMinimumWidth(column:int, width:float) -> None                          # 设置列的最小宽度
setColumnMaximumWidth(column:int, width:float) -> None                          # 设置列的最大宽度
setColumnPreferredWidth(column:int, width:float) -> None                        # 设置指定列的宽度

setColumnStretchFactor(column:int, stretch:int) -> None                         # 设置指定列的拉伸系数

setSpacing(spacing:int) -> None                                                 # 设置行、列之间的间距
setRowSpacing(row:int, spacing:float) -> None                                   # 设置指定行的间距
setColumnSpacing(column:int, spacing:int) -> None                               # 设置指定列的间距

setHorizontalSpacing(spacing:int) -> None                                       # 设置水平间距
setVerticalSpacing(spacing:int) -> None                                         # 设置垂直间距

# 继承于QGraphicsLayout的虚拟方法
abstract count() -> int                                                         # 获取图形控件和布局的个数

abstract itemAt(index:int) -> QGraphicsLayoutItem                               # 根据索引获取图形控件或布局
abstract removeAt(index:int) -> None                                            # 根据索引移除图形控件或布局

# 继承于QGraphicsLayoutItem的虚拟方法
setGeometry(rect:Union[QRectF, QRect]) -> None                                  # 设置控件所在的区域

  修改 ui.py 文件的内容。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene
from PySide6.QtWidgets import QGraphicsWidget, QGraphicsProxyWidget
from PySide6.QtWidgets import QGraphicsGridLayout
from PySide6.QtWidgets import QPushButton

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(800, 600)                                                 # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        view = QGraphicsView()                                                  # 3.创建一个视图,并添加到垂直布局中
        layout.addWidget(view)

        scene = QGraphicsScene()                                                # 4.创建一个场景
        view.setScene(scene)                                                    # 5.将场景添加到视图中

        widget = QGraphicsWidget()                                              # 6.创建一个图形控件
        scene.addItem(widget)                                                   # 7.将图形控件添加到场景中

        # 8.设置图形控件的属性
        widget.setFlags(QGraphicsWidget.GraphicsItemFlag.ItemIsMovable | QGraphicsWidget.GraphicsItemFlag.ItemIsSelectable)

        grid_layout = QGraphicsGridLayout(widget)                               # 9.创建一个栅格布局

        # 10.创建具体的控件
        button1 = QPushButton("Button1")
        button2 = QPushButton("Button2")
        button3 = QPushButton("Button3")
        button4 = QPushButton("Button4")
        button5 = QPushButton("Button5")

        # 11.创建一个代理控件,然后设置控件
        proxy_widget1 = QGraphicsProxyWidget()
        proxy_widget1.setWidget(button1)

        proxy_widget2 = QGraphicsProxyWidget()
        proxy_widget2.setWidget(button2)

        proxy_widget3 = QGraphicsProxyWidget()
        proxy_widget3.setWidget(button3)

        proxy_widget4 = QGraphicsProxyWidget()
        proxy_widget4.setWidget(button4)

        proxy_widget5 = QGraphicsProxyWidget()
        proxy_widget5.setWidget(button5)

        # 21.线性布局中添加控件
        grid_layout.addItem(proxy_widget1, 0, 0)
        grid_layout.addItem(proxy_widget2, 0, 1)
        grid_layout.addItem(proxy_widget3, 1, 0)
        grid_layout.addItem(proxy_widget4, 1, 1)
        grid_layout.addItem(proxy_widget5, 2, 0, 1, 2)

        grid_layout.setSpacing(10)                                              # 13.设置图形控件之间的间距

7.2.3、锚点布局

  锚点布局可以 设置两个图形控件之间的相对位置,可以是两个边对齐,也可以是两个点对齐。用 QGraphicsAnchorLayout 创建锚点布局的方法如下:

QGraphicsAnchorLayout(parent:QGraphicsLayoutItem=None)

  其中参数 parentQGraphicsLayoutItem 实例。

  QGraphicsAnchorLayout 类的常用方法如下:

# 将第1个图形控件的某个边与第2个图形控件的某个边对齐
addAnchor(firstItem:QGraphicsLayoutItem, firstEdge:Qt.AnchorPoint, secondItem:QGraphicsLayoutItem, secondEdge:Qt.AnchorPoint) -> None

# 第1个控件的某个角点与第2个控件的某个角点对齐
addCornerAnchors(firstItem:QGraphicsLayoutItem, firstCorner:Qt.Corner, secondItem:QGraphicsLayoutItem, secondCorner:Qt.Corner) 

# 使两个控件在某个方向上尺寸相等
addAnchors(firstItem:QGraphicsLayoutItem, secondItem:QGraphicsLayoutItem, orientations:Qt.Orientations=Qt.Horizontal | Qt.Vertical) -> None

setHorizontalSpacing(spacing:float) -> None                                     # 设置控件之间在水平方向的间距
setVerticalSpacing(spacing:float) -> None                                       # 设置控件之间在竖直方向的间距
setSpacing(spacing:float) -> None                                               # 设置控件之间在水平和竖直方向的间距                                    

# 继承于QGraphicsLayout的虚拟方法
abstract itemAt(index:int) -> QGraphicsLayoutItem                               # 获取图形控件
removeAt(index:int) -> None                                                     # 移除图形控件
count() ->int                                                                   # 获取图形控件的数量

  addAnchor(firstItem:QGraphicsLayoutItem, firstEdge:Qt.AnchorPoint, secondItem:QGraphicsLayoutItem, secondEdge:Qt.AnchorPoint) 方法将 第 1 个图形控件的某个边与第 2 个图形控件的某个边对齐,其中参数 firstEdgeQt.AnchorPoint 类型的枚举值,可以取值如下:

Qt.AnchorPoint.AnchorLeft
Qt.AnchorPoint.AnchorHorizontalCenter
Qt.AnchorPoint.AnchorRight
Qt.AnchorPoint.AnchorTop
Qt.AnchorPoint.AnchorVerticalCenter
Qt.AnchorPoint.AnchorBottom

  用 addCornerAnchors(firstItem:QGraphicsLayoutItem, firstCorner:Qt.Corner,secondItem:QGraphicsLayoutItem, secondCorner:Qt.Corner) 方法可以让 第 1 个控件的某个角点与第 2 个控件的某个角点对齐,其中参数 cornetQt.Corner 类型的枚举值,可以取值如下:

Qt.Corner.TopLeftCorner
Qt.Corner.TopRightCorner
Qt.Corner.BottomLeftCorner
Qt.Corner.BottomRightCorner

八、图形效果

  在图项和视图控件的视口之间可以添加渲染通道,实现对图项显示效果的特殊设置。图形效果 QGraphicsEffect 类是图形效果的基类,图形效果类有 QGraphicsBlurEffect模糊效果)、QGraphicsColorizeEffect变色效果)、QGraphicsDropShadowEffect阴影效果)和 QGraphicsOpacityEffect透明效果)。

图形效果类的继承关系

  其中 parent 是指继承自 QObject 的实例。

【1】、模糊效果类 QGraphicsBlurEffect

  使用 QGraphicsBlurEffect 类可以 创建模糊效果对象,使用模糊效果对象可以对图形项的显示设置模糊效果。QGraphicsBlurEffect 类的构造函数如下:

QGraphicsBlurEffect(parent:QWidget=None)

  其中 parent 是指继承自 QObject 的实例。

  QGraphicsBlurEffect 类的常用方法如下:

# 实例方法
blurHints() -> QGraphicsEffect.BlurHint                                         # 获取模糊提示
blurRadius() -> float                                                           # 获取模糊半径

# 槽方法
setEnabled(enable:bool) -> None                                                 # 设置激活图形效果
setBlurHints(hints:QGraphicsEffect.BlurHint) -> None                            # 设置模糊提示
setBlurRadius(blurRadius:float) -> None                                         # 设置模糊半径

  QGraphicsBlurEffect 类的常用信号如下:

blurHintsChanged(hints:QGraphicsEffect.BlurHint)                                # 模糊提示发生改变时发送信号
blurRadiusChanged(blurRadius:float)                                             # 模糊半径发生改变时发送信号

  模糊效果是使图项变得模糊不清,可以隐藏一些细节。在一个图项失去焦点,或将注意力移到其他图项上时,可以使用模糊效果。QGraphicsBlurEffect 的模糊效果是通过设置模糊半径和模糊提示实现的。模糊效果默认的模糊半径为 5 个像素,模糊半径越大图像越模糊。

  用 setBlurHints(hints:QGraphicsEffect.BlurHint) 函数设置模糊提示,参数 hintsQGraphicsBlurEffect.BlurHint 类型的枚举值,可以取值如下:

QGraphicsBlurEffect.BlurHint.PerformanceHint                                    # 主要考虑渲染性能
QGraphicsBlurEffect.BlurHint.QualityHint                                        # 主要考虑渲染质量
QGraphicsBlurEffect.BlurHint.AnimationHint                                      # 用于渲染动画

【2】、变色效果类 QGraphicsColorizeEffect

  使用 QGraphicsColorizeEffect 类可以 创建变色效果对象,使用模糊效果对象可以对图形项的显示设置变色效果。QGraphicsColorizeEffect 类的构造函数如下:

QGraphicsColorizeEffect(parent:QWidget=None)

  其中 parent 是指继承自 QObject 的实例。

  QGraphicsColorizeEffect 类的常用方法如下:

# 实例方法
color() -> QColor                                                               # 获取着色用的颜色
strength() -> float                                                             # 获取颜色强度

# 槽函数
setColor(color:Union[QColor, Qt.GlobalColor, str]) -> None                      # 设置着色用的颜色
setStrength(strength:float) -> None                                             # 设置颜色强度

  QGraphicsColorizeEffect 类的常用信号如下:

colorChanged(color:QColor)                                                      # 颜色发生改变时发送信号
strengthChanged(strength:float)                                                 # 强度发生改变时发送信号  

  变色效果是用另外一种颜色给图项着色,QGraphicsColorizeEffect 的变色效果是通过设置新颜色和着色强度来实现的,默认的着色是 浅蓝色QColor(0, 0, 192))。

【3】、阴影效果类 QGraphicsDropShadowEffect

  使用 QGraphicsDropShadowEffect 类可以 创建阴影效果对象,使用阴影效果对象可以对图形项的显示设置阴影效果。QGraphicsDropShadowEffect 类的构造函数如下:

QGraphicsDropShadowEffect(parent:QWidget=None)

  其中 parent 是指继承自 QObject 的实例。

  QGraphicsDropShadowEffect 类的常用方法如下:

# 实例方法
blurRadius() -> float                                                           # 获取模糊半径

color() -> QColor                                                               # 获取阴影颜色

offset() -> QPointF                                                             # 获取阴影的偏移量
xOffset() -> float                                                              # 获取阴影的X轴偏移量
yOffset() -> float                                                              # 获取阴影的Y轴偏移量

# 槽方法
setBlurRadius(blurRadius:float) -> None                                         # 设置模糊半径

setColor(color:Union[QColor, Qt.GlobalColor, str])                              # 设置阴影颜色

setOffset(ofset:Union[QPointF, QPoint]) -> None                                 # 设置阴影的偏移量
setXOffset(dx:float) -> None                                                    # 设置阴影的X轴偏移量
setYOffset(dy:float) -> None                                                    # 设置阴影的Y轴偏移量

  QGraphicsDropShadowEffect 类的常用信号如下:

blurRadiusChanged(blurRadius:float)                                             # 模糊半径改变时发送信号
colorChanged(color:QColor)                                                      # 阴影颜色改变时发送信号
offsetChanged(offset:QPointF)                                                   # 阴影偏移量改变时发送信号

  阴影效果能给图项增加立体效果,QGraphicsDropShadowEffect 的阴影效果需要设置背景色、模糊半径和阴影的偏移量。默认的阴影颜色是 灰色QColor(63, 63, 63, 180)),默认的模糊半径是 1,偏移量是 8 个像素,方向是 右下

【4】、透明效果类 QGraphicsOpacityEffect

  使用 QGraphicsOpacityEffect 类可以创建 透明效果对象,使用透明效果对象可以对图形项的显示设置透明效果。QGraphicsOpacityEffect 类的构造函数如下:

QGraphicsOpacityEffect(parent:QWidget=None)

  其中 parent 是指继承自 QObject 的实例。

  QGraphicsOpacityEffect 类的常用方法如下:

# 实例方法
opacity() -> float                                                              # 获取不透明度

opacityMask() -> QBrush                                                         # 获取遮掩画刷

# 槽方法
setOpacity(opacity:float) -> None                                               # 设置不透明度

# 设置遮掩画刷
setOpacityMask(mask:Union[QBrush, Qt.BrushStyle, Qt.GlobalColor, QColor, QGradient, QImage, QPixmap]) -> None

  QGraphicsOpacityEffect 类的常用信号如下:

opacityChanged(opacity:float)                                                   # 不透明度发生改变时发送信号
opacityMaskChanged(mask:QBrush)                                                 # 遮掩画刷发送改变是发送信号

  透明效果可以使人看到图项背后的图形,QGraphicsOpacityEffect 的透明效果需要设置不透明度。使用 setOpacity(opacity:float) 用于 设置不透明度,opacity 的值在 0.0 ~ 1.0 之间,0.0 表示 完全透明1.0 表示 完全不透明,默认值为 0.7。我们还可以使用 setOpacityMask() 用于设置部分透明。

  修改 ui.py 文件的内容。

from PySide6.QtWidgets import QWidget
from PySide6.QtWidgets import QToolBar
from PySide6.QtWidgets import QDoubleSpinBox, QLabel
from PySide6.QtWidgets import QVBoxLayout
from PySide6.QtWidgets import QGraphicsView, QGraphicsScene
from PySide6.QtGui import Qt, QIcon

class MyUi:
    def setupUi(self, window:QWidget):
        window.resize(1000, 800)                                                # 1.设置窗口对象大小

        layout = QVBoxLayout(window)                                            # 2.创建一个垂直布局

        self.toolBar = QToolBar()                                               # 3.创建一个工具栏,并添加到布局中
        layout.addWidget(self.toolBar)

        self.toolBar.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextUnderIcon) # 4.设置工具栏的显示样式
        self.toolBar.setOrientation(Qt.Orientation.Horizontal)                      # 5.设置工具栏的方向

        self.open_action = self.toolBar.addAction(QIcon("assets/images/1.ico"), "打开") # 6.创建动作,并添加到工具栏中

        self.toolBar.addSeparator()                                             # 7.添加分隔符

        # 8.添加其它组件到工具栏中

        # 添加模糊效果
        blur_effect_label = QLabel("模糊半径:")
        self.toolBar.addWidget(blur_effect_label)

        self.blur_effect_doubleSpinBox = QDoubleSpinBox()
        self.toolBar.addWidget(self.blur_effect_doubleSpinBox)

        self.toolBar.addSeparator()

        # 添加变色效果
        self.colorize_effect_action = self.toolBar.addAction(QIcon("assets/images/1.ico"), "着色颜色")

        colorize_effect_label = QLabel("颜色强度:")
        self.toolBar.addWidget(colorize_effect_label)

        self.colorize_strength_effect_doubleSpinBox = QDoubleSpinBox(minimum=0, maximum=1, singleStep=0.1)
        self.toolBar.addWidget(self.colorize_strength_effect_doubleSpinBox)

        self.toolBar.addSeparator()

        # 添加阴影效果
        self.dropShadow_effect_color_action = self.toolBar.addAction(QIcon("assets/images/1.ico"), "阴影颜色")

        dropShadow_effect_radius_label = QLabel("阴影半径:")
        self.toolBar.addWidget(dropShadow_effect_radius_label)

        self.dropShadow_effect_radius_doubleSpinBox = QDoubleSpinBox()
        self.toolBar.addWidget(self.dropShadow_effect_radius_doubleSpinBox)

        dropShadow_effect_x_offset_label = QLabel("阴影X轴偏移量:")
        self.toolBar.addWidget(dropShadow_effect_x_offset_label)

        self.dropShadow_effect_x_offset_doubleSpinBox = QDoubleSpinBox(minimum=-999, maximum=999)
        self.toolBar.addWidget(self.dropShadow_effect_x_offset_doubleSpinBox)

        dropShadow_effect_y_offset_label = QLabel("阴影Y轴偏移量:")
        self.toolBar.addWidget(dropShadow_effect_y_offset_label)

        self.dropShadow_effect_y_offset_doubleSpinBox = QDoubleSpinBox(minimum=-999, maximum=999)
        self.toolBar.addWidget(self.dropShadow_effect_y_offset_doubleSpinBox)

        self.toolBar.addSeparator()

        # 添加透明度效果
        opacity_effect_label = QLabel("不透明度:")
        self.toolBar.addWidget(opacity_effect_label)

        self.opacity_effect_doubleSpinBox = QDoubleSpinBox(minimum=0, maximum=1, singleStep=0.1)
        self.toolBar.addWidget(self.opacity_effect_doubleSpinBox)

        self.view = QGraphicsView()                                             # 9.创建一个视图,并添加到布局中
        layout.addWidget(self.view)

        self.scene = QGraphicsScene()                                           # 10.创建一个场景
        self.view.setScene(self.scene)                                          # 11.将场景添加到视图中

  修改 widget.py 文件的内容。

import sys

from PySide6.QtWidgets import QApplication, QWidget
from PySide6.QtWidgets import QFileDialog, QColorDialog
from PySide6.QtWidgets import QGraphicsPixmapItem
from PySide6.QtWidgets import QGraphicsBlurEffect, QGraphicsColorizeEffect
from PySide6.QtWidgets import QGraphicsDropShadowEffect, QGraphicsOpacityEffect
from PySide6.QtGui import QPixmap

from ui import MyUi

class MyWidget(QWidget):
    def __init__(self):
        super().__init__()                                                      # 1.调用父类Qwidget类的__init__()方法

        self.pixmap_item = None
        self.effect = {"blur": False, "colorize": False, "dropShadow": False, "opacity": False}

        self.__ui = MyUi()
        self.__ui.setupUi(self)                                                 # 2.初始化页面

        self.clear_blur_effect_doubleSpinBox_value()
        self.clear_colorize_effect_doubleSpinBox_value()
        self.clear_dropShadow_effect_doubleSpinBox_value()
        self.clear_opacity_effect_doubleSpinBox_value()

        self.__ui.blur_effect_doubleSpinBox.setEnabled(False)
        self.__ui.opacity_effect_doubleSpinBox.setEnabled(False)

        self.__ui.open_action.triggered.connect(self.open_image_file)           # 3.动作被按下时触发信号

        self.__ui.blur_effect_doubleSpinBox.valueChanged.connect(self.set_blur_effect_radius)

        self.__ui.colorize_effect_action.triggered.connect(self.set_colorize_effect)
        self.__ui.colorize_strength_effect_doubleSpinBox.valueChanged.connect(self.set_colorize_effect_strength)

        self.__ui.dropShadow_effect_color_action.triggered.connect(self.set_dropShadow_effect_color)
        self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.valueChanged.connect(self.set_dropShadow_effect_blur_radius)
        self.__ui.dropShadow_effect_x_offset_doubleSpinBox.valueChanged.connect(self.set_dropShadow_effect_x_offset)
        self.__ui.dropShadow_effect_y_offset_doubleSpinBox.valueChanged.connect(self.set_dropShadow_effect_y_offset)

        self.__ui.opacity_effect_doubleSpinBox.valueChanged.connect(self.set_opacity_effect)

    def clear_blur_effect_doubleSpinBox_value(self):
        self.__ui.blur_effect_doubleSpinBox.setValue(1)

    def clear_colorize_effect_doubleSpinBox_value(self):
        self.__ui.colorize_strength_effect_doubleSpinBox.setValue(1)
        self.__ui.colorize_strength_effect_doubleSpinBox.setEnabled(False)

    def clear_dropShadow_effect_doubleSpinBox_value(self):
        self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.setValue(1)
        self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.setEnabled(False)

        self.__ui.dropShadow_effect_x_offset_doubleSpinBox.setValue(8)
        self.__ui.dropShadow_effect_x_offset_doubleSpinBox.setEnabled(False)

        self.__ui.dropShadow_effect_y_offset_doubleSpinBox.setValue(8)
        self.__ui.dropShadow_effect_y_offset_doubleSpinBox.setEnabled(False)

    def clear_opacity_effect_doubleSpinBox_value(self):
        self.__ui.opacity_effect_doubleSpinBox.setValue(1)

    def open_image_file(self):
        # 1.选择文件
        file_name, filter = QFileDialog.getOpenFileName(self, "选择文件", "assets/images", "*.png *.jpg *.jpeg *.gif")
        if file_name:                                                           # 2.判断文件名是否为空
            # 3.清除其它效果数据
            self.effect = {"blur": False, "colorize": False, "dropShadow": False, "opacity": False}

            self.clear_blur_effect_doubleSpinBox_value()
            self.clear_colorize_effect_doubleSpinBox_value()
            self.clear_dropShadow_effect_doubleSpinBox_value()
            self.clear_opacity_effect_doubleSpinBox_value()

            self.__ui.blur_effect_doubleSpinBox.setEnabled(True)
            self.__ui.opacity_effect_doubleSpinBox.setEnabled(True)

            if self.pixmap_item:                                                # 4.判断是否已经存在图片项
                self.__ui.scene.removeItem(self.pixmap_item)                    # 5.如果存在,则移除图片项

            pixmap = QPixmap(file_name)                                         # 6.创建一个QPixmap类的实例
            self.pixmap_item = QGraphicsPixmapItem(pixmap)                      # 7.创建一个QGraphicsPixmapItem类的实例
            self.__ui.scene.addItem(self.pixmap_item)                           # 8.将图片项添加到场景中

    def set_blur_effect_radius(self):
        if self.pixmap_item:                                                    # 1.判断是否已经存在图片项
            # 2.清除其它效果数据
            self.effect = {"blur": True, "colorize": False, "dropShadow": False, "opacity": False}

            self.clear_colorize_effect_doubleSpinBox_value()
            self.clear_dropShadow_effect_doubleSpinBox_value()
            self.clear_opacity_effect_doubleSpinBox_value()

            blur_effect_radius = self.__ui.blur_effect_doubleSpinBox.value()    # 3.获取模糊半径

            self.blur_effect = QGraphicsBlurEffect()                            # 4.模糊效果类
            self.blur_effect.setBlurHints(QGraphicsBlurEffect.BlurHint.QualityHint) # 5.设置模糊提示

            self.blur_effect.setBlurRadius(blur_effect_radius)                  # 6.设置模糊半径
            self.pixmap_item.setGraphicsEffect(self.blur_effect)                # 7.将模糊效果添加到图片项中

    def set_colorize_effect(self):
        if self.pixmap_item:
            color = QColorDialog.getColor()                                     # 1.选择颜色
            if color.isValid():                                                 # 2.判断颜色是否有效
                # 3.清除其它效果数据
                self.effect = {"blur": False, "colorize": True, "dropShadow": False, "opacity": False}

                self.clear_blur_effect_doubleSpinBox_value()
                self.clear_colorize_effect_doubleSpinBox_value()
                self.clear_dropShadow_effect_doubleSpinBox_value()
                self.clear_opacity_effect_doubleSpinBox_value()

                self.__ui.colorize_strength_effect_doubleSpinBox.setEnabled(True)

                strength = self.__ui.colorize_strength_effect_doubleSpinBox.value() # 4.获取颜色强度

                self.colorize_effect = QGraphicsColorizeEffect()                # 5.颜色化效果类
                self.colorize_effect.setColor(color)                            # 6.设置颜色
                self.colorize_effect.setStrength(strength)                      # 7.设置颜色强度
                self.pixmap_item.setGraphicsEffect(self.colorize_effect)        # 8.将颜色化效果添加到图片项中

    def set_colorize_effect_strength(self):
        if self.pixmap_item and self.effect["colorize"]:
            strength = self.__ui.colorize_strength_effect_doubleSpinBox.value() # 1.获取颜色强度
            self.colorize_effect.setStrength(strength)                          # 2.设置颜色强度
            self.pixmap_item.setGraphicsEffect(self.colorize_effect)            # 3.将颜色化效果添加到图片项中

    def set_dropShadow_effect_color(self):
        if self.pixmap_item:
            color = QColorDialog.getColor()                                     # 1.选择颜色
            if color.isValid():                                                 # 2.判断颜色是否有效
                # 3.清除其它效果数据
                self.effect = {"blur": False, "colorize": False, "dropShadow": True, "opacity": False}

                self.clear_blur_effect_doubleSpinBox_value()
                self.clear_colorize_effect_doubleSpinBox_value()
                self.clear_dropShadow_effect_doubleSpinBox_value()
                self.clear_opacity_effect_doubleSpinBox_value()

                self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.setEnabled(True)
                self.__ui.dropShadow_effect_x_offset_doubleSpinBox.setEnabled(True)
                self.__ui.dropShadow_effect_y_offset_doubleSpinBox.setEnabled(True)

                blur_radius = self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.value() # 4.获取阴影的模糊半径
                x_offset = self.__ui.dropShadow_effect_x_offset_doubleSpinBox.value()   # 5.获取阴影的X轴偏移量
                y_offset = self.__ui.dropShadow_effect_y_offset_doubleSpinBox.value()   # 6.获取阴影的Y轴偏移量

                self.dropShadow_effect = QGraphicsDropShadowEffect()            # 7.阴影效果类
                self.dropShadow_effect.setColor(color)                          # 8.设置颜色
                self.dropShadow_effect.setBlurRadius(blur_radius)               # 9.设置模糊半径
                self.dropShadow_effect.setOffset(x_offset, y_offset)            # 10.设置阴影的X轴偏移量,Y轴偏移量
                self.pixmap_item.setGraphicsEffect(self.dropShadow_effect)      # 11.将阴影效果添加到图片项中

    def set_dropShadow_effect_blur_radius(self):
        if self.pixmap_item and self.effect["dropShadow"]:
            blur_radius = self.__ui.dropShadow_effect_blur_radius_doubleSpinBox.value() # 1.获取阴影半径
            self.dropShadow_effect.setBlurRadius(blur_radius)                   # 2.设置阴影的模糊半径
            self.pixmap_item.setGraphicsEffect(self.dropShadow_effect)          # 3.将阴影效果添加到图片项中

    def set_dropShadow_effect_x_offset(self):
        if self.pixmap_item and self.effect["dropShadow"]:
            x_offset = self.__ui.dropShadow_effect_x_offset_doubleSpinBox.value()   # 1.获取阴影X轴偏移量
            self.dropShadow_effect.setXOffset(x_offset)                         # 2.设置X轴阴影的偏移量

    def set_dropShadow_effect_y_offset(self):
        if self.pixmap_item and self.effect["dropShadow"]:
            y_offset = self.__ui.dropShadow_effect_y_offset_doubleSpinBox.value()   # 1.获取阴影Y轴偏移量
            self.dropShadow_effect.setYOffset(y_offset)                         # 2.设置Y轴阴影的偏移量

    def set_opacity_effect(self):
        if self.pixmap_item:
            # 1.清除其它效果数据
            self.effect = {"blur": False, "colorize": False, "dropShadow": False, "opacity": True}

            self.clear_blur_effect_doubleSpinBox_value()
            self.clear_colorize_effect_doubleSpinBox_value()
            self.clear_dropShadow_effect_doubleSpinBox_value()

            opacity = self.__ui.opacity_effect_doubleSpinBox.value()            # 2.获取透明度
            opacity_effect = QGraphicsOpacityEffect()                           # 3.创建透明度效果
            opacity_effect.setOpacity(opacity)                                  # 4.设置透明度
            self.pixmap_item.setGraphicsEffect(opacity_effect)                  # 5.设置图片项的透明度效果

if __name__ == "__main__":
    app = QApplication(sys.argv)                                                # 1.创建一个QApplication类的实例
    window = MyWidget()                                                         # 2.创建一个窗口
    window.show()                                                               # 3.展示窗口
    sys.exit(app.exec())                                                        # 4.进入程序的主循环并通过exit()函数确保主循环安全结束
posted @ 2025-01-23 20:56  星光映梦  阅读(244)  评论(0)    收藏  举报