如何将值从 JS 传回 Python

How to pass back value from JS to Python

我想用 Python 显示一个二维地图,然后用 Python 代码中的 coursor 坐标做一些事情。但是,我无法获得 Python 部分的坐标。 这是我的代码:

from PyQt5.QtCore import *
from PyQt5.QtGui import *

from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtWidgets import QWidget,QVBoxLayout, QApplication
from PyQt5.QtWebChannel import QWebChannel
import bs4

maphtml = '''

<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
  <head>
    <meta name="robots" content="index, all" />    
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <title>WebGL Earth API - Side-by-side - Basic Leaflet compatibility</title>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
    <script src="http://www.webglearth.com/v2/api.js"></script>
    <script>
      var backend;
        new QWebChannel(qt.webChannelTransport, function (channel) {
            backend = channel.objects.backend;
        });
      function init() {
        var m = {};

        start_(L, 'L');
        start_(WE, 'WE');

        function start_(API, suffix) {
          var mapDiv = 'map' + suffix;
          var map = API.map(mapDiv, {
            center: [51.505, -0.09],
            zoom: 4,
            dragging: true,
            scrollWheelZoom: true,
            proxyHost: 'http://srtm.webglearth.com/cgi-bin/corsproxy.fcgi?url='
          });
          m[suffix] = map;

          //Add baselayer
          API.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
            attribution: '© OpenStreetMap contributors'
          }).addTo(map);

          //Add TileJSON overlay
          var json = {"profile": "mercator", "name": "Grand Canyon USGS", "format": "png", "bounds": [-112.26379395, 35.98245136, -112.10998535, 36.13343831], "minzoom": 10, "version": "1.0.0", "maxzoom": 16, "center": [-112.18688965, 36.057944835, 13], "type": "overlay", "description": "", "basename": "grandcanyon", "tilejson": "2.0.0", "sheme": "xyz", "tiles": ["http://tileserver.maptiler.com/grandcanyon/{z}/{x}/{y}.png"]};
          if (API.tileLayerJSON) {
            var overlay2 = API.tileLayerJSON(json, map);
          } else {
            //If not able to display the overlay, at least move to the same location
            map.setView([json.center[1], json.center[0]], json.center[2]);
          }

          //Add simple marker
          var marker = API.marker([json.center[1], json.center[0]]).addTo(map);
          marker.bindPopup(suffix, 50);
          marker.openPopup();

          //Print coordinates of the mouse
          map.on('mousemove', function(e) {
            document.getElementById('coords').innerHTML = e.latlng.lat + ', ' + e.latlng.lng;
            backend.print(e.latlng.lat)
          });
        }

        //Synchronize view
        m['L'].on('move', function(e) {
          var center = m['L'].getCenter();
          var zoom = m['L'].getZoom();
          m['WE'].setView([center['lat'], center['lng']], zoom);
        });
      }
    </script>
    <style>
      html, body{padding: 0; margin: 0; overflow: hidden;}
      #mapL, #mapWE {position:absolute !important; top: 0; right: 0; bottom: 0; left: 0;
                     background-color: #fff; position: absolute !important;}
      #mapL {right: 0%;}
      #mapWE {left: 100%;}
      #coords {position: absolute; bottom: 0;}
    </style>
  </head>
  <body onload="javascript:init()">
    <div id="mapL"></div>
    <div id="mapWE"></div>
    <div id="coords"></div>
  </body>
</html>
'''

class Browser(QApplication):
    def __init__(self):
        QApplication.__init__(self, [])
        self.window = QWidget()
        self.window.setWindowTitle("Serial GPS Emulator");

        self.web = QWebEngineView(self.window)

        self.web.setHtml(maphtml)
        self.layout = QVBoxLayout(self.window)
        self.layout.addWidget(self.web)

        self.window.show()
        self.exec()

Browser()

如果代码保留在一个文件中就好了,但如果完全不可能一次拆分完成,那也是可以接受的。 我想解决 mny 问题的第一步是从 HTML/JS 部分调用一个函数,因为 backend.print("test") 也不起作用。 我还注意到 self.exec() 阻止了其余代码,有没有办法在地图为 运行 时执行任何其他代码?谢谢!

我没有看到你在代码中的哪个地方传递了后端。

在这种情况下,您必须创建一个可以注入的 Backend class,并且对于要调用的方法,它必须是一个插槽,为此您必须使用 pyqtSlot() 设置,它接收的参数取决于您发送的参数,在 e.latlng 的情况下是 QJsonValue。在插槽中,您必须将必要的部分分开。

import sys

from PyQt5.QtCore import pyqtSlot, QObject, QJsonValue, pyqtSignal, QTimer
from PyQt5.QtWebChannel import QWebChannel
from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QApplication

maphtml = '''

<!DOCTYPE HTML>
<!DOCTYPE HTML>
<html>
  <head>
    <meta name="robots" content="index, all" />    
    <script src="qrc:///qtwebchannel/qwebchannel.js"></script>
    <title>WebGL Earth API - Side-by-side - Basic Leaflet compatibility</title>
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
    <script src="http://www.webglearth.com/v2/api.js"></script>
    <script>
      var backend;
        new QWebChannel(qt.webChannelTransport, function (channel) {
            backend = channel.objects.backend;
            console.log(backend);
        });
      function init() {
        var m = {};

        start_(L, 'L');
        start_(WE, 'WE');

        function start_(API, suffix) {
          var mapDiv = 'map' + suffix;
          var map = API.map(mapDiv, {
            center: [51.505, -0.09],
            zoom: 4,
            dragging: true,
            scrollWheelZoom: true,
            proxyHost: 'http://srtm.webglearth.com/cgi-bin/corsproxy.fcgi?url='
          });
          m[suffix] = map;

          //Add baselayer
          API.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
            attribution: '© OpenStreetMap contributors'
          }).addTo(map);

          //Add TileJSON overlay
          var json = {"profile": "mercator", "name": "Grand Canyon USGS", "format": "png", "bounds": [-112.26379395, 35.98245136, -112.10998535, 36.13343831], "minzoom": 10, "version": "1.0.0", "maxzoom": 16, "center": [-112.18688965, 36.057944835, 13], "type": "overlay", "description": "", "basename": "grandcanyon", "tilejson": "2.0.0", "sheme": "xyz", "tiles": ["http://tileserver.maptiler.com/grandcanyon/{z}/{x}/{y}.png"]};
          if (API.tileLayerJSON) {
            var overlay2 = API.tileLayerJSON(json, map);
          } else {
            //If not able to display the overlay, at least move to the same location
            map.setView([json.center[1], json.center[0]], json.center[2]);
          }

          //Add simple marker
          var marker = API.marker([json.center[1], json.center[0]]).addTo(map);
          marker.bindPopup(suffix, 50);
          marker.openPopup();

          //Print coordinates of the mouse
          map.on('mousemove', function(e) {
            document.getElementById('coords').innerHTML = e.latlng.lat + ', ' + e.latlng.lng;
            backend.print(e.latlng)
          });
        }

        //Synchronize view
        m['L'].on('move', function(e) {
          var center = m['L'].getCenter();
          var zoom = m['L'].getZoom();
          m['WE'].setView([center['lat'], center['lng']], zoom);
        });
      }
    </script>
    <style>
      html, body{padding: 0; margin: 0; overflow: hidden;}
      #mapL, #mapWE {position:absolute !important; top: 0; right: 0; bottom: 0; left: 0;
                     background-color: #fff; position: absolute !important;}
      #mapL {right: 0%;}
      #mapWE {left: 100%;}
      #coords {position: absolute; bottom: 0;}
    </style>
  </head>
  <body onload="javascript:init()">
    <div id="mapL"></div>
    <div id="mapWE"></div>
    <div id="coords"></div>
  </body>
</html>
'''


class Backend(QObject):
    positionChanged = pyqtSignal(float, float)

    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.position = None, None
        self.timer = QTimer(self)
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.on_timeout)

    @pyqtSlot(QJsonValue)
    def print(self, val):
        coords = val.toObject()
        lat, lng = (coords[key].toDouble() for key in ("lat", "lng"))
        self.position = lat, lng
        if not self.timer.isActive():
            self.timer.start()

    def on_timeout(self):
        self.positionChanged.emit(*self.position)


def foo(lat, lng):
    # this function will be called every second.
    print(lat, lng)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    view = QWebEngineView()
    view.setWindowTitle("Serial GPS Emulator");

    backend = Backend(view)
    backend.positionChanged.connect(foo)
    channel = QWebChannel()
    channel.registerObject('backend', backend)
    view.page().setWebChannel(channel)
    view.setHtml(maphtml)
    view.show()
    sys.exit(app.exec_())