Qt封装HTML网页及数据交互(通信)

2023-11-07

看到网上有个关于Qt封装网页的例子,CSDN下载需要50积分(我呸 ....)。其实也是人家Qt官网给的demo,然后改了一丢丢而已。既然这样,那我也实现一下,而且提供源码。

 

【环境】Qt5.13 + MSVC

【要点】Qt GUI 、QWebEngine、QWebEngineView、QWebChannel、JS/HTML

窗体分为两个部分,左侧为QtGUi常用控件,右侧则是通过QWebEngineView加载显示的HTML页面。

其他的不做过多介绍。我们看一下主要代码实现部分。源码下载直接看链接。

// main.cpp

#include "dialog.h"
#include "core.h"
#include "../shared/websocketclientwrapper.h"
#include "../shared/websockettransport.h"

#include <QApplication>
#include <QDesktopServices>
#include <QDialog>
#include <QDir>
#include <QFileInfo>
#include <QUrl>
#include <QWebChannel>
#include <QWebSocketServer>

int main(int argc, char** argv)
{
    QApplication app(argc, argv);

    QFileInfo jsFileInfo(QDir::currentPath() + "/qwebchannel.js");

    if (!jsFileInfo.exists())
        QFile::copy(":/qtwebchannel/qwebchannel.js",jsFileInfo.absoluteFilePath());

    // setup the QWebSocketServer
    QWebSocketServer server(QStringLiteral("QWebChannel Standalone Example Server"), QWebSocketServer::NonSecureMode);
    if (!server.listen(QHostAddress::LocalHost, 12345)) {
        qFatal("Failed to open web socket server.");
        return 1;
    }

    // wrap WebSocket clients in QWebChannelAbstractTransport objects
    WebSocketClientWrapper clientWrapper(&server);

    // setup the channel
    QWebChannel channel;
    QObject::connect(&clientWrapper, &WebSocketClientWrapper::clientConnected,
                     &channel, &QWebChannel::connectTo);

    // setup the UI
    Dialog dialog;

    // setup the core and publish it to the QWebChannel
    Core core(&dialog);
    channel.registerObject(QStringLiteral("core"), &core);

#if 0
    // open a browser window with the client HTML page
    QUrl url = QUrl::fromLocalFile(BUILD_DIR "/index.html");
    QDesktopServices::openUrl(url);
    dialog.displayMessage(Dialog::tr("Initialization complete, opening browser at %1.").arg(url.toDisplayString()));

#endif
    dialog.show();

    return app.exec();
}
// core.h

#ifndef CORE_H
#define CORE_H

#include "dialog.h"
#include <QObject>

/*
    An instance of this class gets published over the WebChannel and is then accessible to HTML clients.
*/
class Core : public QObject
{
    Q_OBJECT

public:
    Core(Dialog *dialog, QObject *parent = nullptr)
        : QObject(parent), m_dialog(dialog)
    {
        connect(dialog, &Dialog::sendText, this, &Core::sendText);
    }

signals:
    /*
        This signal is emitted from the C++ side and the text displayed on the HTML client side.
    */
    void sendText(const QString &text);

public slots:

    /*
        This slot is invoked from the HTML client side and the text displayed on the server side.
    */
    void receiveText(const QString &text)
    {
        m_dialog->displayMessage(Dialog::tr("Received message: %1").arg(text));
    }

private:
    Dialog *m_dialog;
};

#endif // CORE_H
//dialog.h

#ifndef DIALOG_H
#define DIALOG_H

#include <QDialog>

QT_BEGIN_NAMESPACE
namespace Ui {
class Dialog;
}
QT_END_NAMESPACE

class Dialog : public QDialog
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = nullptr);

    void displayMessage(const QString &message);

signals:
    void sendText(const QString &text);

private slots:
    void clicked();

private:
    Ui::Dialog *ui;
};

#endif // DIALOG_H
// dialog.cpp

#include "dialog.h"
#include "ui_dialog.h"
#include <QWebEngineView>
Dialog::Dialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::Dialog)
{
    ui->setupUi(this);
    connect(ui->send, &QPushButton::clicked, this, &Dialog::clicked);

    // 使用Qt控件显示网页
    QWebEngineView *view = new QWebEngineView(ui->widget);
    view->resize(490,490);
    view->load(QUrl(BUILD_DIR "/index.html"));
    view->show();


}

void Dialog::displayMessage(const QString &message)
{
    ui->output->appendPlainText(message);
}

void Dialog::clicked()
{
    const QString text = ui->input->text();

    if (text.isEmpty())
        return;

    emit sendText(text);
    displayMessage(tr("Sent message: %1").arg(text));

    ui->input->clear();
}

 

[index.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() {
                if (location.search != "")
                    var baseUrl = (/[?&]webChannelBaseUrl=([A-Za-z0-9\-:/\.]+)/.exec(location.search)[1]);
                else
                    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 core object accessible globally
                        window.core = channel.objects.core;

                        document.getElementById("send").onclick = function() {
                            var input = document.getElementById("input");
                            var text = input.value;
                            if (!text) {
                                return;
                            }

                            output("Sent message: " + text);
                            input.value = "";
                            core.receiveText(text);
                        }

                        core.sendText.connect(function(message) {
                            output("Received message: " + message);
                        });

                        core.receiveText("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: 300px;
                margin: 0 10px 0 0;
            }
            #send {
                width: 90px;
                margin: 0;
            }
            #output {
                width: 500px;
                height: 300px;
            }
        </style>
    </head>
    <body>
        <textarea id="output"></textarea><br />
        <input id="input" /><input type="submit" id="send" value="Send" onclick="javascript:click();" />
    </body>
</html>

 

[qwebchannel.js]

"use strict";

var QWebChannelMessageTypes = {
    signal: 1,
    propertyUpdate: 2,
    init: 3,
    idle: 4,
    debug: 5,
    invokeMethod: 6,
    connectToSignal: 7,
    disconnectFromSignal: 8,
    setProperty: 9,
    response: 10,
};

var QWebChannel = function(transport, initCallback)
{
    if (typeof transport !== "object" || typeof transport.send !== "function") {
        console.error("The QWebChannel expects a transport object with a send function and onmessage callback property." +
                      " Given is: transport: " + typeof(transport) + ", transport.send: " + typeof(transport.send));
        return;
    }

    var channel = this;
    this.transport = transport;

    this.send = function(data)
    {
        if (typeof(data) !== "string") {
            data = JSON.stringify(data);
        }
        channel.transport.send(data);
    }

    this.transport.onmessage = function(message)
    {
        var data = message.data;
        if (typeof data === "string") {
            data = JSON.parse(data);
        }
        switch (data.type) {
            case QWebChannelMessageTypes.signal:
                channel.handleSignal(data);
                break;
            case QWebChannelMessageTypes.response:
                channel.handleResponse(data);
                break;
            case QWebChannelMessageTypes.propertyUpdate:
                channel.handlePropertyUpdate(data);
                break;
            default:
                console.error("invalid message received:", message.data);
                break;
        }
    }

    this.execCallbacks = {};
    this.execId = 0;
    this.exec = function(data, callback)
    {
        if (!callback) {
            // if no callback is given, send directly
            channel.send(data);
            return;
        }
        if (channel.execId === Number.MAX_VALUE) {
            // wrap
            channel.execId = Number.MIN_VALUE;
        }
        if (data.hasOwnProperty("id")) {
            console.error("Cannot exec message with property id: " + JSON.stringify(data));
            return;
        }
        data.id = channel.execId++;
        channel.execCallbacks[data.id] = callback;
        channel.send(data);
    };

    this.objects = {};

    this.handleSignal = function(message)
    {
        var object = channel.objects[message.object];
        if (object) {
            object.signalEmitted(message.signal, message.args);
        } else {
            console.warn("Unhandled signal: " + message.object + "::" + message.signal);
        }
    }

    this.handleResponse = function(message)
    {
        if (!message.hasOwnProperty("id")) {
            console.error("Invalid response message received: ", JSON.stringify(message));
            return;
        }
        channel.execCallbacks[message.id](message.data);
        delete channel.execCallbacks[message.id];
    }

    this.handlePropertyUpdate = function(message)
    {
        for (var i in message.data) {
            var data = message.data[i];
            var object = channel.objects[data.object];
            if (object) {
                object.propertyUpdate(data.signals, data.properties);
            } else {
                console.warn("Unhandled property update: " + data.object + "::" + data.signal);
            }
        }
        channel.exec({type: QWebChannelMessageTypes.idle});
    }

    this.debug = function(message)
    {
        channel.send({type: QWebChannelMessageTypes.debug, data: message});
    };

    channel.exec({type: QWebChannelMessageTypes.init}, function(data) {
        for (var objectName in data) {
            var object = new QObject(objectName, data[objectName], channel);
        }
        // now unwrap properties, which might reference other registered objects
        for (var objectName in channel.objects) {
            channel.objects[objectName].unwrapProperties();
        }
        if (initCallback) {
            initCallback(channel);
        }
        channel.exec({type: QWebChannelMessageTypes.idle});
    });
};

function QObject(name, data, webChannel)
{
    this.__id__ = name;
    webChannel.objects[name] = this;

    // List of callbacks that get invoked upon signal emission
    this.__objectSignals__ = {};

    // Cache of all properties, updated when a notify signal is emitted
    this.__propertyCache__ = {};

    var object = this;

    // ----------------------------------------------------------------------

    this.unwrapQObject = function(response)
    {
        if (response instanceof Array) {
            // support list of objects
            var ret = new Array(response.length);
            for (var i = 0; i < response.length; ++i) {
                ret[i] = object.unwrapQObject(response[i]);
            }
            return ret;
        }
        if (!response
            || !response["__QObject*__"]
            || response.id === undefined) {
            return response;
        }

        var objectId = response.id;
        if (webChannel.objects[objectId])
            return webChannel.objects[objectId];

        if (!response.data) {
            console.error("Cannot unwrap unknown QObject " + objectId + " without data.");
            return;
        }

        var qObject = new QObject( objectId, response.data, webChannel );
        qObject.destroyed.connect(function() {
            if (webChannel.objects[objectId] === qObject) {
                delete webChannel.objects[objectId];
                // reset the now deleted QObject to an empty {} object
                // just assigning {} though would not have the desired effect, but the
                // below also ensures all external references will see the empty map
                // NOTE: this detour is necessary to workaround QTBUG-40021
                var propertyNames = [];
                for (var propertyName in qObject) {
                    propertyNames.push(propertyName);
                }
                for (var idx in propertyNames) {
                    delete qObject[propertyNames[idx]];
                }
            }
        });
        // here we are already initialized, and thus must directly unwrap the properties
        qObject.unwrapProperties();
        return qObject;
    }

    this.unwrapProperties = function()
    {
        for (var propertyIdx in object.__propertyCache__) {
            object.__propertyCache__[propertyIdx] = object.unwrapQObject(object.__propertyCache__[propertyIdx]);
        }
    }

    function addSignal(signalData, isPropertyNotifySignal)
    {
        var signalName = signalData[0];
        var signalIndex = signalData[1];
        object[signalName] = {
            connect: function(callback) {
                if (typeof(callback) !== "function") {
                    console.error("Bad callback given to connect to signal " + signalName);
                    return;
                }

                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
                object.__objectSignals__[signalIndex].push(callback);

                if (!isPropertyNotifySignal && signalName !== "destroyed") {
                    // only required for "pure" signals, handled separately for properties in propertyUpdate
                    // also note that we always get notified about the destroyed signal
                    webChannel.exec({
                        type: QWebChannelMessageTypes.connectToSignal,
                        object: object.__id__,
                        signal: signalIndex
                    });
                }
            },
            disconnect: function(callback) {
                if (typeof(callback) !== "function") {
                    console.error("Bad callback given to disconnect from signal " + signalName);
                    return;
                }
                object.__objectSignals__[signalIndex] = object.__objectSignals__[signalIndex] || [];
                var idx = object.__objectSignals__[signalIndex].indexOf(callback);
                if (idx === -1) {
                    console.error("Cannot find connection of signal " + signalName + " to " + callback.name);
                    return;
                }
                object.__objectSignals__[signalIndex].splice(idx, 1);
                if (!isPropertyNotifySignal && object.__objectSignals__[signalIndex].length === 0) {
                    // only required for "pure" signals, handled separately for properties in propertyUpdate
                    webChannel.exec({
                        type: QWebChannelMessageTypes.disconnectFromSignal,
                        object: object.__id__,
                        signal: signalIndex
                    });
                }
            }
        };
    }

    /**
     * Invokes all callbacks for the given signalname. Also works for property notify callbacks.
     */
    function invokeSignalCallbacks(signalName, signalArgs)
    {
        var connections = object.__objectSignals__[signalName];
        if (connections) {
            connections.forEach(function(callback) {
                callback.apply(callback, signalArgs);
            });
        }
    }

    this.propertyUpdate = function(signals, propertyMap)
    {
        // update property cache
        for (var propertyIndex in propertyMap) {
            var propertyValue = propertyMap[propertyIndex];
            object.__propertyCache__[propertyIndex] = propertyValue;
        }

        for (var signalName in signals) {
            // Invoke all callbacks, as signalEmitted() does not. This ensures the
            // property cache is updated before the callbacks are invoked.
            invokeSignalCallbacks(signalName, signals[signalName]);
        }
    }

    this.signalEmitted = function(signalName, signalArgs)
    {
        invokeSignalCallbacks(signalName, this.unwrapQObject(signalArgs));
    }

    function addMethod(methodData)
    {
        var methodName = methodData[0];
        var methodIdx = methodData[1];
        object[methodName] = function() {
            var args = [];
            var callback;
            for (var i = 0; i < arguments.length; ++i) {
                var argument = arguments[i];
                if (typeof argument === "function")
                    callback = argument;
                else if (argument instanceof QObject && webChannel.objects[argument.__id__] !== undefined)
                    args.push({
                        "id": argument.__id__
                    });
                else
                    args.push(argument);
            }

            webChannel.exec({
                "type": QWebChannelMessageTypes.invokeMethod,
                "object": object.__id__,
                "method": methodIdx,
                "args": args
            }, function(response) {
                if (response !== undefined) {
                    var result = object.unwrapQObject(response);
                    if (callback) {
                        (callback)(result);
                    }
                }
            });
        };
    }

    function bindGetterSetter(propertyInfo)
    {
        var propertyIndex = propertyInfo[0];
        var propertyName = propertyInfo[1];
        var notifySignalData = propertyInfo[2];
        // initialize property cache with current value
        // NOTE: if this is an object, it is not directly unwrapped as it might
        // reference other QObject that we do not know yet
        object.__propertyCache__[propertyIndex] = propertyInfo[3];

        if (notifySignalData) {
            if (notifySignalData[0] === 1) {
                // signal name is optimized away, reconstruct the actual name
                notifySignalData[0] = propertyName + "Changed";
            }
            addSignal(notifySignalData, true);
        }

        Object.defineProperty(object, propertyName, {
            configurable: true,
            get: function () {
                var propertyValue = object.__propertyCache__[propertyIndex];
                if (propertyValue === undefined) {
                    // This shouldn't happen
                    console.warn("Undefined value in property cache for property \"" + propertyName + "\" in object " + object.__id__);
                }

                return propertyValue;
            },
            set: function(value) {
                if (value === undefined) {
                    console.warn("Property setter for " + propertyName + " called with undefined value!");
                    return;
                }
                object.__propertyCache__[propertyIndex] = value;
                var valueToSend = value;
                if (valueToSend instanceof QObject && webChannel.objects[valueToSend.__id__] !== undefined)
                    valueToSend = { "id": valueToSend.__id__ };
                webChannel.exec({
                    "type": QWebChannelMessageTypes.setProperty,
                    "object": object.__id__,
                    "property": propertyIndex,
                    "value": valueToSend
                });
            }
        });

    }

    // ----------------------------------------------------------------------

    data.methods.forEach(addMethod);

    data.properties.forEach(bindGetterSetter);

    data.signals.forEach(function(signal) { addSignal(signal, false); });

    for (var name in data.enums) {
        object[name] = data.enums[name];
    }
}

//required for use with nodejs
if (typeof module === 'object') {
    module.exports = {
        QWebChannel: QWebChannel
    };
}

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt封装HTML网页及数据交互(通信) 的相关文章

随机推荐

  • unity3d 启动项目卡在load script assembly

    最近突然碰到unity启动项目 卡在load script assembly的情况 删除了library重开也还是如此 日志打印里面打印到 刷新script开始然后就戛然而止了 后来发现是 项目里某个文件夹被一个命令行占用了 unity在导
  • 安装nrm报错

    internal validators js 124 throw new ERR INVALID ARG TYPE name string value TypeError ERR INVALID ARG TYPE The path argu
  • 2023华为OD机试真题【最接近中位数的索引】

    前言 本题使用Java解答 如果需要python版本答案 请查看以下链接 Python版本答案 题目内容 给定一个数组X和正整数K 请找出使表达式X i x i 1 X i K 1 结果最接近于数组中位数的下标i 如果有多个i满足条件 请返
  • [Java]两个有理数的加减乘除

    public class Rational private long num 0 分子 private long den 1 分母 Begin long i public Rational long num long den this nu
  • Blender 的 操作与快捷键

    Blender是一款开源的免费的3D建模软件 目前来看还不错哦 Blender的快捷键 鼠标右键选中物体 左键是确认 选中下 按住滑轮拖动鼠标可以环视 选中下 按住shift正反键可以上下 选中下 按住shift按住滑轮拖动鼠标可以左右 小
  • javaweb-HTTP协议(服务器数据的接收、处理、响应流程)详解

    HTTP协议 HTTP请求 两种HTTP请求方式 在服务器接收信息 HttpServletRequest 请求行 请求头 请求体 处理 响应 HTTP协议 什么是HTTP协议 超文本传输协议 Hypertext Transfer Proto
  • https请求跨域问题的解决

    原http服务升级为https服务后 发现同级域名下的单点登录不可用 报错 The page at https aaa xxx com was loaded over HTTPS but requested an insecure fram
  • CTFweb篇——upload-labs

    0x00 前言 本次以 upload labs的Pass 00和pass 01为例 0x01 进入靶场 pass 00 首先查看Pass 00题目 任务要求上传一个webshell到服务器 编写一句话木马 然后上传 右键复制图像地址 连接菜
  • Windows系统克隆***与防范

    随着电脑技术的发展和电脑的普及 还有大大小小的 骇客 网站和越来越简单的工具 使得目前 变得日趋频繁 被植入 的电脑或 服务器也越来越多 与此同时系统管理员的 安全意识也在不断提高 加上杀毒软件的发展 网络 的生命周期也越来越短 所以 者在
  • sql函数创建和调用

    create FUNCTION Fun GetDeptID 传入参数 或参数类型 UserID VARCHAR 100 IDepGrade INT 返回值类型 RETURNS VARCHAR 100 AS BEGIN 定义返回值 DECLA
  • 揭开“视频超分”黑科技的神秘面纱

    在看电影时 有一幕大家应该都非常熟悉 警察从证据图片中选取一块区域放大 再放大 直到一个很小的目标变得清晰可见 从而发现重要的线索 现实中是不是真的有这样的技术 可以把模糊的小图变得清晰 答案是 一定程度上可以 这项黑科技就是超分辨率技术
  • 【教程】PyTorch Timer计时器

    转载请注明出处 小锋学长生活大爆炸 xfxuezhang cn OpenCV的Timer计时器可以看这篇 Python Timer和TimerFPS计时工具类 Timer作用说明 统计某一段代码的运行耗时 直接上代码 开箱即用 import
  • 【实战】通过 JS 将 HTML 导出为 PDF 文档

    背景介绍 某老人院信息管理系统项目 甲方要求将财务模块的各种报表导出为PDF文档 方便打印 之前的解决方案 是将报表生成专门的打印 HTML 页面 然后按 Ctrl P 调用浏览器本身打印功能去打印 这种方式存在的问题是不同分辨率的显示器
  • vue单页应用在页面刷新时保留状态数据的方法

    在Vue单页应用中 如果在某一个具体路由的具体页面下点击刷新 那么刷新后 页面的状态信息可能就会丢失掉 这时候应该怎么处理呢 如果你也有这个疑惑 这篇文章或许能够帮助到你 一 问题 现在产品上有个需求 单页应用走到某个具体的页面 然后点击刷
  • 跨平台开发之 Tauri

    比起 Electron Tauri 打包后的安装包体积是真的小 跨平台开发 最近使用跨平台开发框架写了一个软件 在此记录一下 说起跨平台开发 我的理解是这样的 多依赖浏览器环境运行 多使用前端语言进行开发 只需一次编码 但不同平台可能需要做
  • linux usermod用法,Linux中Usermod命令的一些使用技巧

    usermod 是一个命令行实用程序 允许您修改用户的登录信息 本文介绍了如何使用usermod命令添加用户到组 更改用户shell 登录名 主目录等 1 usermod 命令 该usermod命令的语法采用以下形式 usermod opt
  • 爬虫遇到验证码必须要知道的解决办法(干货)

    对于爬取数据而言 有的网站在登录时或者采集数据过程中 都会出现验证码 对于网络爬虫而言 解决验证码识别识别是非常重要的一件事 今天 我们将讨论有关验证码的5件事 以帮助大家更好的进行网络数据抓取 1 什么是验证码 2 验证码是如何工作的 3
  • 浏览器输入url后经历的过程

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 一 过程简述 二 发送http请求前先进行DNS域名解析 主要过程如下 注意 三 三次握手 四 四次挥手 五 TCP的连接与断开 六 浏览器渲染过程 渲染细节 一
  • 免费、强大的笔记软件推荐:Obsidian、Zettlr、Joplin、FlowUs

    在优质应用推荐系列中 我已经推送了 盘点那些具有特色的笔记软件 盘点那些具有特色的写作软件 优质笔记软件详细盘点 一 优质笔记软件盘点 二 今天在此基础上继续推送 免费且优秀的笔记软件 推荐标准 1 完全免费的笔记软件 这类应用绝大部分是开
  • Qt封装HTML网页及数据交互(通信)

    看到网上有个关于Qt封装网页的例子 CSDN下载需要50积分 我呸 其实也是人家Qt官网给的demo 然后改了一丢丢而已 既然这样 那我也实现一下 而且提供源码 环境 Qt5 13 MSVC 要点 Qt GUI QWebEngine QWe