| 
 | 
	
 
目录 
 
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 
 
 |   
 
 
 
 |