使用鼠标拖动时旋转的 QGraphicsRectItem 随机移动
Rotated QGraphicsRectItem moves randomly when dragged using mouse
我有一个可移动的 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())
(我在主 window 底部添加了一个滑块以方便旋转项目)
为什么会这样?
问题是由多方面原因造成的:
- 在给定坐标处设置 QRect,同时将项目保持在相同的默认位置 (
0, 0
);
- 根据鼠标移动事件更改矩形;
- 之后改变变换原点;
- 基于整型的点(屏幕上)和浮动点(场景上)的鼠标坐标映射;
- 变换(旋转);
- 在不考虑上述情况的情况下实现项目移动(而 QGraphicsItem 已经为它提供了
ItemIsMovable
标志);
请注意,虽然旋转看起来是一个简单的操作,但它是通过结合使用两种变换来实现的:剪切和缩放;这意味着转换应用非常复杂的计算,这取决于浮点精度。
这在处理整数到浮点数的转换时成为一个问题:相同的鼠标(基于整数)坐标可以根据变换映射到非常不同的点,并且应用的变换“越高”,差异就越大。结果,映射的鼠标位置可能非常不同,矩形被转换为“错误”点,并且变换原点将矩形“移开”的比例增加。
解决方案是完全改变矩形的定位方式,实际上简化了引用:矩形总是居中在项目位置,这样我们就可以保持变换默认原点(项目坐标中的 0, 0
)。
此方法唯一的不便之处是项目的 pos()
将不再位于其左上角,但这不是真正的问题:旋转项目时,其左上角不会无论如何都要在那个位置。
如果您需要知道项目的实际位置,您可以根据项目场景位置平移矩形。
如果您想根据矩形的左上角定位矩形,则必须从场景映射位置并计算参考点的 delta(actual左上角)。
我冒昧地接受了你之前的问题,它实现了大小调整,并对其进行了改进以更好地展示解决方案的工作原理。
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)
我有一个可移动的 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())
(我在主 window 底部添加了一个滑块以方便旋转项目)
为什么会这样?
问题是由多方面原因造成的:
- 在给定坐标处设置 QRect,同时将项目保持在相同的默认位置 (
0, 0
); - 根据鼠标移动事件更改矩形;
- 之后改变变换原点;
- 基于整型的点(屏幕上)和浮动点(场景上)的鼠标坐标映射;
- 变换(旋转);
- 在不考虑上述情况的情况下实现项目移动(而 QGraphicsItem 已经为它提供了
ItemIsMovable
标志);
请注意,虽然旋转看起来是一个简单的操作,但它是通过结合使用两种变换来实现的:剪切和缩放;这意味着转换应用非常复杂的计算,这取决于浮点精度。
这在处理整数到浮点数的转换时成为一个问题:相同的鼠标(基于整数)坐标可以根据变换映射到非常不同的点,并且应用的变换“越高”,差异就越大。结果,映射的鼠标位置可能非常不同,矩形被转换为“错误”点,并且变换原点将矩形“移开”的比例增加。
解决方案是完全改变矩形的定位方式,实际上简化了引用:矩形总是居中在项目位置,这样我们就可以保持变换默认原点(项目坐标中的 0, 0
)。
此方法唯一的不便之处是项目的 pos()
将不再位于其左上角,但这不是真正的问题:旋转项目时,其左上角不会无论如何都要在那个位置。
如果您需要知道项目的实际位置,您可以根据项目场景位置平移矩形。
如果您想根据矩形的左上角定位矩形,则必须从场景映射位置并计算参考点的 delta(actual左上角)。
我冒昧地接受了你之前的问题,它实现了大小调整,并对其进行了改进以更好地展示解决方案的工作原理。
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)