#python #pyqt #pyqt5 #qgraphicsitem
#python #pyqt #pyqt5 #qgraphicsitem
Вопрос:
Я хочу использовать QPropertyAnimation в QGraphicsItem, надеясь, что элемент rect может перемещаться из точки (100, 30) в точку (100, 90). Но почему прямоугольник перемещается в правую часть окна? Координата x 100 должна приводить к перемещению прямоугольника в середине в соответствии с размером сцены.
Вот мой код:
import sys
from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF
from PyQt5.QtWidgets import QApplication, QGraphicsEllipseItem, QGraphicsScene, QGraphicsView,
QGraphicsObject
class CustomRect(QGraphicsObject):
def __init__(self):
super(CustomRect, self).__init__()
def boundingRect(self):
return QRectF(100, 30, 100, 30)
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QPropertyAnimation(self.rect, b'pos')
self.animation.setDuration(1000)
self.animation.setStartValue(QPointF(100, 30))
self.animation.setEndValue(QPointF(100, 90))
self.animation.setLoopCount(-1)
self.animation.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
Ответ №1:
Кажется, что они не знают разных систем координат платформы просмотра графики.
В этой системе существуют, по крайней мере, следующие системы координат:
-
Система координат окна (viewport()), где (0, 0) всегда будет в верхнем левом углу окна.
-
Система координат сцены, это относительно некоторой заранее установленной точки.
-
Система координат каждого элемента, эта система координат используется методом paint() для рисования и методами boundingRect() и shape() для получения краев элемента.
У вас также должна быть другая концепция, положение элемента относительно родительского элемента, если он у него есть, если у него его нет, это относительно сцены.
Аналогия
Для объяснения различных систем координат я использую аналогию с записью сцены с помощью камеры.
- QGraphicsView будет экраном камеры.
- QGraphicsScene — это сцена, которая записывается, поэтому точка (0, 0) — это некоторая точка, которая удобна.
- QGraphicsItem — это элементы сцены, их положение может быть относительно других элементов или сцены, например, мы можем рассмотреть положение обуви актера относительно актера, или элемент может быть тем же актером.
Основываясь на вышесказанном, я объясню, что происходит, и мы дадим несколько решений.
Элемент rect не имеет родительского элемента, и по умолчанию posicon элемента равен (0, 0), поэтому в этот момент система координат элемента и сцены совпадают, поэтому boundingRect визуально определит позицию, и поскольку вы разместили QRectF (100, 30, 100, 30), это будет отображаться в той позиции, которая по совпадению будет одинаковой в сцене. Но когда вы применяете анимацию, первое, что будет сделано, — это установить положение элемента на (100, 30), так что, поскольку системы координат сцены и элемента не совпадают, одна смещается относительно другой, поэтому boundingRect больше не соответствует QRectF (100, 30, 100, 30) сцены, но будет перемещаться в том же коэффициенте (только потому, что есть смещение, нет масштабирования или поворота), и прямоугольник будет смещен. QRectF(200, 60, 100, 30) и относительно эллипса, который всегда был в QRect(100, 180, 100, 50), так что прямоугольник находится справа с 200> 100, а с 60<180 — вверх.
Итак, если вы хотите, чтобы прямоугольник находился поверх эллипса, есть как минимум 2 решения:
- Измените boundingRect так, чтобы он находился в позиции 0,0, чтобы при смещении, вызванном анимацией, они совпадали:
import sys
from PyQt5 import QtCore, QtWidgets
class CustomRect(QtWidgets.QGraphicsObject):
def boundingRect(self):
return QtCore.QRectF(0, 0, 100, 30) # <---
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QtWidgets.QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QtWidgets.QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QtWidgets.QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QtCore.QPropertyAnimation(
self.rect,
b"pos",
duration=1000,
startValue=QtCore.QPointF(100, 30),
endValue=QtCore.QPointF(100, 90),
loopCount=-1,
)
self.animation.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
- Измените анимацию так, чтобы она не генерировала смещение:
import sys
from PyQt5 import QtCore, QtWidgets
class CustomRect(QtWidgets.QGraphicsObject):
def boundingRect(self):
return QtCore.QRectF(100, 30, 100, 30)
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QtWidgets.QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QtWidgets.QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QtWidgets.QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QtCore.QPropertyAnimation(
self.rect,
b"pos",
duration=1000,
startValue=QtCore.QPointF(0, 0), # <---
endValue=QtCore.QPointF(0, 60), # <---
loopCount=-1,
)
self.animation.start()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
Комментарии:
1. Спасибо @eyllanesc, ваш ответ хорош, как всегда. Итак, вы имеете в виду, что анимация основана на координатах элемента, но не сцены? Я действительно знаю, как решить проблему, но просто не могу понять координаты, которые использует анимация.
2. @just_be_happy Я этого не говорил, я сказал, что визуально наблюдаемое является результатом набора преобразований. ИМХО, я считаю, что для упрощения вещей boundingRect всегда должен начинаться с (0,0), поскольку это относится к системе координат того же элемента, поскольку это позволяет избежать сложных вычислений, и это очевидно в моем первом решении.
3. @just_be_happy Анимация не знает или не заинтересована в знании различных типов систем координат, она только изменяет свойство «pos». и в этом случае pos касается сцены, но ваш элемент нарисован на прямоугольнике, уже смещенном на себя
QRectF (100, 30, 100, 30)
, и4. @just_be_happy [продолжение.] вы также перемещаете его относительно сцены: QPointF (100, 30) на мгновение после анимации рисунок смещается (100, 30) от QRectF (100, 30) QPoint = (200, 60), так что он находится справа и над эллипсом (100, 180, 100, 50). Просто всегда реализуйте boundingRect с помощью QRectF (0, 0, w, h) и используйте setPos для его перемещения.
5. Я подумал, что если boundingRect равен (100, 30, 100, 30) и если я хочу, чтобы прямоугольник перемещался с правой стороны, мне нужно использовать QPointF (200, 30) и QPointF (200, 90). Не знал, что было смещение. Спасибо eyllanesc. Я отмечу ваш ответ как правильный. Но почему существует это смещение?
Ответ №2:
Попробуйте:
import sys
from PyQt5.QtCore import QPropertyAnimation, QPointF, QRectF
from PyQt5.QtWidgets import QApplication, QGraphicsEllipseItem, QGraphicsScene, QGraphicsView,
QGraphicsObject
class CustomRect(QGraphicsObject):
def __init__(self):
super(CustomRect, self).__init__()
def boundingRect(self):
# return QRectF(100, 30, 100, 30)
return QRectF(0, 0, 100, 30) #
def paint(self, painter, styles, widget=None):
painter.drawRect(self.boundingRect())
class Demo(QGraphicsView):
def __init__(self):
super(Demo, self).__init__()
self.resize(300, 300)
self.scene = QGraphicsScene()
self.scene.setSceneRect(0, 0, 300, 300)
self.rect = CustomRect()
self.ellipse = QGraphicsEllipseItem()
self.ellipse.setRect(100, 180, 100, 50)
self.scene.addItem(self.rect)
self.scene.addItem(self.ellipse)
self.setScene(self.scene)
self.animation = QPropertyAnimation(self.rect, b'pos')
self.animation.setDuration(3000)
self.animation.setStartValue(QPointF(100, 30))
self.animation.setEndValue(QPointF(100, 90))
self.animation.setLoopCount(-1)
self.animation.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())