通过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
: '北京'
}
var autoComplete
= new AMap
.Autocomplete(autoOptions
);
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
)
{
console
.log(cont
);
autoComplete
.search(cont
, function(status
, 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
);
void cityChangeResult(QString result
);
signals
:
void cityChanged(QString city
);
void inputChanged(QString input
);
void setCityLable(QString city
);
void sendAutocomplete(QJsonObject autocom
);
};
#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.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 此时就可以在浏览器中打开此网址进行调试了
调试效果演示
写在最后
本来想写一篇详细的图文教程,这个测试程序代码总量很小,但是写的过程中感觉杂糅了很多分散的知识点,对于每个具体的知识点都有别人写的不错的具体教程,本文均以链接的形式给出了。将他们综合在一起时,难以拿捏何处应该展开细说,以及说到什么程度才算清楚透彻,最后基本是以大段代码的形式呈现,部分我认为的重点给了些解释和说明。 如果发现文章有错误或者觉得哪里应该写得更详细些欢迎留言评论。