【问题标题】:Rotated QGraphicsRectItem moves randomly when dragged using mouse使用鼠标拖动时,旋转的 QGraphicsRectItem 随机移动
【发布时间】:2021-09-27 15:52:44
【问题描述】:

我有一个可移动的 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())

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

为什么会这样?

【问题讨论】:

  • 你正在设置ItemIsMovable标志,在鼠标事件中实现移动是没有用的:尝试评论这两个函数,你会看到。
  • 嗨@musicamante,您的解决方案效果很好。但是,如果我评论 ItemIsMovable 标志并继续实现鼠标事件,问题仍然存在。我发现在仍然使用鼠标事件的同时评论 self.setTransformOriginPoint(self.rect_shape.center()) 也解决了这个问题。这意味着setTransformOriginPoint 使项目在拖动时随机移动。你知道为什么吗?
  • 事实上,您仍在使用定位在某个点的 QGraphicsRectItem,但有一个坐标不同的矩形(请记住,QGraphics 形状项目的形状位于 relative i> 到他们的位置)。这会产生一个非常复杂的转换问题:鼠标位置(基于 integer 值)由旋转(改为使用浮点)转换,然后由于浮点精度和整数转换,一定的旋转量显示了一些限制。我相信您需要更复杂的实现。
  • 有什么变通办法让它工作吗?
  • 没有“解决方法”,您需要以不同的方式实现该类:尝试“保持简单”并使用解决方法只会导致在您进一步实现的某个时刻出现另一个问题。我想你的图像选择/裁剪程序仍然需要这个,对吧?

标签: python pyqt pyside qgraphicsscene qgraphicsrectitem


【解决方案1】:

问题是多方面造成的:

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

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

解决办法是彻底改变矩形的定位方式,实际上简化了引用:矩形总是居中在item位置,这样我们就可以保持变换原点在默认(0, 0 在项目坐标中)。

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

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

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

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)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 2023-01-11
    • 1970-01-01
    • 1970-01-01
    • 2012-06-18
    • 2023-03-30
    • 1970-01-01
    相关资源
    最近更新 更多