|
目录
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_NAMESPACE
class 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_OBJECT
public:
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:12345
m_server = new QWebSocketServer(QStringLiteral("QWebChannel Server"), QWebSocketServer::NonSecureMode);
bool isListened = m_server->listen(QHostAddress:ocalHost, 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 SETUP
function 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 globally
window.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" />
</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通讯更适合项目开发。
————————————————
版权声明:本文为CSDN博主「码拉小农虾」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/d_chunyu/article/details/117118058
|
|