QGraphicsPixmapItem позиционируется неправильно

Мне нужно переместить QGraphicsPixmapItemчерез кружок в верхнем левом углу изображения. То есть, когда я захватываю круг с помощью мыши, мне нужно, чтобы верхний левый угол изображения следовал за кругом. Я сделал подкласс QGraphicsEllipseItem и повторно реализовал itemChange, но когда я устанавливаю положение изображения на это значение, изображение позиционируется неправильно. Что мне следует изменить в моем коде?

    import sys
    from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsView
    from PyQt5 import QtGui, QtWidgets
    
    class MainWindow(QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
    
            self.scene = Scene()
            self.view = QGraphicsView(self)
            self.setGeometry(10, 30, 850, 600)
            self.view.setGeometry(20, 22, 800, 550)
            self.view.setScene(self.scene)
    
    class Scene(QtWidgets.QGraphicsScene):
        def __init__(self, parent=None):
            super(Scene, self).__init__(parent)
            # other stuff here
            self.set_image()
    
        def set_image(self):
            image = Image()
            self.addItem(image)
            image.set_pixmap()
    
    class Image(QtWidgets.QGraphicsPixmapItem):
        def __init__(self, parent=None):
            super(Image, self).__init__(parent)
    
            self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
    
        def set_pixmap(self):
            pixmap = QtGui.QPixmap("image.jpg")
            self.setPixmap(pixmap)
            self.pixmap_controller = PixmapController(self)
            self.pixmap_controller.set_pixmap_controller()
            self.pixmap_controller.setPos(self.boundingRect().topLeft())
            self.pixmap_controller.setFlag(QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges, True)
    
        def change_image_position(self, position):
            self.setPos(position)
    
    class PixmapController(QtWidgets.QGraphicsEllipseItem):
        def __init__(self, pixmap):
            super(PixmapController, self).__init__(parent=pixmap)
            self.pixmap = pixmap
    
            self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
            color = QtGui.QColor(0, 0, 0)
            brush = QtGui.QBrush(color)
            self.setBrush(brush)
    
        def set_pixmap_controller(self):
            self.setRect(-5, -5, 10, 10)
    
        def itemChange(self, change, value):
            if change == QtWidgets.QGraphicsItem.ItemPositionChange:
                self.pixmap.change_image_position(value)
            return super(PixmapController, self).itemChange(change, value)
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        window = MainWindow()
        window.show()
        sys.exit(app.exec_())

1 ответ

Решение

Когда у графического элемента есть родительский элемент, его система координат основана на этом родителе, а не на сцене.

Проблема в том, что при попытке переместить PixmapController, движение осуществляется в родительских координатах (элемент растрового изображения). Когда вы проверяете ItemPositionChange вы изменяете родительскую позицию, но позиция элемента все равно изменяется на основе родительской системы координат.

Хотя вы можете просто вернуть пустой QPoint (который не изменит положение элемента), это не будет хорошим выбором: как только вы отпустите кнопку мыши и снова начнете перемещать ее, растровое изображение сбросит свое положение.

Решение состоит не в установке флага подвижного элемента, а в фильтрации движений мыши, вычислении дельты на основе начальной позиции щелчка и использовании этой дельты для перемещения родительского элемента на основе его текущей позиции.

class PixmapController(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, pixmap):
        super(PixmapController, self).__init__(parent=pixmap)
        self.pixmap = pixmap

        # the item should *NOT* move
        # self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
        color = QtGui.QColor(0, 0, 0)
        brush = QtGui.QBrush(color)
        self.setBrush(brush)

    def set_pixmap_controller(self):
        self.setRect(-5, -5, 10, 10)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.startPos = event.pos()

    def mouseMoveEvent(self, event):
        if event.buttons() == QtCore.Qt.LeftButton:
            delta = event.pos() - self.startPos
            self.parentItem().setPos(self.parentItem().pos() + delta)

Если вы хотите использовать свой change_image_positionфункции, вам необходимо соответствующим образом изменить эти функции; код ниже делает то же самое, что и последняя строка в примере выше:

class Image(QtWidgets.QGraphicsPixmapItem):
    # ...
    def change_image_position(self, delta):
        self.setPos(self.pos() + delta)

class PixmapController(QtWidgets.QGraphicsEllipseItem):
    # ...
    def mouseMoveEvent(self, event):
        if event.buttons() == QtCore.Qt.LeftButton:
            delta = event.pos() - self.startPos
            self.pixmap.change_image_position(delta)

Совет: не добавляйте дочерний виджет к QMainWindow таким образом, поскольку он не будет правильно изменять размер при изменении размера окна. Использовать self.setCentralWidget(self.view)вместо; если вы хотите добавить поля, используйте контейнер QWidget, установите этот виджет как центральный виджет, добавьте простой QHBoxLayout (или QVBoxLayout), добавьте представление в этот макет, а затем установите поля с помощью layout.setContentsMargins(left, top, right, bottom)

Другие вопросы по тегам