GraphicsView Scene postEvent focusProxy 不将事件从事件过滤器传递到 QWebEngineView Widget

GraphicsView Scene postEvent focusProxy does not pass events from event filter to QWebEngineView Widget

我目前正在学习如何在 PyQt5 中编写 GUI,使用户能够在 QGrapchisView Scene 中绘制线条和矩形,并设置包含 folium 地图的 QWebEngineView 小部件作为背景。

问题: 为了同步 QGrapchisView 场景和 folium 地图平移和缩放,我使用事件过滤器将 GraphicsScene 事件传递给 QWebEngineView 小部件。

QEvent.GraphicsSceneWheel 事件按预期传递,但 QEvent.GraphicsSceneMouseReleaseQEvent.GraphicsSceneMousePressQEvent.GraphicsSceneMouseMove 未传递给 QWebEngineView 小部件。

预期行为: 选定的 GraphicsScene 事件被传递到 QWebEngineView 小部件,它可以同步两个小部件的平移和缩放。

目前我尝试过的:

import folium
import sys
from PyQt5 import QtGui, QtCore, QtWidgets, QtWebEngineWidgets
import io

class eventFilterClass(QtCore.QObject):
    def __init__(self, sender, receiver, gv):
        super(eventFilterClass, self).__init__()
        self.gv = gv
        self.m_sender = sender
        self.m_receiver = receiver
        self.m_sender.installEventFilter(self)

    def eventFilter(self, obj, event):
        if self.m_sender is obj:
            if event.type() == QtCore.QEvent.GraphicsSceneMousePress:
                if event.button() == QtCore.Qt.MiddleButton:
                    new_event = QtGui.QMouseEvent(int(2), self.gv.mapFromScene(event.scenePos()),
                                                  self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(),
                                                  event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                    return True

            elif event.type() == QtCore.QEvent.GraphicsSceneMouseMove:
                if event.buttons() == QtCore.Qt.NoButton:
                    new_event = QtGui.QMouseEvent(int(5), self.gv.mapFromScene(event.scenePos()),self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(), event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                    return True

            elif event.type() == QtCore.QEvent.GraphicsSceneMouseRelease:
                print('Release!')
                if event.button() == QtCore.Qt.MiddleButton:
                    new_event = QtGui.QMouseEvent(int(3), self.gv.mapFromScene(event.scenePos()),
                                                  self.gv.mapFromScene(event.scenePos()), event.screenPos(), event.button(),
                                                  event.buttons(), event.modifiers(), event.source())
                    QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)

            elif event.type() == QtCore.QEvent.GraphicsSceneWheel:
                new_event = QtGui.QWheelEvent(self.gv.mapFromScene(event.scenePos()), event.screenPos(), QtCore.QPoint(), QtCore.QPoint(0, event.delta()),event.buttons(), event.modifiers(), int(0), False, int(0))
                QtCore.QCoreApplication.postEvent(self.m_receiver.focusProxy(), new_event)
                return True
        return False

class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent, scene=None):
        super(GraphicsView, self).__init__(scene, parent)
        "VARIABLES INICIALES"
        self.pos_init_class = None
        self.scale_factor = 1.25
        "ACTIVAR TRACKING DE POSICION DE MOUSE"
        self.setMouseTracking(True)
        "REMOVER BARRAS DE SCROLL"
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        "ASIGNAR ANCLA PARA HACER ZOOM SOBRE EL MISMO PUNTO"
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            self.scale(self.scale_factor, self.scale_factor)
        else:
            self.scale(1 / self.scale_factor, 1 / self.scale_factor)
        super(GraphicsView, self).wheelEvent(event)

    def mousePressEvent(self, event):
        pos = self.mapToScene(event.pos())
        "PAN"
        if event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = pos
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ClosedHandCursor)
            super(GraphicsView, self).mousePressEvent(event)
            return
        super(GraphicsView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        pos = self.mapToScene(event.pos())
        "PAN"
        if self.pos_init_class:
            delta = self.pos_init_class - self.mapToScene(event.pos())
            r = self.mapToScene(self.viewport().rect()).boundingRect()
            self.setSceneRect(r.translated(delta))
            return
        super(GraphicsView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        "PAN"
        if self.pos_init_class and event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = None
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
        super(GraphicsView, self).mouseReleaseEvent(event)

class Ui_MainWindow(object):
    def __init__(self):
        super(Ui_MainWindow, self).__init__()

    def zoom_ext(self):
        "Zoom extent"
        x_range, y_range, h_range, w_range = self.graphicsView.scene().itemsBoundingRect().getRect()
        rect_scene = QtCore.QRectF(x_range, y_range, h_range, w_range)
        self.graphicsView.setSceneRect(rect_scene)
        unity = self.graphicsView.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
        self.graphicsView.scale(1 / unity.width(), 1 / unity.height())
        viewrect = self.graphicsView.viewport().rect()
        scenerect = self.graphicsView.transform().mapRect(rect_scene)
        factor = min(viewrect.width() / scenerect.width(), viewrect.height() / scenerect.height())
        self.graphicsView.scale(factor, factor)

    def draw_lines(self):
        cancha = QtGui.QPolygonF([
            QtCore.QPointF(721383.8266, -9678885.4514),
            QtCore.QPointF(721404.5488, -9678961.6564),
            QtCore.QPointF(721453.4389, -9678948.7816),
            QtCore.QPointF(721433.20288, -9678871.92070)])
        self.graphicsView.scene().addPolygon(cancha, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine))
        caseta = QtGui.QPolygonF([
            QtCore.QPointF(721455.8594, -9678925.4517),
            QtCore.QPointF(721492.5411, -9678915.2403),
            QtCore.QPointF(721498.3404, -9678937.9702),
            QtCore.QPointF(721461.7904, -9678947.5050)])
        self.graphicsView.scene().addPolygon(caseta, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine))

    def setupUi(self, MainWindow):
        "CENTRAL WIDGET"
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        MainWindow.setCentralWidget(self.centralwidget)
        MainWindow.resize(1000, 1000)

        "WEB MAP"
        self.w = QtWebEngineWidgets.QWebEngineView(self.centralwidget)
        self.w.setGeometry(QtCore.QRect(0, 0, 1000, 1000))

        "QGRAPCHISVIEW SCENE"
        self.graphicsView = GraphicsView(parent=self.centralwidget)
        self.scene = QtWidgets.QGraphicsScene(parent=self.graphicsView)
        self.graphicsView.setScene(self.scene)
        self.graphicsView.setGeometry(QtCore.QRect(0, 0, 1000, 1000))
        self.graphicsView.setStyleSheet("background:transparent;")

        "Draw LINES"
        self.draw_lines()
        "ZOOM EXT"
        self.zoom_ext()

        "AGREAGR MAPA"
        self.m = folium.Map(tiles='http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}', attr='Google Satellite', zoomSnap=0.0, wheelDebounceTime=-1,
                            wheelPxPerZoomLevel=105, prefer_canvas=True, control_scale=True, max_zoom=100000,
                            zoomControl=False)
        sw, ne = (-2.903683906544795, -79.00835706455769), (-2.9026234455284583, -79.00729860799157)
        self.m.fit_bounds([sw, ne], padding=(0, 0))
        data = io.BytesIO()
        self.m.save(data, close_file=False)
        self.w.setHtml(data.getvalue().decode())

        "APLICAR FILTER"
        self.efc = eventFilterClass(sender=self.graphicsView.scene(), receiver=self.w, gv=self.graphicsView)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

问题:

还有其他方法可以实现吗?

OP 似乎试图通过采用不必要的自定义参数来创建事件。在我的解决方案中,我建议只复制参数:

import io
import sys

from PyQt5 import QtGui, QtCore, QtWidgets, QtWebEngineWidgets

import folium


class ReplyEvents(QtCore.QObject):
    def __init__(self, sender, receiver, parent=None):
        super().__init__(parent)
        self._sender = sender
        self._receiver = receiver
        self._sender.installEventFilter(self)

    def eventFilter(self, obj, event):
        if obj is self._sender:
            new_event = None
            if event.type() in (
                QtCore.QEvent.MouseButtonPress,
                QtCore.QEvent.MouseButtonRelease,
                QtCore.QEvent.MouseButtonDblClick,
                QtCore.QEvent.MouseMove,
            ):
                new_event = QtGui.QMouseEvent(
                    event.type(),
                    event.pos(),
                    event.button(),
                    event.buttons(),
                    event.modifiers(),
                )

            elif event.type() == QtCore.QEvent.Wheel:
                new_event = QtGui.QWheelEvent(
                    event.pos(),
                    event.globalPosition(),
                    event.pixelDelta(),
                    event.angleDelta(),
                    event.buttons(),
                    event.modifiers(),
                    event.phase(),
                    event.inverted(),
                )
            if new_event:
                QtCore.QCoreApplication.postEvent(self._receiver, new_event)
        return super().eventFilter(obj, event)


class GraphicsView(QtWidgets.QGraphicsView):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.scale_factor = 1.25
        self.pos_init_class = QtCore.QPointF()

        scene = QtWidgets.QGraphicsScene(self)
        self.setScene(scene)

        self.setStyleSheet("background:transparent;")
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)

        self.setMouseTracking(True)

    def wheelEvent(self, event):
        if event.angleDelta().y() > 0:
            self.scale(self.scale_factor, self.scale_factor)
        else:
            self.scale(1 / self.scale_factor, 1 / self.scale_factor)
        super().wheelEvent(event)

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = self.mapToScene(event.pos())
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ClosedHandCursor)
        super().mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if not self.pos_init_class.isNull():
            delta = self.pos_init_class - self.mapToScene(event.pos())
            r = self.mapToScene(self.viewport().rect()).boundingRect()
            self.setSceneRect(r.translated(delta))
        super().mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        if not self.pos_init_class.isNull() and event.button() == QtCore.Qt.MiddleButton:
            self.pos_init_class = QtCore.QPointF()
            QtWidgets.QApplication.setOverrideCursor(QtCore.Qt.ArrowCursor)
        super().mouseReleaseEvent(event)


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

        self.resize(1000, 1000)

        self.web_view = QtWebEngineWidgets.QWebEngineView(self)
        self.graphics_view = GraphicsView(self)

        geometry = QtCore.QRect(0, 0, 1000, 1000)

        self.web_view.setGeometry(geometry)
        self.graphics_view.setGeometry(geometry)

        self.draw_lines()
        self.zoom_ext()

        m = folium.Map(
            tiles="http://www.google.cn/maps/vt?lyrs=s@189&gl=cn&x={x}&y={y}&z={z}",
            attr="Google Satellite",
            zoomSnap=0.0,
            wheelDebounceTime=-1,
            wheelPxPerZoomLevel=105,
            prefer_canvas=True,
            control_scale=True,
            max_zoom=100000,
            zoomControl=False,
        )

        sw, ne = (
            (-2.903683906544795, -79.00835706455769),
            (-2.9026234455284583, -79.00729860799157),
        )
        m.fit_bounds([sw, ne], padding=(0, 0))
        data = io.BytesIO()
        m.save(data, close_file=False)
        self.web_view.setHtml(data.getvalue().decode())

        r = ReplyEvents(self.graphics_view.viewport(), self.web_view.focusProxy(), self)

    def draw_lines(self):
        cancha = QtGui.QPolygonF(
            [
                QtCore.QPointF(721383.8266, -9678885.4514),
                QtCore.QPointF(721404.5488, -9678961.6564),
                QtCore.QPointF(721453.4389, -9678948.7816),
                QtCore.QPointF(721433.20288, -9678871.92070),
            ]
        )
        self.graphics_view.scene().addPolygon(
            cancha, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine)
        )
        caseta = QtGui.QPolygonF(
            [
                QtCore.QPointF(721455.8594, -9678925.4517),
                QtCore.QPointF(721492.5411, -9678915.2403),
                QtCore.QPointF(721498.3404, -9678937.9702),
                QtCore.QPointF(721461.7904, -9678947.5050),
            ]
        )
        self.graphics_view.scene().addPolygon(
            caseta, QtGui.QPen(QtCore.Qt.red, 2.5, QtCore.Qt.SolidLine)
        )

    def zoom_ext(self):
        "Zoom extent"
        x_range, y_range, h_range, w_range = (
            self.graphics_view.scene().itemsBoundingRect().getRect()
        )
        rect_scene = QtCore.QRectF(x_range, y_range, h_range, w_range)
        self.graphics_view.setSceneRect(rect_scene)
        unity = self.graphics_view.transform().mapRect(QtCore.QRectF(0, 0, 1, 1))
        self.graphics_view.scale(1 / unity.width(), 1 / unity.height())
        viewrect = self.graphics_view.viewport().rect()
        scenerect = self.graphics_view.transform().mapRect(rect_scene)
        factor = min(
            viewrect.width() / scenerect.width(), viewrect.height() / scenerect.height()
        )
        self.graphics_view.scale(factor, factor)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())