Изменить форму курсора на кнопке события - PullRequest
2 голосов
/ 10 января 2020

Я пытаюсь изменить форму курсора при ключевом событии:

  • Когда я нажимаю 'C', я хочу отобразить LineCursor,
  • , когда я нажимаю 'S', я хочу отобразить CrossCursor, и
  • , когда я нажимаю 'N', я хочу отобразить стандартный ArrowCursor.

Курсор изменится, только если он покинет холст и вернуться к нему, но не, если курсор остается на холсте. self.update () на холсте не работает

Вот код для воспроизведения проблемы:

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import sys

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setObjectName("MainWindow")
        self.resize(942, 935)
        self.centralwidget = QWidget(self)
        self.centralwidget.setObjectName("centralwidget")
        self.horizontalLayout = QHBoxLayout(self.centralwidget)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.MainView = QGraphicsView(self.centralwidget)
        self.MainView.setObjectName("MainView")
        self.horizontalLayout.addWidget(self.MainView)
        self.setCentralWidget(self.centralwidget)
        self.setWindowTitle("MainWindow")

        self.scene = QGraphicsScene( 0.,0., 1240., 1780. )
        self.canvas = Canvas()

        self.widget = QWidget()
        box_layout = QVBoxLayout()
        self.widget.setLayout(box_layout)
        box_layout.addWidget(self.canvas)
        self.scene.addWidget(self.widget)
        self.MainView.setScene(self.scene)
        self.MainView.setRenderHints(QPainter.Antialiasing)
        self.MainView.fitInView(0, 0, 45, 55, Qt.KeepAspectRatio)

        self.show()

        empty = QPixmap(1240, 1748)
        empty.fill(QColor(Qt.white))
        self.canvas.newPixmap(empty)

    def keyPressEvent(self, e):
        key = e.key()
        if key == Qt.Key_C:
            self.canvas.setCutCursor()
        elif key == Qt.Key_N:
            self.canvas.setNormalCursor()
        elif key == Qt.Key_S:
            self.canvas.setSelectionCursor()


class Canvas(QLabel):
    def __init__(self):
        super().__init__()
        sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.setSizePolicy(sizePolicy)
        self.setAlignment(Qt.AlignLeft)
        self.setAlignment(Qt.AlignTop)

    def newPixmap(self, pixmap):
        self.setPixmap(pixmap)

    def setCutCursor(self):
        newCursor = QPixmap(500,3)
        newCursor.fill(QColor("#000000"))
        self.setCursor(QCursor(newCursor))

    def setSelectionCursor(self):
        self.setCursor(Qt.CrossCursor)

    def setNormalCursor(self):
        self.setCursor(QCursor(Qt.ArrowCursor))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    mainWindow = MainWindow()
    sys.exit(app.exec_())

1 Ответ

2 голосов
/ 11 января 2020

Кажется, это старая ошибка, которая так и не была устранена: setCursor на QGraphicsView не работает при добавлении QWidget на QGraphicsScene

Возможный обходной путь, но он далек от perfect.
Прежде всего, вы должны учитывать, что при работе с QGraphicsScene и его view [s] нелегко работать с событиями мыши и прокси виджетов, в основном из-за множественных вложенных уровней событий и взаимодействия между фактический вид (и его родитель, вплоть до окна верхнего уровня) и сам прокси, который является абстракцией виджета, который вы добавили в сцену. Хотя разработчики Qt проделали огромную работу, чтобы сделать его максимально прозрачным, в какой-то момент вы, вероятно, столкнетесь с неожиданным или нежелательным поведением, которое обычно трудно исправить или обойти, и это также потому, что графическая сцена может быть визуализирована в больше, чем одно представление.

Помимо вышеупомянутой ошибки, вы должны учитывать, что графическое представление использует QWidget.setCursor внутри себя всякий раз, когда любой из его элементов вызывает setCursor для себя, и так как представление является очень сложным widget, в какой-то момент он может даже попытаться «восстановить» курсор, если он считает необходимым (даже если не должен).
Наконец, некоторые события, которые также имеют отношение к фокусу может стать препятствием для всего этого.

Первый обходной путь - установить курсор на само представление (или, что лучше, на видовой экран вида, который является фактическим виджетом, отображающим содержимое сцены). Чтобы убедиться в этом, нам, очевидно, необходимо проверить, находится ли курсор внутри холста.

К сожалению, из-за написанной выше обработки событий это может стать немного грязным, поскольку некоторые события даже задерживаются по крайней мере на цикл внутри основного цикла событий Qt; В результате, при установке курсора в первый раз может работать , его повторная установка может не , и даже если это произойдет, возможно, что курсор не будет применен, пока мышь перемещается как минимум на один пиксель.
В качестве второго обходного пути нам нужен фильтр событий, чтобы обойти все это и проверять курсор всякий раз, когда мышь перемещается в пределах полей области просмотра.

class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        # ...
        self.show()

        empty = QPixmap(1240, 1748)
        empty.fill(QColor(Qt.darkGray))
        self.canvas.newPixmap(empty)

        # install an event filter on the view's viewport;
        # this is very, *VERY* important: on the *VIEWPORT*!
        # if you install it on the view, it will *not* work
        self.MainView.viewport().installEventFilter(self)

    def insideCanvasRect(self, pos):
        canvasRect = self.canvas.rect()
        # translate the canvas rect to its top level window to get the actual
        # geometry according to the scene; we can't use canvas.geometry(), as
        # geometry() is based on the widget's parent coordinates, and that
        # parent could also have any number of parents in turn;
        canvasRect.translate(self.canvas.mapTo(self.canvas.window(), QPoint(0, 0)))
        # map the geometry to the view's transformation, which probably uses
        # some scaling, but also translation *and* shearing; the result is a
        # polygon, as with shearing you could transform a rectangle to an
        # irregular quadrilateral
        polygon = self.MainView.mapFromScene(QRectF(canvasRect))
        # tell if the point is within the resulting polygon
        return polygon.containsPoint(pos, Qt.WindingFill)

    def eventFilter(self, source, event):
        if source == self.MainView.viewport() and (
            (event.type() == QEvent.MouseMove and not event.buttons()) or
            (event.type() == QEvent.MouseButtonRelease)
            ):
                # process the event
                super(MainWindow, self).eventFilter(source, event)
                if self.insideCanvasRect(event.pos()):
                    source.setCursor(self.canvas.cursor())
                else:
                    source.unsetCursor()
                # usually a mouse move event within the view's viewport returns False,
                # but in that case the event would be propagated to the parents, up
                # to the top level window, which might reset the *previous* cursor
                # at some point, no matter if we try to avoid that; to prevent that
                # we return True to avoid propagation.
                # Note that this will prevent any upper-level filtering and *could*
                # also create some issues for the drag and drop framework
                if event.type() == QEvent.MouseMove:
                    return True
        return super(MainWindow, self).eventFilter(source, event)

    def keyPressEvent(self, e):
        # send the canvas a fake leave event
        QApplication.sendEvent(self.canvas, QEvent(QEvent.Leave))
        key = e.key()
        if key == Qt.Key_C:
            self.canvas.setCutCursor()
        elif key == Qt.Key_N:
            self.canvas.setNormalCursor()
        elif key == Qt.Key_S:
            self.canvas.setSelectionCursor()
        pos = self.canvas.rect().center()
        event = QEnterEvent(pos, self.canvas.mapTo(self.canvas.window(), pos), self.canvas.mapToGlobal(pos))
        # send a fake enter event (mapped to the center of the widget, just to be sure)
        QApplication.sendEvent(self.canvas, event)
        # if we're inside the widget, set the view's cursor, otherwise it will not
        # be set until the mouse is moved
        if self.insideCanvasRect(self.MainView.viewport().mapFromGlobal(QCursor.pos())):
            self.MainView.viewport().setCursor(self.canvas.cursor())
...