QGraphicsPathItem 的形状方法中的问题

Problem in shape method of QGraphicsPathItem

在下图中,我在场景中将 QGraphicsPathItem 作为红色部分并将其形状覆盖为蓝色部分。我想要当红色 space 被拖动并移动时,项目会线性延长或缩短,而当蓝色 space 被拖动时,整个项目必须被移动。 这是我尝试过的...

import sys

from PyQt5.QtCore import QRectF, Qt, QPointF
from PyQt5.QtGui import QPainterPath, QPen, QPainterPathStroker, QPainter
from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsScene, QGraphicsView, QGraphicsPathItem, QGraphicsItem

class Item(QGraphicsPathItem):
    circle = QPainterPath()
    circle.addEllipse(QRectF(-5, -5, 10, 10))

    def __init__(self):
        super(Item, self).__init__()
        self.setPath(Item.circle)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)

    def paint(self, painter, option, widget):
        color = Qt.red if self.isSelected() else Qt.black
        painter.setPen(QPen(color, 2, Qt.SolidLine))
        painter.drawPath(self.path())

        # To paint path of shape
        painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine))
        painter.drawPath(self.shape())

    def shape(self):
        startPoint = self.mapFromScene(self.pos())
        endPoint = self.mapFromScene(QPointF(10, 10))
        path = QPainterPath(startPoint)
        path.lineTo(endPoint)
        stroke = QPainterPathStroker()
        stroke.setWidth(10)
        return stroke.createStroke(path)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = QMainWindow()
    window.show()
    scene = QGraphicsScene()
    scene.setSceneRect(0, 0, 200, 200)
    view = QGraphicsView()
    view.setScene(scene)
    window.setCentralWidget(view)
    scene.addItem(Item())
    sys.exit(app.exec_())

我得到的输出是受干扰的路径

在同一项目中处理调整大小和拉伸的任务很复杂,因此为避免这种情况,我使用了 2 个项目:手柄和管道。因此每个人都管理自己的任务并更新其他元素的位置:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets


class HandleItem(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super().__init__(parent)
        path = QtGui.QPainterPath()
        path.addEllipse(QtCore.QRectF(-5, -5, 10, 10))
        self.setPath(path)

        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)

        self._pipe_item = None

    @property
    def pipe_item(self):
        return self._pipe_item

    @pipe_item.setter
    def pipe_item(self, item):
        self._pipe_item = item

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
            ip = self.pipe_item.mapFromScene(value)
            self.pipe_item.end_pos = ip
        elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
            color = QtCore.Qt.red if value else QtCore.Qt.black
            self.setPen(QtGui.QPen(color, 2, QtCore.Qt.SolidLine))
        return super().itemChange(change, value)


class PipeItem(QtWidgets.QGraphicsPathItem):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)

        self._end_pos = QtCore.QPointF()

        self._handle = HandleItem()
        self.handle.pipe_item = self

        self.end_pos = QtCore.QPointF(10, 10)
        self.handle.setPos(self.end_pos)

        self.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine))

    @property
    def handle(self):
        return self._handle

    @property
    def end_pos(self):
        return self._end_pos

    @end_pos.setter
    def end_pos(self, p):
        path = QtGui.QPainterPath()
        path.lineTo(p)
        stroke = QtGui.QPainterPathStroker()
        stroke.setWidth(10)
        self.setPath(stroke.createStroke(path))
        self._end_pos = p

    def paint(self, painter, option, widget):
        option.state &= ~QtWidgets.QStyle.State_Selected
        super().paint(painter, option, widget)

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemSceneHasChanged:
            if self.scene():
                self.scene().addItem(self.handle)
        elif change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
            p = self.mapToScene(self.end_pos)
            self.handle.setPos(p)
        return super().itemChange(change, value)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    scene = QtWidgets.QGraphicsScene(sceneRect=QtCore.QRectF(0, 0, 200, 200))
    item = PipeItem()
    scene.addItem(item)
    view = QtWidgets.QGraphicsView(scene)
    window = QtWidgets.QMainWindow()
    window.setCentralWidget(view)
    window.resize(640, 480)
    window.show()
    sys.exit(app.exec_())

更新:

如果你想要你想要实现的逻辑,那就更复杂了。错误原因是paint()方法使用了boundingRect()来设置paint区域,但是你的情况没有考虑到它的变化,可能的解决方法如下:

class Item(QGraphicsPathItem):
    circle = QPainterPath()
    circle.addEllipse(QRectF(-5, -5, 10, 10))

    # ...

    def boundingRect(self):
        return self.shape().boundingRect()