02. QCefView + QWebChannel 实现Qt程序中嵌入Web页面
目录
1. 为什么要用QCefView + QWebChannel开发?
2. 自定义WebChannel
3. 示例完整代码
3.1 自定义Transport类
3.2 自定义channel
3.3 交互窗体、注册对象及通讯
3.4 Html页面如下:
4. 运行结果
5. 调试
6. QWebEngine vs QCefView + QWebChannel vs QCefView 对比
1. 为什么要用QCefView + QWebChannel开发?
基于Qt自带的QWebEngineView + QWebChannel开发,常规电脑可以满足需求。但实际项目部署时在个别环境会有兼容性问题,导致有些电脑莫名崩溃,或者出现web加载失败,卡死的情况。
如果系统自带的.netframe版本过低,QWebengineView 编译的程序在windows7无法运行;
网上查相关资料,有部分介绍如下:
(1)QWebEngineView在运行之前需要检查本地硬件环境,硬件必须要支持OpenGL2.0以上的版本,否则无法运行。
(2)机器的显卡和系统所带的显卡驱动不匹配,导致QtWebEngine在渲染时出现了崩溃。用户需要手动更新显卡驱动来解决这个问题。
而QCefView 基于CEF的封装,对硬件要求低,性能好(XP、windowNT和其他Unix、MacOS都可以支持),在显示上规避了QWebEngineView的问题。
对于QCefView的通讯机制,网上有人说当QCefView整合Vue项目时,会有QCefClient找不到的问题,我也本地Demo了一下,这里没有出现问题,即QCefView直接整合Vue项目也是通过的。
但QCefView和WebChannel的通讯调用方式不太一样,还是可以尝试下不同的组合,再根据喜好做个选择。
而且本人感觉,WebChannel方式,调试起来会更方便。
以下是基于QCefView + QWebChannel 的开发方法。
2. 自定义WebChannel
基本原理是通过channel将C++对象暴露给HTML,在HTML中调用qwebchannel.js。 前提是建立transport,QT只提供了一个抽象基类QWebChannelAbstractTransport,需要自己进行实现,官方建议用QWebSocket实现,并给出了实例。
1、实现Transport类,内置一个WebSocket套接字;
2、实现新的channel类,内置一个WebSocketServer;
3、利用新的channel注册C++对象,从而HTML可以使用该对象;
之后使用跟QWebChannel相同.
3. 示例完整代码
3.1 自定义Transport类
websockettransport.h
#ifndef WEBSOCKETTRANSPORT_H
#define WEBSOCKETTRANSPORT_H#include <QWebChannelAbstractTransport>QT_BEGIN_NAMESPACE
class QWebSocket;
QT_END_NAMESPACEclass WebSocketTransport : public QWebChannelAbstractTransport
{Q_OBJECT
public:explicit WebSocketTransport(QWebSocket * socket);virtual ~WebSocketTransport();void sendMessage(const QJsonObject & message) override;public slots:void textMessageReceived(const QString & message);private:QWebSocket * m_socket;
};#endif // WEBSOCKETTRANSPORT_H
websockettransport.cpp
#include "websockettransport.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QWebSocket>/*!Construct the transport object and wrap the given socket.The socket is also set as the parent of the transport object.
*/
WebSocketTransport::WebSocketTransport(QWebSocket * socket): QWebChannelAbstractTransport(socket), m_socket(socket)
{connect(socket, &QWebSocket::textMessageReceived,this, &WebSocketTransport::textMessageReceived);connect(socket, &QWebSocket::disconnected,this, &WebSocketTransport::deleteLater);
}/*!Destroys the WebSocketTransport.
*/
WebSocketTransport::~WebSocketTransport()
{m_socket->deleteLater();
}/*!Serialize the JSON message and send it as a text message via the WebSocket to the client.
*/
void WebSocketTransport::sendMessage(const QJsonObject & message)
{QJsonDocument doc(message);m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
}/*!Deserialize the stringified JSON messageData and emit messageReceived.
*/
void WebSocketTransport::textMessageReceived(const QString & messageData)
{QJsonParseError error;QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);if (error.error) {qWarning() << "Failed to parse text message as JSON object:" << messageData << "Error is:" << error.errorString();return;}else if (!message.isObject()) {qWarning() << "Received JSON message that is not an object: " << messageData;return;}emit messageReceived(message.object(), this);
}
3.2 自定义channel
websocketchannel.h
#ifndef WEBSOCKETCHANNEL_H
#define WEBSOCKETCHANNEL_H#include <QWebChannel>class QWebSocketServer;
class WebSocketTransport;//继承QWebchannel类,在内部建立socket server与transport中socket的连接class WebSocketChannel : public QWebChannel
{Q_OBJECT
public:WebSocketChannel(QWebSocketServer* server);signals:void clientConnected(WebSocketTransport* client);private slots:void handleNewConnection();private:QWebSocketServer* _server;
};#endif // WEBSOCKETCHANNEL_H
websocketchannel.cpp
#include "websocketchannel.h"#include <QWebSocketServer>
#include "websockettransport.h"WebSocketChannel::WebSocketChannel(QWebSocketServer* server):_server(server)
{connect(server, &QWebSocketServer::newConnection,this, &WebSocketChannel::handleNewConnection);connect(this, &WebSocketChannel::clientConnected,this, &WebSocketChannel::connectTo);//connectTo槽继承自QWebchannel
}void WebSocketChannel::handleNewConnection()
{emit clientConnected(new WebSocketTransport(_server->nextPendingConnection()));
}
3.3 交互窗体、注册对象及通讯
testCEF02.h
#pragma once#include <QtWidgets/QWidget>
#include <QTextEdit>
#include <QLineEdit>
#include <QPushButton>
#include <QWebSocketServer>
#include "websocketchannel.h"
#include "QCefView.h"class testCEF02 : public QWidget
{Q_OBJECTpublic:testCEF02(QWidget *parent = Q_NULLPTR);~testCEF02();signals:void sendMessage(const QString& msg); // 用信号向Web发送消息public slots:void btnGoClicked();void btnSendClicked();void receiveMessage(const QString& msg);private:void initView();void initConnect();void initWebSocket();void initWebChannel();void displayMsg(const QString& msg);private:QLineEdit* m_edtWeb;QPushButton* m_btnGo;QCefView* m_cefView;QTextEdit* m_edtText;QLineEdit* m_edtLine;QPushButton* m_btnSend;QWebSocketServer* m_server;WebSocketChannel* m_channel;
};
testCEF02.cpp
#include "testCEF02.h"
#include <QHBoxLayout>
#include <QVBoxLayout>testCEF02::testCEF02(QWidget *parent): QWidget(parent)
{initView();initConnect();initWebSocket();initWebChannel();
}testCEF02::~testCEF02()
{m_channel->deregisterObject(this);delete m_channel;m_server->close();m_server->deleteLater();
}void testCEF02::receiveMessage(const QString& msg)
{displayMsg("Receive Msg: " + msg);
}void testCEF02::btnGoClicked()
{QString strUrl = m_edtWeb->text();QUrl urlCheck(strUrl);if (urlCheck.isValid()){m_cefView->navigateToUrl(strUrl);}
}void testCEF02::btnSendClicked()
{displayMsg("Send Msg: " + m_edtLine->text());emit sendMessage(m_edtLine->text());m_edtLine->clear();
}void testCEF02::initView()
{m_edtWeb = new QLineEdit;m_btnGo = new QPushButton("Go");m_cefView = new QCefView();m_edtWeb->setText("file:///E:/test_code/qt/testCEF02/testCEF02/x64/index.html");m_edtText = new QTextEdit;m_edtLine = new QLineEdit;m_btnSend = new QPushButton("Send");QHBoxLayout* webBarLayout = new QHBoxLayout;webBarLayout->addWidget(m_edtWeb);webBarLayout->addWidget(m_btnGo);QVBoxLayout* webLayout = new QVBoxLayout;webLayout->addLayout(webBarLayout);webLayout->addWidget(m_cefView);QHBoxLayout* sendBarLayout = new QHBoxLayout;sendBarLayout->addWidget(m_edtLine);sendBarLayout->addWidget(m_btnSend);QVBoxLayout* qtLayout = new QVBoxLayout;qtLayout->addWidget(m_edtText);qtLayout->addLayout(sendBarLayout);QVBoxLayout* layout = new QVBoxLayout;layout->addLayout(webBarLayout, 1);layout->addLayout(webLayout, 2);layout->addLayout(qtLayout, 2);setLayout(layout);
}void testCEF02::initConnect()
{connect(m_btnGo, SIGNAL(clicked()), this, SLOT(btnGoClicked()));connect(m_btnSend, SIGNAL(clicked()), this, SLOT(btnSendClicked()));
}void testCEF02::initWebSocket()
{//建立QWebSocketServer,url是ws://localhost:12345m_server = new QWebSocketServer(QStringLiteral("QWebChannel Server"), QWebSocketServer::NonSecureMode);bool isListened = m_server->listen(QHostAddress::LocalHost, 12345);if (!isListened){qDebug() << "Failed to open web socket server.";}
}void testCEF02::initWebChannel()
{m_channel = new WebSocketChannel(m_server);m_channel->registerObject(QStringLiteral("qtClient"), this);
}void testCEF02::displayMsg(const QString& msg)
{m_edtText->setText(m_edtText->toPlainText() + msg + '\n');
}
main函数默认不变,如下:
#include "testCEF02.h"
#include <QtWidgets/QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);testCEF02 w;w.show();return a.exec();
}
3.4 Html页面如下:
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><script type="text/javascript" src="./qwebchannel.js"></script><script type="text/javascript">//BEGIN SETUPfunction output(message) {var output = document.getElementById("output");output.innerHTML = output.innerHTML + message + "\n";}window.onload = function() {var baseUrl = "ws://localhost:12345";output("Connecting to WebSocket server at " + baseUrl + ".");var socket = new WebSocket(baseUrl);socket.onclose = function() {console.error("web channel closed");};socket.onerror = function(error) {console.error("web channel error: " + error);};socket.onopen = function() {output("WebSocket connected, setting up QWebChannel.");new QWebChannel(socket, function(channel) {// make qtClient object accessible globallywindow.qtClient = channel.objects.qtClient;document.getElementById("send").onclick = function() {var input = document.getElementById("input");var text = input.value;if (!text) {return;}output("Sent message: " + text);input.value = "";//调用C++公有槽函数qtClient.receiveMessage(text);}//连接C++信号与javascript函数qtClient.sendMessage.connect(function(message) {output("Received message: " + message);});qtClient.receiveMessage("Client connected, ready to send/receive messages!");output("Connected to WebChannel, ready to send/receive messages!");});}}//END SETUP</script><style type="text/css">html {height: 100%;width: 100%;}#input {width: 400px;margin: 0 10px 0 0;}#send {width: 90px;margin: 0;}#output {width: 500px;height: 300px;}body {background-color: #FAEBD7;}</style></head><body><h4>这是Web界面</h4><textarea id="output"></textarea><br /><input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" /></body>
</html>
4. 运行结果
5. 调试
这种方式调试方式更加灵活。
直接打开浏览器输入对应地址,也能够跟QT客户端进行通讯,
相比QWebEngineView方式方便很多。
截图如下:
6. QWebEngine vs QCefView + QWebChannel vs QCefView 对比
对比项 |
显示 |
通讯 |
兼容性 |
调试 |
与前端集成 |
资料 |
QWebEngine |
支持 |
支持 |
少数电脑较差 |
一般(除控制台输出外,支持浏览器调试,但需要降级chrome版本) |
支持 |
多 |
QCefView + QWebChannel |
支持 |
支持 |
好 |
好(可直接用浏览器调试) |
支持 |
中 |
QCefView |
支持 |
支持 |
好 |
差(只有控制台输出,浏览器调试不了) |
支持 (网上有资料说不支持,但本地尝试没有问题) |
少 |
综上,这几种方案中,QCefView + QWebChannel最稳定合适
PS. 单比QCefView与QWebEngine,QCefView在显示,加载和通讯上,支持的操作都比QWebEngine要丰富一些,灵活度更高,但是一般情况下,客户端集成很使用,所以整体对比,从客户端集成角度看,QCefView显示+QWebChannel通讯更适合项目开发。
02. QCefView + QWebChannel 实现Qt程序中嵌入Web页面相关推荐
- Qt中嵌入web网页的几种实现方式
1.背景 Web网页的界面交互相比较Qt客户端而言有着比较大的优势:更加的多样化和更高的使用便捷性使得我们即使在客户端中也可以考虑将web网页嵌入到客户端的界面当中.如此便能将web的优势和客户端进行 ...
- 在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎
前些天听到一个需求:某业务方需要我们帮忙清理用户电脑上的一些废弃文件.同事完成这个逻辑的方案便是在我们程序中加入了一个很"独立"的业务逻辑:检索和删除某个程序产生的废弃文件.试想, ...
- 在windows程序中嵌入Lua脚本引擎--编写自己的Lua库
在<在windows程序中嵌入Lua脚本引擎--建立一个简易的"云命令"执行的系统>一文中,我提到了使用Lua的ffi库,可以让我们像写C代码一样写lua程序.这是个非 ...
- 在windows程序中嵌入Lua脚本引擎--建立一个简易的“云命令”执行的系统
在<在windows程序中嵌入Lua脚本引擎--使用VS IDE编译Luajit脚本引擎>开始处,我提到某公司被指责使用"云命令"暗杀一些软件.本文将讲述如何去模拟一个 ...
- 在WinForm应用程序中嵌入WPF控件(转)
我们知道,在WPF界面上添加WinForm的控件需要使用WindowsFormHost类.而在WinForm界面上添加WPF控件该如何做呢?有没有类似的类呢?明显是有的,ElementHost就是 ...
- 在JavaFX程序中嵌入Swing内容
转载自 在JavaFX程序中嵌入Swing内容 本教程描述如何在JavaFX应用程序中嵌入Swing组件.本文将讨论线程限制并提供一个可运行的应用程序来说明在JavaFX应用程序中嵌入带HTML内容 ...
- Matplotlib画图教程:在QT界面中嵌入三维图片
Matplotlib画图教程:在QT界面中嵌入三维图片 需求: 做项目报告的时候,有这么一个想法,就是能通过UI随时调用matplotlib进行二维图和三维图的绘制.因此就诞生了做这么一个小模块的想法 ...
- 在python程序中嵌入浏览器_用Python中的wxPython实现最基本的浏览器功能
通常,大多数应用程序通过保持 HTML 简单来解决大多数浏览器问题 ― 或者说,根据最低共同特性来编写.然而,即便如此,也仍然存在字体和布局的问题,发行新浏览器和升级现有浏览器时,也免不了测试应用程序 ...
- php 嵌入手机百度地图,C# 程序中嵌入百度地图
本例是对WinForm中使用百度地图的简要介绍.百度地图目前支持Android开发,IOS开发,Web开发,服务接口,具体可以参照'百度地图开放平台'. [动态加载百度地图]涉及到的知识点:WebBr ...
最新文章
- 一次失败的尝试:paxosstore示例编译
- 解决Layui数据表格无数据最后列被顶出去的问题
- linux sheel script demo
- python制作一个教学网站_小白如何入门Python? 制作一个网站为例
- 5G 网络接口与基础流程
- Nokia手机短信发件人显示乱码问题解决
- CentOS中vsftp安装、配置、卸载
- 转: ant condition使用
- 天正电气html帮助,天正电气绘图经验心得
- libc.so.6: version 'GLIBC_2.14' not found报错提示的解决方案
- mysql pxc集群 原理 (图解+秒懂+史上最全)
- Macbook PS快捷键
- 10.12 SNK中国 游戏客户端 二面40min
- openstack 资源超分
- json.dumps()、json.laods()、demjson相关
- QT根据屏幕分辨率调整控件
- 第六节 交叉分析和非参数检验
- python十六进制转为二进制数_python进制转换(二进制、十进制和十六进制)及注意事项...
- 网格交易法是什么意思,外汇投资网格交易法怎么操作?
- Juc07_乐观锁和悲观锁、公平锁和非公平锁、递归锁(可重入锁)、死锁及排查、自旋锁
热门文章
- WPF随笔(六)--查看网络图片
- Crack EditPlus
- UPS 精密空调等默认设置密码持续收录中......
- 1、Ubuntu下安装软件报错
- 成都卫星地图 高清最高级别19级 含标签数据信息叠加
- Linux下su命令的实现
- 计算机音乐数字乐谱fade,Faded钢琴简谱-数字双手-Alan Walker
- c++ inet_pton和inet_ntop在windows下用不了咋办?
- 桌面图标无缘无故变成白色了,咋整?
- 问一下matlab里ARIMA模型咋预测