You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyQt5的QTabWidget中无布局嵌入Folium地图?

问题:在PyQt5的QTabWidget中嵌入Folium地图(无需使用布局)

我是PyQt5与Python新手,希望在名为tabContainer1的QTabWidget组件中嵌入Folium地图,且完全不使用任何布局(需移除代码中标记为avoid1、avoid2的QVBoxLayout相关代码)。我已拥有mapTezt.ui界面设计文件,经多方搜索未找到可行方案,特此求助。当前代码如下:

class WebEnginePage(QWebEnginePage):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent

    def javaScriptConsoleMessage(self, level, msg, line, sourceID):
        print(msg)  # Check js errors
        if 'coordinates' in msg:
            self.parent.handleConsoleMessage(msg)


class FoliumDisplay(QDialog):

    def __init__(self,parent=None):
        super().__init__()
        super(FoliumDisplay, self).__init__()
        uic.loadUi("mapTezt.ui", self)
        self.setWindowTitle('B7_Map_view')
        self.setWindowFlags(QtCore.Qt.Window)
        self.window_width, self.window_height = 1200, 800
        self.setMinimumSize(self.window_width, self.window_height)
        layout = QtWidgets.QVBoxLayout() # avoid 1
        self.setLayout(layout)           # avoid 2

        self.tabContainer1=self.findChild(QTabWidget,"tabContainer1") #I want to add folium map inside the tab widget



        coordinate = (8.575589503988725,436.98120117187506)
        m = folium.Map(
            tiles="OpenStreetMap",
            zoom_start=8,
            location=coordinate)

        folium.Marker(location=[8.558954404198643,436.87803268432623],
                            popup='One', icon=folium.Icon(color='purple',prefix='fa',icon='Anchor')).add_to(m)
        folium.Marker(location=[8.908136736259276,436.90841674804693],tooltip="Two" , name="Two").add_to(m)

        A=[8.558954404198643,436.87803268432623]
        B=[8.908136736259276,436.90841674804693]
        folium.PolyLine(locations=[A, B], color='red').add_to(m)
        c1=(A[0]+B[0])/2
        c2 = (A[1] + B[1]) / 2
        angle=90
        folium.RegularPolygonMarker(location=(c1,c2), fill_color='red',color="red", number_of_sides=3, radius=5,
                                    rotation=angle, fill=True).add_to(m)

        # Add Custom JS to folium map
        m = self.add_customjs(m)
        # save map data to data object
        data = io.BytesIO()
        m.save(data, close_file=False)

        webView = QWebEngineView()  # start web engine
        page = WebEnginePage(self)
        webView.setPage(page)
        webView.setHtml(data.getvalue().decode())  # give html of folium map to webengine

        layout.addWidget(webView) # Here, i need map view in tabWidget



    def add_customjs(self, map_object):
        my_js = f"""{map_object.get_name()}.on("click",
                 function (e) {{
                    var data = `{{"coordinates": ${{JSON.stringify(e.latlng)}}}}`;
                    console.log(data)}});"""

        e = Element(my_js)
        html = map_object.get_root()
        html.script.get_root().render()
        # Insert new element or custom JS
        html.script._children[e.get_name()] = e
        return map_object

    def handleConsoleMessage(self, msg):
        data = json.loads(msg)
        lat = data['coordinates']['lat']
        lng = data['coordinates']['lng']
        coords = f"latitude: {lat} longitude: {lng}"
        


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    UIWindow = FoliumDisplay()
    UIWindow.show()

    sys.exit(app.exec_())

解决方案

以下是修改后的代码,核心是移除布局代码,并将WebEngineView直接绑定到QTabWidget的目标tab页,通过手动控制大小实现无布局嵌入:

import sys
import io
import json
from PyQt5 import QtWidgets, QtCore, QtWebEngineWidgets, uic
from folium import Map, Marker, PolyLine, RegularPolygonMarker, Icon
from branca.element import Element

class WebEnginePage(QtWebEngineWidgets.QWebEnginePage):
    def __init__(self, parent):
        super().__init__(parent)
        self.parent = parent

    def javaScriptConsoleMessage(self, level, msg, line, sourceID):
        print(msg)  # 打印JS错误信息
        if 'coordinates' in msg:
            self.parent.handleConsoleMessage(msg)


class FoliumDisplay(QtWidgets.QDialog):

    def __init__(self, parent=None):
        super().__init__(parent)
        uic.loadUi("mapTezt.ui", self)
        self.setWindowTitle('B7_Map_view')
        self.setWindowFlags(QtCore.Qt.Window)
        self.window_width, self.window_height = 1200, 800
        self.setMinimumSize(self.window_width, self.window_height)

        # 移除原布局代码
        # layout = QtWidgets.QVBoxLayout() # avoid 1
        # self.setLayout(layout)           # avoid 2

        self.tabContainer1 = self.findChild(QtWidgets.QTabWidget, "tabContainer1")
        # 假设要把地图放到第一个tab页(索引0),如果是其他tab页修改索引即可
        self.target_tab = self.tabContainer1.widget(0)
        if not self.target_tab:
            # 如果没有现有tab页,新建一个
            self.target_tab = QtWidgets.QWidget()
            self.tabContainer1.addTab(self.target_tab, "地图")

        # 生成Folium地图
        coordinate = (8.575589503988725, 436.98120117187506)
        m = Map(
            tiles="OpenStreetMap",
            zoom_start=8,
            location=coordinate)

        Marker(location=[8.558954404198643, 436.87803268432623],
               popup='One', icon=Icon(color='purple', prefix='fa', icon='Anchor')).add_to(m)
        Marker(location=[8.908136736259276, 436.90841674804693], tooltip="Two", name="Two").add_to(m)

        A = [8.558954404198643, 436.87803268432623]
        B = [8.908136736259276, 436.90841674804693]
        PolyLine(locations=[A, B], color='red').add_to(m)
        c1 = (A[0] + B[0]) / 2
        c2 = (A[1] + B[1]) / 2
        angle = 90
        RegularPolygonMarker(location=(c1, c2), fill_color='red', color="red", number_of_sides=3, radius=5,
                             rotation=angle, fill=True).add_to(m)

        # 添加自定义JS
        m = self.add_customjs(m)
        # 保存地图数据
        data = io.BytesIO()
        m.save(data, close_file=False)

        # 创建WebEngineView并设置为实例变量,方便后续调整大小
        self.webView = QtWebEngineWidgets.QWebEngineView()
        page = WebEnginePage(self)
        self.webView.setPage(page)
        self.webView.setHtml(data.getvalue().decode())

        # 将WebEngineView绑定到目标tab页,不使用布局
        self.webView.setParent(self.target_tab)
        # 初始设置大小为tab页的大小
        self.webView.resize(self.target_tab.size())

    def add_customjs(self, map_object):
        my_js = f"""{map_object.get_name()}.on("click",
                 function (e) {{
                    var data = `{{"coordinates": ${{JSON.stringify(e.latlng)}}}}`;
                    console.log(data)}});"""

        e = Element(my_js)
        html = map_object.get_root()
        html.script.get_root().render()
        html.script._children[e.get_name()] = e
        return map_object

    def handleConsoleMessage(self, msg):
        data = json.loads(msg)
        lat = data['coordinates']['lat']
        lng = data['coordinates']['lng']
        coords = f"latitude: {lat} longitude: {lng}"
        print(coords)  # 可以在这里处理点击坐标

    def resizeEvent(self, event):
        """重写resize事件,确保WebEngineView随tab页大小变化"""
        super().resizeEvent(event)
        if self.target_tab and hasattr(self, 'webView'):
            self.webView.resize(self.target_tab.size())


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

关键修改说明

  1. 移除布局代码:直接删掉原代码中标记为avoid1和avoid2的两行布局相关代码,不再设置全局布局。
  2. 绑定WebEngineView到目标tab:
    • 获取QTabWidget中的目标tab页(如果没有则新建),将WebEngineView的父控件设为该tab页。
    • 初始设置WebEngineView的大小与tab页一致。
  3. 重写resizeEvent:监听窗口大小变化事件,实时调整WebEngineView的大小,确保地图始终铺满整个tab页。
  4. WebEngineView改为实例变量:将webView从局部变量改为self.webView,方便在resizeEvent中访问调整。

内容的提问来源于stack exchange,提问作者user6543831

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:25:32