Повернутый QGraphicsRectItem перемещается случайным образом при перетаскивании с помощью мыши
У меня есть подвижный 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.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())
(Я включил ползунок в нижней части главного окна, чтобы удобно вращать элемент)
Почему это происходит?
1 ответ
Проблема вызвана различными аспектами:
- установка QRect в заданных координатах, сохраняя элемент в той же позиции по умолчанию ();
- изменение прямоугольника вследствие события перемещения мыши;
- после этого изменить точку начала трансформации;
- отображение координат мыши между целочисленной точкой (на экране) и плавающей (на сцене);
- преобразование (вращение);
- реализация перемещения элемента без учета вышеизложенного (в то время как QGraphicsItem уже предоставляет ему
флаг);
Обратите внимание: хотя вращение может показаться простой операцией, оно достигается с помощью комбинации двух преобразований: сдвига и масштабирования; это означает, что преобразование применяет очень сложные вычисления, которые зависят от точности с плавающей запятой.
Это становится проблемой при преобразовании целых чисел в числа с плавающей запятой: одна и та же координата мыши (основанная на целых числах) может отображаться в очень разных точках в зависимости от преобразования, и чем «выше» применяется преобразование, тем больше может быть разница. В результате отображаемое положение мыши может сильно отличаться, прямоугольник переводится в «неправильную» точку, а исходная точка преобразования перемещает прямоугольник «в сторону» в увеличивающемся соотношении.
Решение состоит в том, чтобы полностью изменить способ позиционирования прямоугольника и фактически упростить ссылку: прямоугольник всегда центрируется в позиции элемента, так что мы можем сохранить исходную точку преобразования по умолчанию (
Единственное неудобство такого подхода заключается в том, что предмет
Если вам нужно знать фактическое положение элемента, вы можете затем перевести прямоугольник в зависимости от положения элемента в сцене.
Если вы хотите расположить прямоугольник на основе его верхнего левого угла, вам необходимо сопоставить положение со сцены и вычислить дельту опорной точки ( фактический верхний левый угол).
Я взял на себя смелость ответить на ваш предыдущий вопрос, касающийся изменения размера, и улучшить его, чтобы лучше показать, как работает решение.
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)