PySide6 QGraphicsItem paint绘图演示与说明


前言

这是一篇关于QGraphicsItem的paint方法重写的详细记录,使用Python和PySide6

一、QGraphicsItem是什么?

它是PySide6中QGraphicsWidget体系的图元,通过场景(scene)管理并使用视图(view)显示它,它可以实现复杂的图形交互,甚至开发游戏

二、使用步骤

首先我们需要定义Item的重绘的区域,调用boundingRect可以实现定义

def boundingRect(self) -> QRectF:
	return QRectF(0, 0, self.width, self.height)

对于boundingRect,我们需要返回应该QRectF对象参数如下:
(left,top)item左上角坐标,正常设置为(0,0)
接下来传入,item的宽和高(width,height)


def paint(self, painter: PySide6.QtGui.QPainter, option: PySide6.QtWidgets.QStyleOptionGraphicsItem, widget: Optional[PySide6.QtWidgets.QWidget] = ...) -> None:

对于paint函数的调用我们需要传入三个参数:

  • painter :QPainter对象
  • option :QStyleOptionGraphicsItem对象,提供item的基本绘制信息和风格
  • widget :QWidget对象(该Item所绘制在的widget对象)

下面是实际上最终调用的代码:(widget=0是绘制在缓存区,似乎None也可以)

def paint(self, painter: QPainter, option, widget=0) -> None:

需要注意的是:在paint中绘制的图形坐标和大小尽量不超过boundingRect定义的大小,下面是示意图:(left,top)是item相对圆点(0,0),箭头指向的是正方向+
在这里插入图片描述
其次,paint重绘的是item的背景(底层)图形
此时我们开始进行绘制:

- 通过参数painter进行绘制

from PySide6.QtWidgets import QApplication, QGraphicsItem, QGraphicsScene, QGraphicsView
from PySide6.QtGui import QPainter, QPainterPath, QPen
from PySide6.QtCore import Qt, QRectF, QRect


class item(QGraphicsItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.width = 120
        self.height = 160
        self.setPos(190, 170)

    def boundingRect(self) -> QRectF:
        return QRectF(0, 0, self.width, self.height)

    def paint(self, painter: QPainter, option, widget=None) -> None:
        painter.setPen(QPen(Qt.black, 3))  # 设置画笔
        # 方法一
        painter.drawRect(0, 0, 120, 160)

        # 方法二
        rectPath = QPainterPath()
        rectPath.addRect(0, 0, 120, 160)
        # 剪切
        # clipPath = QPainterPath()
        # clipPath.addRect(-25,-25,50,50)
        # rectPath = rectPath.subtracted(clipPath)

        painter.drawPath(rectPath)

        # 需要注意的是,Qt 中的角度单位是以 1/16 度为单位的,因此在设置起始角度和弧度时需要将角度值乘以 16。
        # painter.drawArc(0, 0, 25, 25, 90 * 16, 90 * 16)


if __name__ == '__main__':
    app = QApplication([])
    scene = QGraphicsScene()
    scene.setSceneRect(0, 0, 500, 500)
    view = QGraphicsView(scene)
    scene.addItem(item())
    view.show()
    app.exec()

运行结果:

结果一
剪切结果:
剪切结果

注意:此时通过painter绘制的图形坐标圆点是相对于item的

- 通过设置子类item绘制

from PySide6.QtWidgets import QApplication, QGraphicsItem, QGraphicsRectItem, QGraphicsScene, QGraphicsView
from PySide6.QtGui import QPainter, QPainterPath, QPen
from PySide6.QtCore import Qt, QRectF, QRect


class item(QGraphicsItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.width = 120
        self.height = 160
        self.setPos(190, 170)

    def boundingRect(self) -> QRectF:
        return QRectF(0, 0, self.width, self.height)

    def paint(self, painter: QPainter, option, widget=None) -> None:
        rect = QGraphicsRectItem(0,0,120,160,self)
        rect.setPen(QPen(Qt.black, 3))

        rect_2 = QGraphicsRectItem(25,25,50,50)
        rect_2.setPen(QPen(Qt.black,2))
        rect_2.setParentItem(self)
        # rect_2.setPos(50,50)


if __name__ == '__main__':
    app = QApplication([])
    scene = QGraphicsScene()
    scene.setSceneRect(0, 0, 500, 500)
    view = QGraphicsView(scene)
    scene.addItem(item())
    view.show()
    app.exec()

运行结果图:(红点和坐标为后期P上的)
运行结果
#设置坐标后:
设置坐标后

对于此方法:QGraphics_**Item实例化传入的坐标参数,使用的坐标系是相对于父类item(self)的
但如果实例化传入的不是(0,0)是可能会影响的坐标系转换函数,可以理解为使用的是以新坐标(25,25)为圆点,计算设置产生坐标(50,50),但转换函数map_**使用的还是(0,0)所以需要在map_*函数中加上新圆点(25,25)值,才可以得到正确的坐标

print(rect_2.mapToItem(self,0,0)) --> (50,50)
print(rect_2.mapToItem(self,25,25)) --> (75,75)

这里建议是:item实例化参数坐标尽量传入(0,0)与父类item的圆点重合,
统一使用父类item的坐标系
通过设置坐标setPos(),实现上述效果(较统一的坐标系可以提高开发效率和质量)

以下是标准写法:

    def paint(self, painter: QPainter, option, widget=None) -> None:
        rect = QGraphicsRectItem(0,0,120,160,self)
        rect.setPen(QPen(Qt.black, 3))

        rect_2 = QGraphicsRectItem(0,0,50,50)
        rect_2.setPen(QPen(Qt.black,2))
        rect_2.setParentItem(self)
        rect_2.setPos(75,75)

小提示:

  • 这两种方法使用场景,大概总结一下:如果想要动态的更新图形建议是不要使用子类item,不然你还需要手动的更新(updata)它们,直接使用painter绘制只需调用一次
  • 对于子类item不需要再paint绘制,可以再父类item的其他地方实现,可以实现较高的自定义性(class重写)
  • 可能导致图形绘制不正常的原因有:超出boundingRect方法定义的范围,场景(scene)没有定义大小范围(setSceneRect())
  • 如果item的图形需要发生改变,或者改变不及时,可以调用update()更新它
  • 以下是需要调用和不需要的两种情况:
class item(QGraphicsItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.width = 120
        self.height = 160
        self.setPos(190, 170)
        self.isPress = False

    def mousePressEvent(self, event:QGraphicsSceneMouseEvent) -> None:
        super().mousePressEvent(event)
        if event.button() == Qt.MouseButton.LeftButton:#点击左键时
            self.isPress = True
            self.update()
        elif event.button() == Qt.MouseButton.RightButton:#点击右键时
            self.isPress = False
            self.update()
            
    def boundingRect(self) -> QRectF:
        return QRectF(0, 0, self.width, self.height)

    def paint(self, painter: QPainter, option, widget=None) -> None:
        painter.setPen(QPen(Qt.black,3))
        painter.drawRoundedRect(0,0,120,160,20,20)
        # QGraphicsRectItem(0,0,120,160,self)
        
        if self.isPress is False:
            painter.drawText(50,50,'请点击')
        else:
            painter.drawText(50,50,'鼠标已点击')

updata对比运行结果

上面代码你可以看到:当我们把self.update()去除时,点击它不会有效果。
但去除QGraphicsRectItem(0,0,120,160,self)的注释,它又可以运行了。通过print()调试,可以观察到没有子类_*item时paint只调用了一次,但有子类item时它不断执行的自我更新重绘,此时可能会导致性能问题
建议:实例化子类Item尽量不要再paint方法里,除非item本身需要不断动态更新

总结

方法paint重实现虽简单,但也充满了小细节,一不留神就掉进坑里。
多尝试,多踩坑是唯一的办法

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 PySide6 中的 QGraphicsView 类可以实现鼠标滚轮放大缩小和移动功能。具体实现步骤如下: 1. 继承 QGraphicsView 类创建自定义视图类,例如 MyGraphicsView。 ```python from PySide6.QtWidgets import QGraphicsView class MyGraphicsView(QGraphicsView): pass ``` 2. 在自定义视图类中重写 wheelEvent() 函数,实现鼠标滚轮缩放功能。可以通过调整视图的缩放比例和滚轮事件的 delta() 值来实现缩放效果。 ```python from PySide6.QtCore import Qt class MyGraphicsView(QGraphicsView): def wheelEvent(self, event): # 改变视图缩放比例 zoomInFactor = 1.25 zoomOutFactor = 1 / zoomInFactor if event.angleDelta().y() > 0: zoomFactor = zoomInFactor else: zoomFactor = zoomOutFactor self.scale(zoomFactor, zoomFactor) ``` 3. 在自定义视图类中添加 mousePressEvent() 和 mouseMoveEvent() 函数,实现鼠标移动功能。通过记录鼠标按下时的位置和移动后的位置,计算出偏移量并调整视图的位置实现移动效果。 ```python class MyGraphicsView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) # 设置拖拽模式为滚动条拖拽 def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._lastPos = event.pos() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: delta = event.pos() - self._lastPos self._lastPos = event.pos() self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x()) self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y()) ``` 最终实现的 MyGraphicsView 类可以同时实现鼠标滚轮放大缩小和移动功能。 完整代码如下: ```python from PySide6.QtWidgets import QGraphicsView from PySide6.QtCore import Qt class MyGraphicsView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) def wheelEvent(self, event): zoomInFactor = 1.25 zoomOutFactor = 1 / zoomInFactor if event.angleDelta().y() > 0: zoomFactor = zoomInFactor else: zoomFactor = zoomOutFactor self.scale(zoomFactor, zoomFactor) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._lastPos = event.pos() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: delta = event.pos() - self._lastPos self._lastPos = event.pos() self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x()) self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y()) ``` 可以在 PySide6 应用程序中使用 MyGraphicsView 类来创建带有鼠标滚轮放大缩小和移动功能的视图。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值