02. QCefView + QWebChannel 实现Qt程序中嵌入Web页面

3 篇文章 1 订阅
2 篇文章 0 订阅

目录

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::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 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" 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通讯更适合项目开发。

 

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
PyCharm 2020.1是集成开发环境(IDE),主要用于编写和调试Python代码。它具有许多强大的功能,例如代码自动补全、调试器、版本控制集成等,使开发人员更加高效地编写代码。 PyQt是一个用于创建图形用户界面(GUI)应用程序的Python库。它是对Qt应用程序框架的Python绑定,提供了丰富的界面组件和功能。通过PyQt,开发人员可以轻松地创建跨平台的GUI应用程序Qt Designer是Qt开发工具包的一部分,它是一个可视化设计工具,用于创建Qt应用程序的用户界面。它提供了丰富的界面组件和布局选项,开发人员可以通过拖放和设置属性来设计界面。Qt Designer还可以将设计的界面转换为Python代码,以便在PyQt使用。 在PyCharm 2020.1使用PyQtQt Designer的案例可以是创建一个简单的GUI应用程序。首先,我们可以在PyCharm创建一个新的PyQt项目,然后使用Qt Designer来设计应用程序的界面。在Qt Designer,我们可以添加按钮、标签、文本框等界面组件,并设置它们的属性和布局。 然后,我们可以将设计好的界面保存为.ui文件,并将其转换为Python代码。在PyCharm,我们可以使用PyQt的工具来将.ui文件转换为.py文件,并在代码导入生成的Python模块。 接下来,我们可以在PyCharm编写代码来处理界面组件的事件,例如按钮的点击事件。通过PyQt提供的信号与槽机制,我们可以连接界面组件的信号和事件处理函数,以实现交互逻辑。 最后,我们可以在PyCharm运行项目,测试和调试应用程序的功能。PyCharm提供了调试器和代码分析工具,帮助我们找出潜在的错误并进行修复。 总之,使用PyCharm 2020.1、PyQtQt Designer,我们可以方便地开发跨平台的GUI应用程序,通过可视化设计界面和编写Python代码,使应用程序更加易于维护和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值