qt使用高德地图并与之简单交互

    技术2023-06-17  104

    通过QWebEngine加载高德地图,并通过QWebChannel与之交互。以自动补全(输入提示)功能为例演示如何使用高德JS API。

    文章目录

    项目代码环境效果演示拖拽设置城市自动补全(输入提示) 准备工作使用QWebEngine显示地图HTML地图HTML的编写mymap.html 使用QWebChannel与地图HTML交互HTML及交互类的编写mymap_ba.htmlmyChannel.hmyChannel.cpp 其余代码文件mainwindow.hmainwindow.cppmain.cpp 使用chrome浏览器进行远程调试调试效果演示 写在最后

    项目代码

    本测试项目的qt工程已完整上传至github

    环境

    QT 5.10.1 Qt Creator 4.5.1(MSVC2017 64bit) Chrome 版本 79.0.3945.117(高于80版本的chrome目前无法远程调试QWebEngine中的html)

    效果演示

    拖拽

    这个其实就是创建地图,不需要额外代码

    设置城市

    自动补全(输入提示)

    准备工作

    在高德开放平台注册账号并申请key。 这个key具体做什么用官方也没过多解释,似乎主要是用来限制散户开发者调用其接口的次数,不正确的key值会导致调用接口失败(仅仅是创建并显示地图倒是不需要key)。申请web应用的key方法很简单,也没什么门槛,按照页面说明,服务平台选择Web端(JS API)即可。

    使用QWebEngine显示地图HTML

    QWebEngine约等于程序中封装了一个chrome浏览器,会导致程序体积比较大。 在QWebEngineView中显示HTML页面跟使用浏览器打开HTML非常的类似,页面部分的开发和qt程序主体的开发还是比较独立的,最终通过QWebChannel进行交互,文章后续会有补充。

    有关HTML和JavaScript的基础知识可以去W3CSchool中学习。 HTML系列教程 JavaScript教程

    关于QWebEngine的使用,可以参考这篇文章 Qt嵌入浏览器(一)——QWebEngineView实现浏览器基本功能

    地图HTML的编写

    页面部分的编写和功能API的使用主要就要参考高德官方的API介绍和例子了。 基本上可以在示例中心找到你想使用的功能和教程代码。 以地图的创建为例,将源代码编辑器中的内容复制下来保存为一个html文件,注意替换一下key值部分即可。

    mymap.html

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"> <link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" /> <title>地图显示</title> <style> html, body, #container { width: 100%; height: 100%; } </style> </head> <body> <div id="container"></div> <!-- 加载地图JSAPI脚本 --> <script src="https://webapi.amap.com/maps?v=1.4.15&key=您申请的key值"></script> <script> var map = new AMap.Map('container', { resizeEnable: true, //是否监控地图容器尺寸变化 zoom:11, //初始化地图层级 center: [116.397428, 39.90923] //初始化地图中心点 }); </script> </body> </html>

    剩下的就是使用QWebEngineView加载此页面了,我在ui文件中设置了QWebEngineView,只需在相关类中令QWebEngineView加载这个html即可。

    ui->webengineview->load(QUrl("你的路径/mymap.html"));

    如果并不需要Qt主体程序对地图信息做额外的处理,那么功能基本都可以在网页开发中完成(毕竟本身使用的就是Web开发的API,功能其实很全面),否则就需要qt能够跟html进行交互了。

    使用QWebChannel与地图HTML交互

    关于QWebChannel的使用,可以参考以下文章 最清晰Qt与JS通过qwebchannel交互例子 其中qwebchannel.js可以在 QT安装目录/Examples/Qt-5.10.1/webchannel/shared内找到。

    这里我再强调一下,使用QWebChannel时,用来向JS注册的类应该额外编写一个继承自QObject的类,使其只包含交互必要的属性和方法,否则虽然也可以编译运行,但是会在控制台出现很多类似Property xxxx of object xxxx has no notify signal and is not constant…的信息。意思大概是xxxx类的xxxx属性没有通知信号,当属性值在HTML中被修改时qt将无法更新属性值。 可以认为QWebChannel会默认所注册的类中的所有属性都需要进行交互,对其是否有合适的信号和槽函数进行了检测并给出了警告。从这个角度来说也应该让负责交互的类只包含必要的内容,否则可能会在交互时传递很多无用的信息增加程序负担。

    HTML及交互类的编写

    以输入提示功能为例,我们获取一个功能API的方法信息主要有三个途径:

    教程示例中心参考手册

    通常综合参考三个部分即可了解一个API能完成哪些任务以及使用方法。 交互过程中涉及信息的传递时,基本可以通过字符串和JSON来解决。Qt5已经自带了JSON的支持,Qt的槽函数可以使用QString类型的参数接收字符串信息,使用QJsonObject类型的参数接收JS对象的信息。

    mymap_ba.html

    <!doctype html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width"> <link rel="stylesheet" href="https://cache.amap.com/lbs/static/main1119.css"/> <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=您申请的key值"></script> <script type="text/javascript" src="https://cache.amap.com/lbs/static/addToolbar.js"></script> </head> <body> <div id="container"></div> <script src="qwebchannel.js"></script> <script type="text/javascript"> var map = new AMap.Map("container", { resizeEnable: true, zoom:11, //初始化地图层级 center: [116.397428, 39.90923] //初始化地图中心点 }); AMap.plugin(["AMap.Autocomplete"], function() { //构造地点查询类 }); var autoOptions = { //city 限定城市,默认全国 city: '北京' } var autoComplete= new AMap.Autocomplete(autoOptions); //关键字查询 //placeSearch.search('北京大学'); var mchannel; window.onload=function(){ if (typeof qt != 'undefined') { new QWebChannel(qt.webChannelTransport, function(channel) { channel.objects.qtChannel.cityChanged.connect(testSetCity); channel.objects.qtChannel.inputChanged.connect(dealComplete); mchannel=channel; getCurCity(); } ); } else { alert("qt对象获取失败!"); } } function getCurCity()//用来获取当前地图中心点所在城市 { map.getCity(function(place){ if(place.city=="") { autoComplete.setCity(place.province); mchannel.objects.qtChannel.cityChangeResult(place.province); }else{ autoComplete.setCity(place.city); mchannel.objects.qtChannel.cityChangeResult(place.city); } }); } function testSetCity(city)//用来设置地图显示以及设置"输入提示"功能的城市限制 { console.log(city); map.setCity(city,function(){ getCurCity(); }); } function dealComplete(cont)//用来将自动补全API返回的结果传给qt做进一步处理 { console.log(cont); autoComplete.search(cont, function(status, result) { // 搜索成功时,result即是对应的匹配数据 //mchannel.objects.qtChannel.getAutocomplete(JSON.stringify(result)); console.log(result); mchannel.objects.qtChannel.getAutocomplete(result); }) } </script> </body> </html>

    myChannel.h

    #ifndef __MYCHANNEL_H__ #define __MYCHANNEL_H__ #include <QWebChannel> #include <QJsonObject> class myChannel :public QObject { Q_OBJECT public: explicit myChannel(QObject* parent=nullptr); void setCity(QString city); public slots: void getAutocomplete(QJsonObject result);//html--> myChannel void cityChangeResult(QString result);//html--> myChannel signals: void cityChanged(QString city);//myChannel --> html void inputChanged(QString input);//myChannel --> html void setCityLable(QString city);// myChannel --> mainwindow void sendAutocomplete(QJsonObject autocom);//myChannel --> mainwindow }; #endif

    myChannel.cpp

    #include "myChannel.h" myChannel::myChannel(QObject *parent) :QObject(parent) { } void myChannel::cityChangeResult(QString result) { emit setCityLable(result); } void myChannel::getAutocomplete(QJsonObject result) { emit sendAutocomplete(result); } void myChannel::setCity(QString city) { emit cityChanged(city); }

    在QWebEngineView相关类中添加如下代码即可使用,完整的代码文件下文会给出

    myChannel* _myChannel = new myChannel(this); QWebChannel* web_channel = new QWebChannel(this); web_channel->registerObject("qtChannel",_myChannel);

    其中 web_channel->registerObject("qtChannel",_myChannel);用来最终向JS注册想要交互的类,"qtChannel"相当于一个标识,在js中就以此名称来代表这个类。

    channel.objects.qtChannel.cityChanged.connect(testSetCity); 这种形式相当于将myChannel中的cityChanged信号与JS中的testSetCity函数连接。

    mchannel.objects.qtChannel.getAutocomplete(result); 这种形式相当于在JS中调用了myChannel中的getAutocomplete槽函数。

    注意以上两行代码中起始的channel和mchannel是变量而非固定名称。

    其余代码文件

    在.pro文件中需要添加相应的模块 QT += core gui webenginewidgets webchannel 处理JSON时使用了C++11的特性所以再加上 CONFIG += c++11

    mainwindow.h

    #ifndef MAINWINDOW_H #define MAINWINDOW_H #include "myChannel.h" #include <QMainWindow> #include <QStandardItemModel> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); void setCity(); void setAutoComplete(QJsonObject result); void searhInputChanged(); private: Ui::MainWindow *ui; myChannel * _myChannel; QStandardItemModel * _mItemModel; }; #endif // MAINWINDOW_H

    mainwindow.cpp

    #include "mainwindow.h" #include "ui_mainwindow.h" #include <iostream> #include <QWebEngineView> #include <QWebChannel> #include <QMessageBox> #include <QJsonObject> #include <QJsonArray> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "7777"); _myChannel = new myChannel(this); _mItemModel =new QStandardItemModel(this); QWebChannel* web_channel = new QWebChannel(this); web_channel->registerObject("qtChannel",_myChannel); ui->webengineview->page()->setWebChannel(web_channel); ui->webengineview->load(QUrl("qrc:/smap/mymap_ba.html")); ui->listView->setModel(_mItemModel); QObject::connect(ui->pushButton_setcity, &QPushButton::clicked, this, &MainWindow::setCity); QObject::connect(_myChannel, &myChannel::setCityLable, [this](QString city) { ui->label_city->setText(QString::fromLocal8Bit("city:") + city); }); QObject::connect(_myChannel,&myChannel::sendAutocomplete,this,&MainWindow::setAutoComplete); QObject::connect(ui->lineEdit_search,&QLineEdit::textEdited,this,&MainWindow::searhInputChanged); } MainWindow::~MainWindow() { delete ui; } void MainWindow::setCity() { QString city = ui->lineEdit_setcity->text().trimmed(); if (city.size() == 0) { QMessageBox::warning(this,"Warning","please enter a city!"); return; } _myChannel->setCity(city); } void MainWindow::setAutoComplete(QJsonObject result) { std::cout<<"in setAuto"<<std::endl; _mItemModel->clear(); if(!(result.contains("tips")&&result["tips"].isArray())) { return; } for(auto e:result["tips"].toArray()) { QJsonObject d=e.toObject(); if(d.contains("name")) { QString s=d["name"].toString(); if(s.size()>0) { QStandardItem * item=new QStandardItem(s); _mItemModel->appendRow(item); std::cout<<s.toStdString()<<std::endl; } } } } void MainWindow::searhInputChanged() { QString cont = ui->lineEdit_search->text().trimmed(); _myChannel->inputChanged(cont); }

    main.cpp

    #include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }

    代码文件均已贴出,完整的qt项目可以在文章起始的github链接处获取

    使用chrome浏览器进行远程调试

    开发过程中不可避免的会出现错误,由于页面端对错误的处理方式有一些不同,当html中出现编写错误时程序往往仍然可以顺利编译运行,但是页面加载会出现问题,不专门进行调试很难发现错误发生的位置,直接使用浏览器打开html又无法获得与qt有关的类。好在Qt提供了一种可以像网页开发一样对QWebEngineView中的页面进行调试的方法。 在程序中添加

    qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "7777");

    其中“7777”为端口号,你可以定义为其他你想使用的端口,在程序运行时,就会看到控制台输出

    Remote debugging server started successfully. Try pointing a Chromium-based browser to http://127.0.0.1:7777 此时就可以在浏览器中打开此网址进行调试了

    调试效果演示

    写在最后

    本来想写一篇详细的图文教程,这个测试程序代码总量很小,但是写的过程中感觉杂糅了很多分散的知识点,对于每个具体的知识点都有别人写的不错的具体教程,本文均以链接的形式给出了。将他们综合在一起时,难以拿捏何处应该展开细说,以及说到什么程度才算清楚透彻,最后基本是以大段代码的形式呈现,部分我认为的重点给了些解释和说明。 如果发现文章有错误或者觉得哪里应该写得更详细些欢迎留言评论。

    Processed: 0.017, SQL: 9