如何使用Qt-WebEngine和QWebChannel

How to use Qt WebEngine and QWebChannel?

本文关键字:QWebChannel Qt-WebEngine 何使用      更新时间:2023-10-16

我正在使用新的WebEngine进行游戏和学习。我一直在尝试使用Qt WebKit找到一些类似的方法:addToJavaScriptWindowObject()

我发现使用QtWebEngine,我必须使用QWebChannel将函数注册到JavaScript窗口对象。如果这是正确的,我就要回答以下问题。

我在电脑上安装了Qt 5.4.0。我注意到在我电脑上安装的SDK中找不到qwebchannel.js。我在Git源代码上找到的。

如果我有一个带有QWebEnginePageQWebEngineView的Qt原生桌面应用程序,那么我需要什么才能在JavaScript窗口对象上注册函数?

我的桌面应用程序会自动导航到我创建的http页面。因此,我可以访问连接到QWebEngineView的内容。

我要采取哪些步骤才能使之发挥作用?

在Qt5.6中,如果你想让C++部分和JavaScript进行通信,唯一的方法就是在QWebEngineView上使用QWebChannel,正如你所说。您可以在.cpp文件中这样做:

m_pView = new QWebEngineView(this);
QWebChannel * channel = new QWebChannel(page);
m_pView->page()->setWebChannel(channel);
channel->registerObject(QString("TheNameOfTheObjectUsed"), this);

在这里,您只需要注册一个名为TheNameOfTheObjectUsed的对象,该对象将在JS端可用。现在,这是要在JS端使用的代码部分:

new QWebChannel(qt.webChannelTransport, function (channel) {
            // now you retrieve your object
            var JSobject = channel.objects.TheNameOfTheObjectUsed;
        });

现在,如果你想在JS端检索类的一些属性,你需要在C++端有一个方法,它返回一个字符串、一个整数、一个长。。。这就是它在C++端的样子,在您的.h:中

Q_INVOKABLE int getInt();
Q_PROPERTY(int myIntInCppSide READ getInt);

现在,在JS端可以得到这样的int:

var myIntInJSside= JSobject.myIntInCppSide;

这是一个非常简单的解释,我建议您观看这段视频,这对我非常有用。此外,您可能想阅读更多关于QWebChannel提供的JavaScript API的信息,以及有关QWebChannel的文档。

希望能有所帮助!

我将把您的问题总结如下:

  1. 我需要QWebChannel在WebEngine中注册JavaScript函数吗
  2. 在哪里可以找到QWebChannel.js
  3. 如何将JS与C++和C++与JS进行通信

我将逐步解释代码。在下面的代码段中,有标记为DEFINITIONSSETUPTEST的部分。以下代码将插入其中。

首先,让我们使用一个简单的代码:

#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
// ... DEFINITIONS HERE
auto main( int argn, char* argv[] )-> int
{
    QApplication app(argn, argv);
    QWebEngineView browser;
    browser.resize(QSize(800,600));
    browser.show();
    browser.load(QUrl("http://www.wikipedia.org"));
    
    // .. SETUP HERE
    
    QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
    { 
        qDebug()<<"Load Finished " << ok;
        
        // TEST CODE HERE
    ));
    
    return app.exec();
}

说明:这段代码创建一个Qt应用程序,创建一个QWebEngineView,并设置一些最小的属性使其可见。"维基百科"中的一个页面被load编辑在里面,当页面最终加载时,会连接一个信号/插槽事件来打印一些日志。

如何从C++中调用JS函数

您可以简单地使用QWebEnginePage::runJavaScript调用JS,如下所示。将此代码添加到TEST CODE HERE中。

QString code = QStringLiteral(
R"DELIM(
var links = document.getElementsByTagName('a');
for ( var i=0; i<links.length; ++i)
{
    links[i].style.backgroundColor = 'yellow';
};
)DELIM");
browser.page()->runJavaScript(code, 42);

说明:这段代码在浏览器中执行一些JS,上下文ID为42,避免了与页面ID 0的默认上下文发生冲突。脚本将每个链接的背景颜色更改为黄色。

如何从JS调用C++

在这种情况下,我们需要QWebChannel机制将C++对象注册到JavaScript中。

首先,让我们创建一个C++接口,可从JS(在DEFINITION中)调用:

class JsInterface: public QObject
{
    Q_OBJECT
public:
    /// Log, for debugging
    Q_INVOKABLE void log(const QString& str) const
    {
        qDebug() << "LOG from JS: " << str;
    }
};
#include "main.moc"

说明:这段代码声明并定义了一个QObject类,里面有一个简单的log函数。声明函数Q_INVOKABLE很重要,否则JavaScript找不到它!。由于声明与代码的其余部分在同一个文件中,因此我们在后面包含QT中的auto-moc文件(它是main.moc,因为我的文件是main.cpp)。

DEFINITION中创建一个函数,返回JavaScript QWebChannel.js内容。QWebChannel.js的内容可以在您的QT库中找到(./5.12.2/Src/qtwebchannel/examples/webchannel/shared/QWebChannel.js或./examples/QT-5.12.2/webchannel/sshared/QWebChannel.js)。您可以直接在页面中加载。

DECLARATION部分,附加:

QString qWebChannelJs()
{
    return R"DELIMITER(
    // COPY HERE ALL THE FILE
    )DELIMITER";
}

我们在代码中注入它(将它附加到TEST CODE HERE部分):

browser.page()->runJavaScript(qWebChannelJs(), 42);

我们需要在C++侧(SETUP部分)设置QWebChannel

QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);

说明:我们创建一个通道JsInterface对象,并将它们注册到浏览器中。我们需要使用相同的上下文id 42(但可以是0到255之间的另一个数字)。

最后,在我们的JS代码中,我们访问通道并调用接口的函数(附加到TEST CODE部分):

QString code2 = QStringLiteral(
R"DELIM(
                   
window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
{
    var cpp = channel.objects.JsInterface;
    cpp.log("Hello from JavaScript");
});
                   
)DELIM");
browser.page()->runJavaScript(code2, 42);

注意事项

值得一提的是,从C++到JavaScript或从JavaScript到C++的任何调用都要经过异步的进程间通信(IPC)。这意味着runJavaScript在JavaScript执行之前返回,JavaScript在C++log执行之前返回。

JsInterface对象创建一次,在将通道设置为页面(setWebChannel)之前,必须将registerObject放入QWebChannel中。然后,每次加载页面时,都会加载js API(runJavaScript(qWebChannelJs())),并将通道设置为该页面。如果在通道设置后注册JsInterface,则会收到以下日志消息:

初始化后注册了新对象,不会通知现有客户端!

完整代码

#include <QApplication>
#include <QDebug>
#include <QWebEngineView>
#include <QWebChannel>
QString qWebChannelJs()
{
    return R"DELIMITER(
        // TODO INSERT JS code here
    )DELIMITER";
}
class JsInterface: public QObject
{
    Q_OBJECT
public:
    /// Log, for debugging
    Q_INVOKABLE void log(const QString& str) const
    {
        qDebug() << "LOG from JS: " << str;
    }
};
#include "main.moc"
auto main( int argn, char* argv[] )-> int
{
    QApplication app(argn, argv);
    QWebEngineView browser;
    browser.resize(QSize(800,600));
    browser.show();
    browser.load(QUrl("http://www.wikipedia.org"));
    
    // .. SETUP HERE
    QWebChannel channel;
    JsInterface jsInterface;
    browser.page()->setWebChannel(&channel, 42);
    channel.registerObject(QString("JsInterface"), &jsInterface);
    
    QObject::connect(&browser, &QWebEngineView::loadFinished, [&browser](bool ok)
    { 
        qDebug()<<"Load Finished " << ok;
        
        // TEST CODE HERE
        QString code = QStringLiteral(
        R"DELIM(
        
        var links = document.getElementsByTagName('a');
        for ( var i=0; i<links.length; ++i)
        {
            links[i].style.backgroundColor = 'yellow';
        };
                           
        )DELIM");
        browser.page()->runJavaScript(code, 42);
        
        browser.page()->runJavaScript(qWebChannelJs(), 42);
        
        QString code2 = QStringLiteral(
        R"DELIM(                   
        window.webChannel = new QWebChannel(qt.webChannelTransport, function( channel)
        {
            var cpp = channel.objects.JsInterface;
            cpp.log("Hello from JavaScript");
        });
                           
        )DELIM");
        browser.page()->runJavaScript(code2, 42);
    });
    
    return app.exec();
}

相关主题:

如何设置在QWebEngineView中使用的QWebChannel JS API?

外部文件:

https://doc.qt.io/qt-5/qwebengineview.html
https://doc.qt.io/qt-5/qwebchannel.html
https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-contentmanipulation-example.html

Qt现在有关于这方面的文档:

Qt WebChannel独立示例

您必须向您的cpp应用程序添加一个QWebSocketServerQWebEngineView的HTML/Javascript将使用WebSocket连接到该应用程序。然后使用QWebChannel进行双向通信。

与页面通信的另一种更简单的方式是使用runJavaScript函数:

view->page()->runJavaScript("alert('Hello from C++');");

它有其局限性:调用必须从C++端启动,并且只能从JS获得同步响应。但也有好处:不需要修改底层网页。

可以使用QWebEngineView::page()功能访问当前打开的网页,如上面的示例所示。在导航过程中,浏览器不会更改页面,直到从网络接收到下一个页面,因此此函数随时返回有效的页面对象。但是您的JS可能仍然会中断新页面加载,就像您在document.readyState == 'loading'中出现的那样,其中DOM树尚未构建,页面上的一些脚本可能尚未运行。在这种情况下,您应该等待DOMContentLoaded事件。