Qt嵌入百度地图API的详细流程与常见问题

关于Qt应用嵌入地图这一块 我是好久之前就开始做了,前段时间测试的时候貌似是没问题了,可刚拉出去溜溜时就出BUG了。。。在查找BUG时发现几个坑,不小心的话还是很容易掉进去的,所以想着记录下来和大家分享一下 。关于创建这一块我也没写过,那就从工程建立开始——


0 准备工作

开发环境 Qt5.7 MSVC2015
(我刚开始接触Qt时使用的MinGW版本 后来就是要嵌入网页时发现MinGW好像没有QWebEngine模块)

需要文件

  1. 地图文件 BDMap.html
  2. 连接工具 QwebChannel.js

其中 地图文件是要自己创建,QwebChannel.jsQt自带的,不需要任何修改,拷贝到地图文件同一层目录即可。文章最后会附上项目链接,项目中可以找到。

基本思路

  1. 在界面中要创建网页容器QWebEngineView类,这是Qt提供的继承于QWidget类的一个窗口,使用时在.Pro文件中要添加QT += webenginewidgets
  2. 创建QtHtml的连接通道QWebChannel,通过该通道可以实现主程序与网页之间的通信。当然,通道的建立是双方面的,即主程序和网页中都要进行相关操作;
  3. 通道建立完成即可实现主程序与网页之间的函数相互调用,并由此实现参数传递。

一、工程建立

  1. 建立一个主窗口项目,名称为BDMap,项目文件中添加QT += webenginewidgets,UI界面如下图所示:
    在这里插入图片描述
    其中MapWidgetQWidget提升而成:
    在这里插入图片描述

  2. 右键项目,添加自定义bridge类,继承于QObject,在主窗口中包含头文件并创建对象JSBridge

  3. 在项目文件夹下新建文件夹Baidu_JS,并将BDMap.html
    qwebchannel.js拷贝到该文件夹下;在右击项目添加资源文件map,并将BDMap.htmlqwebchannel.js导入其中。
    (网页文件可以不作为资源使用,不过要指定文件路径,当程序发布到别的电脑使用时,网页文件要跟着拷贝过去)
    至此,项目创建完成,文件结构为:
    在这里插入图片描述


二、程序编写

  1. bridge类作为主程序和网页之间的"桥梁",起到从网页接收数据的作用。不妨定义一个公有槽函数:

    void bridge::RcvPoint(const QString &lng, const QString &lat)
    {
        qDebug()<<lng<<","<<lat;
    }
    

    即接收地图传来的坐标并打印。

  2. 主窗口构造函数:

    MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),
    ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
        bridge *JSBridge = new bridge(this);
        QWebChannel *channel = new QWebChannel(this);
        channel->registerObject("window",(QObject*)JSBridge);
        ui->MapWidget->page()->setWebChannel(channel);
        ui->MapWidget->page()->load(QUrl("qrc:/Baidu_JS/BDMap.html"));
    }
    

    首先分别创建bridge对象JSBridgeQWebChannel对象channel

    然后将JSBridge注册到channel中,注册的名称为window

    接着将通道channel添加到网页中

    最后加载网页,完成。

3.主窗口通道建立完成之后就轮到html中了。在html中首先加载qwebchannel.js工具。

<script src="qwebchannel.js"></script>

(再说一遍,.js要和.html放在同一层目录)
接着,可以在地图初始化完成后创建通道:

 new QWebChannel(qt.webChannelTransport, function(channel) {
     mw = channel.objects.window;
 });	

这样,就可以在后面调用我们在bridge中定义的槽函数RcvPoint(lng,lat)了:

mp.addEventListener("click",function(e){
        mw.RcvPoint(e.point.lng,e.point.lat);
 });

BDMap.html:

<!DOCTYPE html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>CanvasLayer</title>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的密钥"></script>
<style type="text/css">
body, html,#container {width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}
</style>
</head>
<body>
    <div id="container"></div>
</body>
</html>
<script src="qwebchannel.js"></script>
<script type="text/javascript">
    var mp = new BMap.Map("container");
    mp.centerAndZoom(new BMap.Point(116.3964,39.9093), 10);
    mp.enableScrollWheelZoom();
    new QWebChannel(qt.webChannelTransport, function(c) {
        mw = c.objects.window;
    });
    mp.addEventListener("click",function(e){
        mw.RcvPoint(e.point.lng,e.point.lat);
    });
</script>


有话要说
1).

    new QWebChannel(qt.webChannelTransport, function(channel) {
        mw = channel.objects.window;
    });	

对于这样一个函数,里面哪些是可变的?
首先,这个函数名是qwebchannel.js内置的,不可变,第一个参数为channel的类型(或者说作用)是Qt端到Web端的一个转换通道;第二个参数是一个自定义的function(),他的参数channel是可变的,甚至可以用 c来代替,但要和下一行的保持一致;
mw相当于一个全局变量,但也是我自定义的,甚至可以用b来代替;
再看等号右边,channel说过了,要和上边一致;右边的objects是他的一个元素,不可变;再右边的window也是自定义的,但要和主窗口中JSBridge的注册名一致。

2).这样说可能还是有点不明白,但再举一个例子就清楚多了:
比如说有两个变量 int a =5;int b = 10;,现在要把这两个数交换一下应该怎么做?——最简单的做法是再定义一个int c;,然后

   c = a;
   a = b;
   b = c;

在这里边,c就是一个中间变量,只起到一个传递作用,就相当于上边的channel,我们在主程序中创建一个bridge对象,然后要注册到channel中,说白了就是在channel中保存为window(注册名),然后到html中将保存的对象取出来赋给mw,所以这个mw就是主程序中的bridge!

三、功能完善

  1. 把收到的坐标在主界面的显示框中显示
    (这部分涉及的就是不同类之间的通信问题了,我之前在多线程的创建中有详细讲过)
    一句话们就是通过信号和槽传参——bridge在接收到经纬度之后,发出一个以经纬度为参数的信号,主窗口响应信号,并在槽函数中进行显示。
  2. 在主界面的输入框中输入坐标,点击发送,地图定位到该点
    首先在html中定义一个以经纬度为参数的定位函数
    function SetPoint(lng,lat){
        mp.setCenter(new BMap.Point(lng,lat));
     }
    
    在主程序中使用runJavaScript(cmd)调用html中的函数:
     void MainWindow::on_pushButton_clicked()
     {
         QString context = ui->lineEdit_SendMsg->text();
         if(!context.contains(','))
         {
             qDebug()<<"输入格式错误";        //输入格式 经度+纬度,中间以英文逗号‘,’隔开
             return;
         }
         QString lng = context.split(',').at(0);
         QString lat = context.split(',').at(1);
     
         ui->MapWidget->page()->runJavaScript(QString("SetPoint(%1,%2)").arg(lng).arg(lat));
     }
    
    至此,项目功能基本实现。地图实现更深层功能需参照百度地图API示例百度地图JSAPI3.0参考类这两个网站。

四、排坑工作

好吧,终于到这一步了,我是真没想到前面写这么多的,过了这么久才步入正题。。。

先说说遇到的第一个问题
如果在主窗口的构造函数中直接加载地图,当电脑没有网络时,控制台会持续报错:
在这里插入图片描述
当然这些错误仅仅在控制台中显示,而且除了地图模块外其他功能不会受影响,但我有强迫症——在地图初始化之前先检测一下有没有网络连接,如果没联网就不初始化地图,这下果然没有报错了;但是有一天不小心点到了地图页面某个按钮,程序都没报错,直接没了!
后来通过Debug发现出错位置在ui->MapWidget->page()->runJavaScript()那里,才恍然大悟,**地图功能都没初始化,我却在按钮槽函数调用了html中的函数。。。**赶紧改,面前有两个选择,一是去掉网络检测;二是设置一个标志位,在每次调用runJavaScript()之前检测一下标志位,。。。。我当然选择了后者

这个问题解决了,但是新的问题又出现了——如果刚运行程序时电脑没联网,地图没初始化,中间电脑网络恢复了 我又想用地图这怎么办?重启软件——这肯定不是一个好方法,稍微好一点的方法是设一个按钮重新调用一下地图初始化函数,OK,这个问题貌似也解决了,但是——这个初始化函数只能运行一次,实测的结果是当多次运行地图加载函数ui->MapWidget->page()->load(..)时,程序会出错,控制台会持续报错

[08:53:20:219]  Uncaught TypeError: Cannot read property 'x' of undefined          qrc:/Baidu_JS/my_sample.html _line1

而且地图无法正常使用,卡顿,缩放时坐标跟着偏移,无法绘制轨迹等等,具体是什么什么原因我暂时也没搞明白。

还有一个问题 当主程序有多个坐标需要上传到地图进行标注或者绘制时,一般采用for循环的方式,感觉上没什么问题,但实际操作时会发现如果坐标不止一个,在地图上绘制的顺序是错乱的,而且有可能发过去十个坐标只标注出来五六个,一开始以为是绘制的问题,直到将发送的顺序与接收的顺序对比一下才发现接收的顺序就是错乱的,而且有丢失。这主要是因为 主程中发送相邻两个坐标的时间间隔都是在1ms之内,也就几百us,但是在地图上标注,绘制所花的时间远远大于1ms,大概是几十ms的样子,所以说如果地图端采用“接收一个、绘制一个”这样的方式的话,就会出现 第一个坐标还没处理完,接下来又接收到了好几个坐标,那这几个点来不及处理肯定就丢失了。这个问题最终采用的是信号与槽的方式解决,即主程序发送一个坐标后,地图端开始处理,等到处理完给主程序返回一个信号,主程序收到该信号后再发送下一个,这样以此类推直到最后一个
END


PS:在Qt中调试JS程序时,不像直接在Chrome浏览器调试一样,想看的信息可以直接console.log()一下,采用alert()吧又是阻塞的,有时候想看一下某个数据很难,这时候可以在bridge类中专门定义一个函数用于打印数据:

void bridge::printMessage(QString msg1, QString msg2)
{
    qDebug()<<"JS_PRINT:"<<msg1<<"   "<<msg2<<;
}

当在html中想看某个变量的值时,直接调用

mw.printMessage(data1,data2);

就可以在Qt的控制台看到了,而且显示几个参数完全可以自定义,调试时很方便。


项目链接:https://github.com/Mr-Yslf/BlogResources.git

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值