0

我有一个可移动的 QGraphicsRectItem 旋转到 90 度并设置为场景。当我拖动该项目时,它会随机移动并最终消失。

但是,当我将旋转设置为 0 时,该项目完美地移动。

这是我最小的可重现示例。

class main_window(QWidget):
    def __init__(self):
        super().__init__()
        self.rect = Rectangle(100, 100, 100, 100)
        self.rect.setRotation(90)

        self.view = QGraphicsView(self)
        self.scene = QGraphicsScene(self.view)
        self.scene.addItem(self.rect)

        self.view.setSceneRect(0, 0, 500,500)
        self.view.setScene(self.scene)

        self.slider = QSlider(QtCore.Qt.Horizontal)
        self.slider.setMinimum(0)
        self.slider.setMaximum(90)

        vbox = QVBoxLayout(self)
        vbox.addWidget(self.view)
        vbox.addWidget(self.slider)

        self.setLayout(vbox)

        self.slider.valueChanged.connect(self.rotate)

    def rotate(self, value):
        self.angle = int(value)
        self.rect.setRotation(self.angle)

class Rectangle(QGraphicsRectItem):
    def __init__(self, *args):
        super().__init__(*args)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True)
        self.setPen(QPen(QBrush(QtGui.QColor('red')), 5))
        self.selected_edge = None
        self.first_pos = None
        self.click_rect = None

    def mousePressEvent(self, event):
        self.first_pos = event.pos()
 
        self.rect_shape = self.rect()
        self.click_rect = self.rect_shape
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        # Calculate how much the mouse has moved since the click.
        self.pos = event.pos()
        x_diff = self.pos.x() - self.first_pos.x()
        y_diff = self.pos.y() - self.first_pos.y()

        # Start with the rectangle as it was when clicked.
        self.rect_shape = QtCore.QRectF(self.click_rect)

        self.rect_shape.translate(x_diff, y_diff)

        self.setRect(self.rect_shape)
        self.setTransformOriginPoint(self.rect_shape.center())

(我在主窗口底部添加了一个滑块以方便地旋转项目)

为什么会这样?

4

1 回答 1

1

问题是多方面造成的:

  • 在给定坐标处设置 QRect,同时将项目保持在相同的默认位置(0, 0);
  • 由于鼠标移动事件而改变矩形;
  • 之后改变变换原点;
  • 基于整数的点(在屏幕上)和浮动(在场景上)之间的鼠标坐标映射;
  • 变换(旋转);
  • 在不考虑上述情况的情况下实现项目移动(而 QGraphicsItem 已经为它提供了ItemIsMovable标志);

请注意,虽然旋转似乎是一个简单的操作,但它是通过结合使用两种变换来实现的:剪切和缩放;这意味着转换应用了非常复杂的计算,这些计算取决于浮点精度。
这在处理整数到浮点转换时会成为一个问题:相同的鼠标(基于整数)坐标可以映射到非常不同的点,具体取决于转换,并且应用的转换“越高”,差异就越大。结果,映射的鼠标位置可能非常不同,矩形被平移到一个“错误”点,并且变换原点将矩形“移开”一个增加的比率。

解决方案是彻底改变矩形的定位方式,实际上简化了参考:矩形始终项目位置为中心,这样我们就可以将变换原点保持在默认值(0, 0在项目坐标中)。

这种方法的唯一不便之处在于该项目pos()将不再位于其左上角,但这不是一个真正的问题:当项目旋转时,其左上角无论如何都不会在该位置。

如果您需要知道项目的实际位置,则可以根据项目场景位置平移矩形。
如果要根据矩形的左上角定位矩形,则必须从场景映射位置并计算参考点的增量实际的左上角)。

我冒昧地提出了您之前的问题,该问题实现了调整大小,并对其进行了改进以更好地展示解决方案的工作原理。

class Selection(QtWidgets.QGraphicsRectItem):
    Left, Top, Right, Bottom = 1, 2, 4, 8
    def __init__(self, *args):
        rect = QtCore.QRectF(*args)
        pos = rect.center()
        # move the center of the rectangle to 0, 0
        rect.translate(-rect.center())
        super().__init__(rect)
        self.setPos(pos)
        self.setPen(QtGui.QPen(QtCore.Qt.red, 5))
        self.setFlags(
            self.ItemIsMovable | 
            self.ItemIsSelectable | 
            self.ItemSendsGeometryChanges
        )

    def mapRect(self):
        return QtCore.QRectF(
            self.mapToScene(self.rect().topLeft()), 
            self.rect().size()
        )

    def setRectPosition(self, pos):
        localPos = self.mapFromScene(pos)
        delta = self.rect().topLeft() - localPos
        self.setPos(self.pos() + delta)

    def itemChange(self, change, value):
        if change in (self.ItemPositionHasChanged, self.ItemRotationHasChanged):
            print(self.mapRect())
        return super().itemChange(change, value)

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        pos = event.pos()
        rect = self.rect()
        margin = self.pen().width() / 2
        self.anchor = 0

        if pos.x() <= rect.x() + margin:
            self.anchor |= self.Left
        elif pos.x() >= rect.right() - margin:
            self.anchor |= self.Right

        if pos.y() <= rect.y() + margin:
            self.anchor |= self.Top
        elif pos.y() >= rect.bottom() - margin:
            self.anchor |= self.Bottom

        if self.anchor:
            self.clickAngle = QtCore.QLineF(QtCore.QPointF(), pos).angle()
        else:
            super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self.anchor:
            super().mouseMoveEvent(event)
            return
        rect = self.rect()
        pos = event.pos()
        if self.anchor == self.Left:
            rect.setLeft(pos.x())
        elif self.anchor == self.Right:
            rect.setRight(pos.x())
        elif self.anchor == self.Top:
            rect.setTop(pos.y())
        elif self.anchor == self.Bottom:
            rect.setBottom(pos.y())
        else:
            # clicked on a corner, let's rotate
            angle = QtCore.QLineF(QtCore.QPointF(), pos).angle()
            rotation = max(0, min(90, self.rotation() + self.clickAngle - angle))
            self.setRotation(rotation)
            return
        pos = self.mapToScene(rect.center())
        self.setPos(pos)
        rect.moveCenter(QtCore.QPointF())
        self.setRect(rect)
于 2021-09-27T19:14:58.597 回答