QWebChannel 向 JavaScript 发送空的 QVariant POD 结构

Posted

技术标签:

【中文标题】QWebChannel 向 JavaScript 发送空的 QVariant POD 结构【英文标题】:QWebChannel sends null QVariant POD structs to JavaScript 【发布时间】:2018-10-27 03:34:19 【问题描述】:

我正在编写一个 C++ QWidget 应用程序,它与运行在资源托管网页中的 javascript 进行通信。我需要找到一种方法将以下 POD 结构的数组发送到网页中托管的 JavaScript 函数,但不幸的是,数据总是以空数组的形式结束。我认为这个问题类似于this question - 但这也没有答案。

自定义 POD 结构(我需要发送这些列表(QVariantList))是:

using FPlanWPT = struct FPlanWPT 
    //std::string name;
    double longitude;
    double latitude;
    double bearing;
;

// register custom type with the QT type system
Q_DECLARE_METATYPE(FPlanWPT);

用作通知器的IPC类如下:

class FlightRoute : public QObject 
    Q_OBJECT
    Q_PROPERTY(QVariantList data READ data NOTIFY dataChanged)
public:
    explicit FlightRoute(QObject* parent = nullptr)
        : QObject(parent)
    

    //! Flight route data getter.
    QVariantList data() const 
        return valueList;
    
public slots:
    void updateRouteData(const QVariantList& data);

signals:
    void dataChanged(const QVariantList& data);

private:
    QVariantList valueList;
;

上面的想法是当

我发现与我想要实现的目标最接近的 example 是基于 QT Widget 的 JavaScript Chart.js 应用程序,它生成随机图表列并更新在网页中运行的图表。

在 QT C++ Widget 应用程序和 JavaScript 之间获得 IPC 通信的关键是在两端初始化一个QWebChannel。

C++ 方面,这基本上是:

class mainwindow : public QMainWindow

    Q_OBJECT
public:
    explicit mainwindow(QWidget *parent = Q_NULLPTR);
    ~mainwindow();
    ...
private:
    ...
    std::unique_ptr<QWebEngineView> mpWebView;

构造函数如下:

//! Constructor.
mainwindow::mainwindow(QWidget *parent)
    : QMainWindow(parent)
    , mUI(new Ui::mainwindow())
    . . .
    , mpWebView(std::make_unique<QWebEngineView>())
    , mRecordBuffer
    , mpWorkerThreadnullptr
    , mpWorkerObjectnullptr

    static auto& gLogger = gpLogger->getLoggerRef(
        gUseSysLog ? Logger::LogDest::SysLog :
        Logger::LogDest::EventLog);

    // JavaScript integration - allow remote debugging with mpWebView
    qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "1234");

    // register custom types for serialization with signals/slots
    qRegisterMetaType<FPlanWPT>("FPlanWPT");

    // initialize the form
    mUI->setupUi(this);    

    // load the web page containing the google map javascript
    mpWebView->page()->load(QUrl("qrc:///html/test.html"));

    // initialize the link to the HTML web page content
    auto webChannel = new QWebChannel(this);
    const auto routeIPC = new FlightRoute(this);
    // register IPC object with the QWebChannel
    connect(mpWorkerObject, &Worker::fooSignal, routeIPC, &FlightRoute::updateRouteData, Qt::DirectConnection);
    webChannel->registerObject(QStringLiteral("routeIPC"), routeIPC);
    mpWebView->page()->setWebChannel(webChannel);

    // Insert the html page at the top of the grid layout
    mUI->flightTrackGridLayout->addWidget(mpWebView.get(), 0, 0, 1, 3);
    . . .

JavaScript 方面(这是我的整个 HTML 文件)

<html>
<head>
    <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
</head>
<body>
    <div style="margin:auto; width:100%;">
        <canvas id="canvas"></canvas>
    </div>
    <script>
        // function to update the google
        var updateMap = function (waypointData) 
            // waypoint data is always a list of nulls
            console.log(waypointData);
        

        // script called once web page loaded
        window.onload = function () 
            new QWebChannel(qt.webChannelTransport,
                function (channel) 
                    // whenever the route data changes, invoke updateMap slot
                    var dataSource = channel.objects.routeIPC;
                    dataSource.dataChanged.connect(updateMap);
                
            );
        
    </script>
</body>
</html>

此外,在 C++ 端,我们将页面从 QWebEngineView 设置为显示 Web 内容的 Gui 布局。这些需要提前初始化,通过已建立的 QWebChannel 异步流向插槽的信号。就我而言,我只关心通过 QVariant 对象从 C++ 端向 JavaScript 端发送自定义 POD 结构。

为了在 QVariant 中使用自定义数据结构 - 或者在我的情况下是 QVariantList 的 - 我们需要使用 QT 注册自定义类型 - 作为一种元 rtti 注册。如果没有这个,javascript 槽函数 var updateMap 将无法从 POD 的字段中解码类型信息。

【问题讨论】:

@eyllanesc 完成! @eyllanesc ty 很好的答案,我还没有完全为我工作。我有点困惑 FlightRoute 对象如何神奇地通知 javascript 中的插槽 - 我的代码使用 Json 数组调用发出 routesChanged(array) - 但我的函数不再在 JavaScript 中被调用 - 注意我删除了我的 FlightRoute 的 qRegisterMetaType<..> 和 Q_DECLARE_METATYPE(...)。我的 javascript 理论上应该使用 dataSource.routesChanged.connect(updateMap) 建立连接(它曾经) 我的测试代码在以下链接中:github.com/eyllanesc/***/tree/master/53018492 执行并打开链接:127.0.0.1:9000,你会看到印象。如果你有问题,我可以帮助你,如果你通过 github 与我分享你的代码,我可以更正它。 @eyllanesc 谢谢 - 天哪,你真的很有条理!惊人的工作流程。我在那里看到了大量的 sn-p 项目。顺便说一句,我终于让我的代码工作了,在您的示例中,routeIPC 的生命周期仍然存在,因为它存在于 main 中,在我的情况下,我需要在 mainwindow 中有一个它的实例,我只有一个 WebViewEngine 成员。因此,为了从(为了发布到它)访问 IPC,我需要通过以下方式检索 ipc 对象: auto pFlightRouteIPC = mpWebView->page()->webChannel()->registeredObjects().find(" routeIPC").value() 然后通过 pFlightRouteIPC->setRoutes(routes) 发布。 @eyllanesc 顺便说一句 QWebChannel 只支持 Json 对象?它真的没有写在任何地方,引擎是否需要具有本机 json 转换的 QVariants 或 QStrings 才能工作? - 只是好奇。顺便说一句,我需要做的下一件事(现在我在 javascript 级别有纬度/经度值是用线将这些点连接在一起构建一个地图。它可能有点牵强,但你碰巧有这样一个例子: )。我将调整我的 html 以调用类似 developers.google.com/maps/documentation/javascript/shapes 的内容 【参考方案1】:

有一个很常见的错误,很多人认为通过QWebChannel传输数据是使用QMetatype,而且文档不是很清楚也没有说明可以传输什么样的数据,但是一个report明确说明了可以运输:json 或者可以打包成 json 的物品。如果对实现进行审查,可以检查这一点,例如在answer 中为 WebView 实现 QWebChannel,我的主要任务是从 QString 转换为 QJsonObject,反之亦然。

因此,在任何需要通过 QWebChannel 发送数据的情况下,您都必须将其转换为 QJsonValue、QJsonObject 或 QJsonArray。由于您希望发送数据数组,我将使用 QJsonArray:

flightroute.h

#ifndef FLIGHTROUTE_H
#define FLIGHTROUTE_H

#include <QJsonArray>
#include <QJsonObject>
#include <QObject>

struct FPlanWPT 
    QString name;
    double longitude;
    double latitude;
    double bearing;
    QJsonObject toObject() const
        QJsonObject obj;
        obj["name"] = name;
        obj["longitude"] = longitude;
        obj["latitude"] = latitude;
        obj["bearing"] = bearing;
        return obj;
    
;

class FlightRoute : public QObject

    Q_OBJECT
public:
    using QObject::QObject;
    void setRoutes(const QList<FPlanWPT> &routes)
        QJsonArray array;
        for(const FPlanWPT & route: routes)
            array.append(route.toObject());
        
        emit routesChanged(array);
    
signals:
    void routesChanged(const QJsonArray &);
;

#endif // FLIGHTROUTE_H

ma​​in.cpp

#include "flightroute.h"

#include <QApplication>
#include <QTimer>
#include <QWebChannel>
#include <QWebEngineView>
#include <random>

int main(int argc, char *argv[])

    qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "9000");
    QApplication a(argc, argv);
    QWebEngineView view;

    FlightRoute routeIPC;
    QWebChannel webChannel;
    webChannel.registerObject(QStringLiteral("routeIPC"), &routeIPC);
    view.page()->setWebChannel(&webChannel);

    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_real_distribution<> dist(0, 100);

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&]()
        QList<FPlanWPT> routes;
        for(int i=0; i<10; i++)
            routes << FPlanWPT"name1", dist(gen), dist(gen), dist(gen);
        
        routeIPC.setRoutes(routes);
    );
    timer.start(1000);

    view.load(QUrl(QStringLiteral("qrc:/index.html")));
    view.resize(640, 480);
    view.show();
    return a.exec();

【讨论】:

以上是关于QWebChannel 向 JavaScript 发送空的 QVariant POD 结构的主要内容,如果未能解决你的问题,请参考以下文章

实现QObject与JavaScript通讯(基于QWebEngine + QWebChannel)

消除 QWebChannel 属性通知器信号警告

未捕获的 ReferenceError:未定义 QWebChannel

QWebChannel:在 QtCreator 之外运行时构造崩溃

如何在 Qt 中使用 QWebChannel 发送 QJsonObject

使用 QWebChannel 时未定义的属性和返回类型