python - 如何使用 QWebEngineView 打开下载文件对话框?

标签 python pyqt pyqt5 qwebengineview

我正在构建一个 pyqt5 桌面界面,我在其中使用 QWebEngineView 来显示一个 html 文件,其中我显示了 Leaflet map 。这工作正常。 下一步是导出用户添加到 map 的所有要素。当我在 map 上单击“导出功能”时没有任何反应,但是当我在我的 Chromium 网络浏览器上打开相同的 html 文件时,“导出功能”可以正常打开下载对话框。

PyQt5 脚本:

self.MainWindow.webMapViewer = QtWebEngineWidgets.QWebEngineView()
self.MainWindow.webPageLayout.addWidget(self.MainWindow.webMapViewer)
self.html_path = os.path.split(os.path.abspath(__file__))[0] + r'/html/test.html'
self.MainWindow.webMapViewer.load(QtCore.QUrl().fromLocalFile(self.html_path))

HTML:

<!DOCTYPE html>
        <html lang="en">
          <head>
            <meta charset="utf-8">
            <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
            <title>FazMaraneyRGB_transparent_mosaic_group1</title>

            <!-- Leaflet -->
            <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
            <script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script>

            <!-- Leaflet.draw -->
            <link rel="stylesheet" href="https://unpkg.com/leaflet-draw@0.4.1/dist/leaflet.draw.css" />
            <script src="https://unpkg.com/leaflet-draw@0.4.1/dist/leaflet.draw.js"></script>

            <!-- Leaflet Ajax -->
            <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-ajax/2.1.0/leaflet.ajax.min.js"></script>

            <!-- Leaflet Measument -->
            <link rel="stylesheet" href="http://ljagis.github.io/leaflet-measure/leaflet-measure.css" />
            <script src="http://ljagis.github.io/leaflet-measure/leaflet-measure.min.js"></script>


            <style>
                body { margin:0; padding:0; }
                body, table, tr, td, th, div, h1, h2, input { font-family: "Calibri", "Trebuchet MS", "Ubuntu", Serif; font-size: 11pt; }
                #map { position:absolute; top:0; bottom:0; width:100%; } /* full size */
                .ctl {
                    padding: 2px 10px 2px 10px;
                    background: white;
                    background: rgba(255,255,255,0.9);
                    box-shadow: 0 0 15px rgba(0,0,0,0.2);
                    border-radius: 5px;
                    text-align: right;
                }
                .title {
                    font-size: 18pt;
                    font-weight: bold;
                }
                .src {
                    font-size: 10pt;
                }
                #delete, #export {
                    position: absolute;
                    top:100px;
                    right:10px;
                    z-index:100;
                    background:white;
                    color:black;
                    padding:6px;
                    border-radius:4px;
                    font-family: 'Helvetica Neue';
                    cursor: pointer;
                    font-size:12px;
                    text-decoration:none;
                }
                #export {
                    top:130px;
                }
            </style>

        </head>
        <body>

        <div id='map'></div>
        <div id='delete'>Delete Features</div>
        <a href='#' id='export'>Export Features</a>

        <script>
        /* **** Leaflet **** */

        // Base layers
        //  .. OpenStreetMap
        var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'});

        //  .. White background
        var white = L.tileLayer("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEX///+nxBvIAAAAH0lEQVQYGe3BAQ0AAADCIPunfg43YAAAAAAAAAAA5wIhAAAB9aK9BAAAAABJRU5ErkJggg==");

        // Overlay layers (TMS)
        var lyr1 = L.tileLayer('./tiles/{z}/{x}/{y}.png', {tms: true, maxZoom: 22, opacity: 0.9, attribution: ""});

        // Map
        var map = L.map('map', {
            measureControl: true,
            center: [-18.3604868606589, -52.694255477616245],
            zoom: 22,
            minZoom: 0,
            maxZoom: 22,
            layers: [osm]
        });

        lyr1.addTo(map);

        //Geojson Layers


        var basemaps = {"OpenStreetMap": osm, "Without background": white}
        var overlaymaps = {"Layer 1": lyr1}

        // Title
        var title = L.control();
        title.onAdd = function(map) {
            this._div = L.DomUtil.create('div', 'ctl title');
            this.update();
            return this._div;
        };
        title.update = function(props) {
            this._div.innerHTML = "FazMaraneyRGB_transparent_mosaic_group1";
        };
        title.addTo(map);

        // Note
        var src = 'Generated by Hawkit';
        var title = L.control({position: 'bottomleft'});
        title.onAdd = function(map) {
            this._div = L.DomUtil.create('div', 'ctl src');
            this.update();
            return this._div;
        };
        title.update = function(props) {
            this._div.innerHTML = src;
        };
        title.addTo(map);

        var featureGroup = L.featureGroup().addTo(map);

        var drawControl = new L.Control.Draw({
            edit: {
                featureGroup: featureGroup
            }
        }).addTo(map);

        map.on('draw:created', function(e) {

            // Each time a feaute is created, it's added to the over arching feature group
            featureGroup.addLayer(e.layer);
        });

        // on click, clear all layers
        document.getElementById('delete').onclick = function(e) {
            featureGroup.clearLayers();
        }

        document.getElementById('export').onclick = function(e) {
            // Extract GeoJson from featureGroup
            var data = featureGroup.toGeoJSON();

            // Stringify the GeoJson
            var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));

            // Create export
            document.getElementById('export').setAttribute('href', 'data:' + convertedData);
            document.getElementById('export').setAttribute('download','data.geojson');
        }


        // Add base layers
        L.control.layers(basemaps, overlaymaps, {collapsed: true}).addTo(map);

        // Fit to overlay bounds (SW and NE points with (lat, lon))
        map.fitBounds([[-18.36827062251916, -52.6871074784942], [-18.35270287637126, -52.7014028427423]]);

        </script>

        </body>
        </html>

最佳答案

该弹出窗口由浏览器生成,在 QWebEngine 的情况下,我们必须创建它。首先,必须检测指示下载的信号,该信号是来自 QWebEngineProfiledownloadRequested。该信号向我们发送了一个处理下载的 QWebEngineDownloadItem 对象,我们在其中借助 QFileDialog 创建了一个对话窗口。对于这种情况,我们将创建一个自定义 QWebEnginePage,如下所示:

index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' />
    <title>FazMaraneyRGB_transparent_mosaic_group1</title>

    <!-- Leaflet -->
    <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.css" />
    <script src="http://cdn.leafletjs.com/leaflet-0.7.5/leaflet.js"></script>

    <!-- Leaflet.draw -->
    <link rel="stylesheet" href="https://unpkg.com/leaflet-draw@0.4.1/dist/leaflet.draw.css" />
    <script src="https://unpkg.com/leaflet-draw@0.4.1/dist/leaflet.draw.js"></script>

    <!-- Leaflet Ajax -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-ajax/2.1.0/leaflet.ajax.min.js"></script>

    <!-- Leaflet Measument -->
    <link rel="stylesheet" href="http://ljagis.github.io/leaflet-measure/leaflet-measure.css" />
    <script src="http://ljagis.github.io/leaflet-measure/leaflet-measure.min.js"></script>

    <style>
        body {
            margin: 0;
            padding: 0;
        }
        
        body,
        table,
        tr,
        td,
        th,
        div,
        h1,
        h2,
        input {
            font-family: "Calibri", "Trebuchet MS", "Ubuntu", Serif;
            font-size: 11pt;
        }
        
        #map {
            position: absolute;
            top: 0;
            bottom: 0;
            width: 100%;
        }
        /* full size */
        
        .ctl {
            padding: 2px 10px 2px 10px;
            background: white;
            background: rgba(255, 255, 255, 0.9);
            box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
            border-radius: 5px;
            text-align: right;
        }
        
        .title {
            font-size: 18pt;
            font-weight: bold;
        }
        
        .src {
            font-size: 10pt;
        }
        
        #delete,
        #export {
            position: absolute;
            top: 100px;
            right: 10px;
            z-index: 100;
            background: white;
            color: black;
            padding: 6px;
            border-radius: 4px;
            font-family: 'Helvetica Neue';
            cursor: pointer;
            font-size: 12px;
            text-decoration: none;
        }
        
        #export {
            top: 130px;
        }
    </style>

</head>

<body>

    <div id='map'></div>
    <div id='delete'>Delete Features</div>
    <a href='#' id='export'>Export Features</a>

    <script>
        /* **** Leaflet **** */

        // Base layers
        //  .. OpenStreetMap
        var osm = L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
            attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        });

        //  .. White background
        var white = L.tileLayer("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAQMAAABmvDolAAAAA1BMVEX///+nxBvIAAAAH0lEQVQYGe3BAQ0AAADCIPunfg43YAAAAAAAAAAA5wIhAAAB9aK9BAAAAABJRU5ErkJggg==");

        // Overlay layers (TMS)
        var lyr1 = L.tileLayer('./tiles/{z}/{x}/{y}.png', {
            tms: true,
            maxZoom: 22,
            opacity: 0.9,
            attribution: ""
        });

        // Map
        var map = L.map('map', {
            measureControl: true,
            center: [-18.3604868606589, -52.694255477616245],
            zoom: 22,
            minZoom: 0,
            maxZoom: 22,
            layers: [osm]
        });

        lyr1.addTo(map);

        //Geojson Layers

        var basemaps = {
            "OpenStreetMap": osm,
            "Without background": white
        }
        var overlaymaps = {
            "Layer 1": lyr1
        }

        // Title
        var title = L.control();
        title.onAdd = function(map) {
            this._div = L.DomUtil.create('div', 'ctl title');
            this.update();
            return this._div;
        };
        title.update = function(props) {
            this._div.innerHTML = "FazMaraneyRGB_transparent_mosaic_group1";
        };
        title.addTo(map);

        // Note
        var src = 'Generated by Hawkit';
        var title = L.control({
            position: 'bottomleft'
        });
        title.onAdd = function(map) {
            this._div = L.DomUtil.create('div', 'ctl src');
            this.update();
            return this._div;
        };
        title.update = function(props) {
            this._div.innerHTML = src;
        };
        title.addTo(map);

        var featureGroup = L.featureGroup().addTo(map);

        var drawControl = new L.Control.Draw({
            edit: {
                featureGroup: featureGroup
            }
        }).addTo(map);

        map.on('draw:created', function(e) {

            // Each time a feaute is created, it's added to the over arching feature group
            featureGroup.addLayer(e.layer);
        });

        // on click, clear all layers
        document.getElementById('delete').onclick = function(e) {
            featureGroup.clearLayers();
        }

        document.getElementById('export').onclick = function(e) {
            // Extract GeoJson from featureGroup
            var data = featureGroup.toGeoJSON();

            // Stringify the GeoJson
            var convertedData = 'text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(data));

            // Create export
            document.getElementById('export').setAttribute('href', 'data:' + convertedData);
            document.getElementById('export').setAttribute('download', 'data.geojson');
        }

        // Add base layers
        L.control.layers(basemaps, overlaymaps, {
            collapsed: true
        }).addTo(map);

        // Fit to overlay bounds (SW and NE points with (lat, lon))
        map.fitBounds([
            [-18.36827062251916, -52.6871074784942],
            [-18.35270287637126, -52.7014028427423]
        ]);
    </script>

</body>

</html>

主.py

from PyQt5 import QtWebEngineWidgets, QtWidgets, QtCore

class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, *args, **kwargs):
        QtWebEngineWidgets.QWebEnginePage.__init__(self, *args, **kwargs)
        self.profile().downloadRequested.connect(self.on_downloadRequested)

    @QtCore.pyqtSlot(QtWebEngineWidgets.QWebEngineDownloadItem)
    def on_downloadRequested(self, download):
        old_path = download.path()
        suffix = QtCore.QFileInfo(old_path).suffix()
        path, _ = QtWidgets.QFileDialog.getSaveFileName(self.view(), "Save File", old_path, "*."+suffix)
        if path:
            download.setPath(path)
            download.accept()


if __name__ == '__main__':
    import sys

    sys.argv.append("--remote-debugging-port=8000")
    sys.argv.append("--disable-web-security")

    app = QtWidgets.QApplication(sys.argv)
    view = QtWebEngineWidgets.QWebEngineView()
    page = WebEnginePage(view)
    view.setPage(page)
    path = QtCore.QDir.current().filePath("index.html")
    view.load(QtCore.QUrl.fromLocalFile(path))
    view.show()
    sys.exit(app.exec_())

在你的情况下:

self.MainWindow.webMapViewer = QtWebEngineWidgets.QWebEngineView()
self.MainWindow.webPageLayout.addWidget(self.MainWindow.webMapViewer)
page = WebEnginePage(self.MainWindow.webMapViewer) # create page
self.MainWindow.webMapViewer.setPage(page) # set page
self.html_path = os.path.split(os.path.abspath(__file__))[0] + r'/html/test.html'
self.MainWindow.webMapViewer.load(QtCore.QUrl().fromLocalFile(self.html_path))

加上:

如果你想设置定义的路由,请使用以下命令:

@QtCore.pyqtSlot(QtWebEngineWidgets.QWebEngineDownloadItem)
def on_downloadRequested(self, download):
    directory = "/path/of/directory"
    filename = QtCore.QFileInfo(download.path()).fileName()
    download.setPath(QtCore.QDir(directory).filePath(filename))
    download.accept()

关于python - 如何使用 QWebEngineView 打开下载文件对话框?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50164712/

相关文章:

python - 如何从 QTableWidget 项目中获取选定的标题标签?

python - Qtreewidgetitem 内部字段

python - 解析 LaTex 作者标签以提取作者姓名

python - Python set([]) 如何检查两个对象是否相等?一个对象需要定义哪些方法来自定义它?

python - 从图像中选择区域,但可调整大小的 QRubberBand 不会调整大小

python - PyQt:具有撤消和重做操作的 QTableWidget

python - PyQt5 QTextEdit自动补全

python - 事件时对不同字段进行求和 - Odoo v8

python - 使用 PyArrow 读取 CSV

python - 秒表(计时器)不起作用