PyQt/PySide:在QGraphicsview中,如何在光标位于边缘时保持图像移动,而无需移动鼠标?

问题描述 投票:0回答:0

在我的程序中,我有一个 QDialog,它显示一张可以缩放和平移的图片,如果单击鼠标右键平移,则可以绘制一个矩形。当您绘制矩形并到达视图边缘时,图片应该移动,以便您可以继续绘制矩形。目前,只有当您不断轻微移动光标以触发 mouseMoveEvent 时,它才有效。

如果我尝试在鼠标不移动时以编程方式调用 mouseMoveEvent ,为了保持图片移动,它会移动图片,但由于某种原因视图不会更新,因此您只能在再次移动鼠标后看到图片已移动。所以基本上如果你将鼠标放在边缘一段时间而不移动它,然后再移动它,图片会突然移动很多。

有趣的是,当我以编程方式调用 mouseMoveEvent 时,由于某种原因 event.position() 发生了变化,即使事件始终是相同的。

这是一个最小的可重现示例:

adjustment_view 函数是原来的函数,需要上下移动光标才能保持图片移动

adjustment_view_loop 函数是我尝试以编程方式使其工作

我使用Python 3.10.7和PySide6.3.2

from PySide6.QtCore import Signal, Qt, QRectF, QPoint, QRect
from PySide6.QtGui import QPainter, QPixmap, QImage, QPen, QCursor, QMouseEvent
from PySide6.QtWidgets import (
    QGraphicsView,
    QGraphicsScene,
    QGraphicsPixmapItem,
    QGraphicsRectItem,
    QApplication,
    QDialog,
    QDialogButtonBox,
    QVBoxLayout,
    QHBoxLayout,
)

import warnings
import time
import sys


class MyDialog(QDialog):
    MIN_WIDTH = 900
    MIN_HEIGHT = int(MIN_WIDTH * 2 / 3)

    def __init__(self, path, parent=None):
        super().__init__(parent)
        self.path = path
        self.setFixedSize(self.MIN_WIDTH, self.MIN_HEIGHT)

        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        self.buttonBox.rejected.connect(self.reject)
        self.buttonBox.accepted.connect(self.accept)

        self.layout = QVBoxLayout()
        self.image_viewer = PhotoViewer(self)
        image = QImage(path)
        self.pixmap = QPixmap(image)
        self.image_viewer.set_photo(self.pixmap)

        bottom_layout = QHBoxLayout()
        bottom_layout.addWidget(self.buttonBox)

        self.layout.addWidget(self.image_viewer)
        self.layout.addLayout(bottom_layout)
        self.setLayout(self.layout)

    def exec(self):
        self.image_viewer.viewport().setMinimumSize(
            self.MIN_WIDTH - 100, self.MIN_HEIGHT - 100
        )
        self.image_viewer.fit_in_view()
        self.image_viewer.calc_delta()
        super().exec()


class PhotoViewer(QGraphicsView):
    """
    class for displaying the pictures
    """

    DELTA = 10
    mouse_event_signal = Signal()

    def __init__(self, parent):
        super(PhotoViewer, self).__init__(parent)
        warnings.filterwarnings("error")
        self.zoom = 0
        self._empty = True
        self._photo = QGraphicsPixmapItem()
        self._photo.setTransformationMode(Qt.SmoothTransformation)

        self.set_scene()
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setRenderHints(QPainter.SmoothPixmapTransform | QPainter.Antialiasing)

    def set_scene(self):
        self.scene = SelectColorBarDialogScene(self)
        self.scene.addItem(self._photo)
        self.setScene(self.scene)

    def fit_in_view(self, scale=True):
        """
        Resizes the photo to fit in the view
        """
        rect = QRectF(self._photo.pixmap().rect())
        if not rect.isNull():
            self.setSceneRect(rect)
            unity = self.transform().mapRect(QRectF(0, 0, 1, 1))
            self.scale(1 / unity.width(), 1 / unity.height())
            viewrect = self.viewport().rect()
            scenerect = self.transform().mapRect(rect)
            factor = min(
                viewrect.width() / scenerect.width(),
                viewrect.height() / scenerect.height(),
            )
            self.scale(factor, factor)
            self.zoom = 0

    def mousePressEvent(self, event):
        if event.buttons() & Qt.RightButton:
            self.update()
        super(PhotoViewer, self).mousePressEvent(event)

    def mouseMoveEvent(self, event, pass_adjust=False):
        super(PhotoViewer, self).mouseMoveEvent(event)
        if event.buttons() & Qt.RightButton:
            if not self.scene.begin.isNull() and not self.scene.destination.isNull():
                if not pass_adjust:
                    # self.adjust_view(event)
                    self.adjust_view_loop(event)

                print(event.globalPosition())
                print(event.position())
                print("_______________")

            self.update()

    #! When programatically changed, the mouse pos changes even though cursor is not moved
    def adjust_view_loop(self, event):
        edges = self.check_if_point_on_edge(self.scene.destination)

        if (
            edges
            and self.scene.destination.x() < self.photo_size.width() - self.DELTA
            and self.scene.destination.y() < self.photo_size.height() - self.DELTA
            and self.scene.destination.x() > self.DELTA
            and self.scene.destination.y() > self.DELTA
        ):
            i = 0
            self.cursor_pos = QCursor.pos()
            while True:
                i += 1
                old_view = self.mapToScene(self.viewport().rect()).boundingRect()
                new_view = [0, 0, 0, 0]
                for _ in range(edges.bit_count()):
                    if edges & 1 == 1:
                        new_view[1] = -self.DELTA
                        new_view[3] = -self.DELTA
                    elif edges & 4 == 4:
                        new_view[1] = self.DELTA
                        new_view[3] = self.DELTA
                    elif edges & 2 == 2:
                        new_view[0] = -self.DELTA
                        new_view[2] = -self.DELTA
                    elif edges & 8 == 8:
                        new_view[0] = self.DELTA
                        new_view[2] = self.DELTA
                new_view_rect = old_view.adjusted(
                    new_view[0], new_view[1], new_view[2], new_view[3]
                )
                self.fitInView(new_view_rect, Qt.KeepAspectRatio)
                x = self.scene.destination.x()
                y = self.scene.destination.y()
                self.set_rect()
                if i % 2 == 0:
                    new_event = QMouseEvent(
                        event.type(),
                        QPoint(event.position().x(), event.position().y() + 1),
                        QPoint(
                            event.globalPosition().x(), event.globalPosition().y() + 1
                        ),
                        event.button(),
                        event.buttons(),
                        event.modifiers(),
                    )
                else:
                    new_event = QMouseEvent(
                        event.type(),
                        QPoint(event.position().x(), event.position().y() - 1),
                        QPoint(
                            event.globalPosition().x(), event.globalPosition().y() - 1
                        ),
                        event.button(),
                        event.buttons(),
                        event.modifiers(),
                        event.pointingDevice(),
                    )
                self.mouseMoveEvent(new_event, True)
                self.update()
                print("Looping...")
                time.sleep(0.5)
                if not self.on_loop_signal():
                    break

        self.set_rect()

    def adjust_view(self, event):
        edges = self.check_if_point_on_edge(self.scene.destination)

        if (
            edges
            and self.scene.destination.x() < self.photo_size.width() - self.DELTA
            and self.scene.destination.y() < self.photo_size.height() - self.DELTA
            and self.scene.destination.x() > self.DELTA
            and self.scene.destination.y() > self.DELTA
        ):
            old_view = self.mapToScene(self.viewport().rect()).boundingRect()
            new_view = [0, 0, 0, 0]
            for _ in range(edges.bit_count()):
                if edges & 1 == 1:
                    new_view[1] = -self.DELTA
                    new_view[3] = -self.DELTA
                elif edges & 4 == 4:
                    new_view[1] = self.DELTA
                    new_view[3] = self.DELTA
                elif edges & 2 == 2:
                    new_view[0] = -self.DELTA
                    new_view[2] = -self.DELTA
                elif edges & 8 == 8:
                    new_view[0] = self.DELTA
                    new_view[2] = self.DELTA
            new_view_rect = old_view.adjusted(
                new_view[0], new_view[1], new_view[2], new_view[3]
            )
            self.fitInView(new_view_rect, Qt.KeepAspectRatio)
            x = self.scene.destination.x()
            y = self.scene.destination.y()
            self.set_rect()

        self.set_rect()

    def set_rect(self):
        for item in self.scene.items():
            if isinstance(item, QGraphicsRectItem):
                self.scene.removeItem(item)
        self.rect = QRect(self.scene.begin, self.scene.destination)
        self.scene.addRect(self.rect.normalized(), QPen(Qt.red))

    def get_rect(self):
        return self.rect.normalized()

    def mouseReleaseEvent(self, event):
        if event.button() & Qt.RightButton:
            self.set_rect()
            self.update()
        super(PhotoViewer, self).mouseReleaseEvent(event)

    def check_if_point_on_edge(self, point):
        rect = self.mapToScene(
            self.viewport().rect()
        ).boundingRect()  # left, top, width, height
        edges = 0  # top = 1, bottom = 2, left = 4, right = 8
        internal_rect = rect.adjusted(self.DELTA, self.DELTA, -self.DELTA, -self.DELTA)
        if rect.contains(point) and not internal_rect.contains(point):
            if point.x() < internal_rect.left():
                edges = edges | 2
            elif point.x() > internal_rect.right():
                edges = edges | 8
            if point.y() < internal_rect.top():
                edges = edges | 1
            elif point.y() > internal_rect.bottom():
                edges = edges | 4

        return edges

    def wheelEvent(self, event):
        self.calc_delta()
        if event.angleDelta().y() > 0:
            factor = 1.25
            self.zoom += 1
        else:
            factor = 0.8
            self.zoom -= 1
        if self.zoom > 0:
            self.scale(factor, factor)
        elif self.zoom == 0:
            self.fit_in_view()
        else:
            self.zoom = 0

    def calc_delta(self):
        rect_width = self.mapToScene(self.viewport().rect()).boundingRect().width()
        self.DELTA = int(rect_width / 25)

    def set_photo(self, pixmap=None):
        """
        Sets the photo to be displayed, if pixmap is None, the photo is set to empty
        """
        if pixmap and not pixmap.isNull():
            self._empty = False
            self.setDragMode(QGraphicsView.ScrollHandDrag)
            self._photo.setPixmap(pixmap)
        else:
            self._empty = True
            self.setDragMode(QGraphicsView.NoDrag)
            self._photo.setPixmap(QPixmap())
        self.photo_size = QRectF(self._photo.pixmap().rect())

    def on_loop_signal(self):
        if self.cursor_pos != QCursor.pos():
            self.mouse_event_signal.emit()
            return False
        return True


class SelectColorBarDialogScene(QGraphicsScene):
    def __init__(self, parent):
        super(SelectColorBarDialogScene, self).__init__(parent)
        self.begin, self.destination = QPoint(), QPoint()

    def mousePressEvent(self, event):
        if event.buttons() & Qt.RightButton:
            self.begin = event.scenePos().toPoint()
            self.destination = self.begin
            self.update()
        super(SelectColorBarDialogScene, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if event.buttons() & Qt.RightButton:
            self.destination = event.scenePos().toPoint()
            self.update()
        super(SelectColorBarDialogScene, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if event.button() & Qt.RightButton:
            self.begin, self.destination = QPoint(), QPoint()
            self.update()
        super(SelectColorBarDialogScene, self).mouseReleaseEvent(event)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    dialog = MyDialog(path=None)  #! set picture path here
    dialog.exec()
    sys.exit(app.exec_())
python pyqt pyside6
© www.soinside.com 2019 - 2024. All rights reserved.