更改事件键上的光标形状

Change cursor shape on event key

我正在尝试更改按键事件的光标形状:

只有离开 canvas 和 return 光标才会改变, 但如果光标停留在 canvas 中则不会。 self.update() 在 canvas 上不起作用

这里是重现问题的代码:

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_())

这似乎是一个从未解决的老错误:setCursor on QGraphicsView don't work when add QWidget on the QGraphicsScene

有一个可能的解决方法,但它远非完美。
首先,您必须考虑到,在处理鼠标事件和小部件代理时,处理 QGraphicsScene 及其视图 [s] 并不容易,主要是因为事件的多个嵌套级别以及实际视图(及其视图)之间的交互父级,直到顶层 window) 和代理本身,它是您添加到场景中的小部件的抽象。虽然 Qt 开发人员做了大量工作以使其尽可能透明,但在某些时候您可能会遇到一些通常难以修复或变通的意外或不希望的行为,这也是因为图形场景可能在不止一个视图。

除了上述错误之外,您还必须考虑到图形视图在其任何项目自身调用 setCursor 时在内部使用 QWidget.setCursor,并且由于视图是一个非常复杂的小部件,在如果它 认为 它应该(即使它不应该),它甚至可能会尝试 "restore" 光标。
最后,一些也与焦点有关的事件可能会妨碍所有这些。

第一个解决方法是将光标设置到视图本身(或者,更好的是,视图的视口,它是显示场景内容的实际小部件)。为了确保这一点,我们显然需要检查光标是否在 canvas.

不幸的是,由于上面写的事件处理,这可能会变得有点混乱,因为一些事件甚至在主 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())