【问题标题】:Create Listener For Dom Changes in Qml WebEngineView (Mutation Observer)为 Qml WebEngineView 中的 Dom 更改创建侦听器(Mutation Observer)
【发布时间】:2019-07-09 10:43:06
【问题描述】:

我有一个包含这个的 html 页面:

<div id="ajaxloader" style="display: none;">
        <div>
          hello world
        </div>
</div>

我可以用document.getElementById('ajaxloader').style.display读取它的显示值
我想在其显示更改为blocknone 时收到通知。
目前我正在使用计时器的愚蠢解决方案:

Timer{
        running : true
        repeat : true
        interval: 500
        onTriggered: {
            var js = "document.getElementById('ajaxloader').style.display"
            webView.runJavaScript(js,x=>{
                  if(x=="block"){
                       // we catch the change but its really bad solution                   
                  }
            })
        }
    }  

我正在寻找一种方法来捕捉 DOM 中的这种变化,有一种叫做 Mutation Observer 的东西,但我不知道如何在 QML 的 WebEngineView 中实现它。
我只需要一种方法来捕捉 WebEngineView 中发生的变化,或者捕捉引擎中发生的 CRUD 的事件,或者比这个计时器更好!
更新 :
例如,我们有一个访问 google.com 的网络引擎,加载完成后它将搜索文本更改为“hello world”,我们希望在不使用计时器的情况下捕捉该更改,在实际网站中,这种更改实际上发生在 CRUD 函数(ajax 请求)或其他方式:

WebChannel{
    id:_channel
}
WebEngineView{
    id:webEngine
    height: parent.height
    width: parent.width
    webChannel: _channel
    url : "www.google.com"
    onNewViewRequested: {
        request.openIn(webEngine)
    }
    objectName: "webView"
    profile.httpCacheType: WebEngineProfile.NoCache

    onLoadingChanged: {
        if(loadRequest.status == WebEngineView.LoadSucceededStatus){
            var js = "document.querySelector('input[role=combobox]').value = 'hello world'"
            webEngine.runJavaScript(js,y=>{})
        }
    }

}  

不要忘记在 c++ 中初始化引擎,如果没有这个,它将无法工作:QtWebEngine::initialize(); 和其他导入的东西加上你需要将它添加到 pro 文件 QT += webengine webengine-private webenginecore webenginecore-private
现在如果我使用我想把它放在一边的计时器方法,它应该是这样的:

Timer{
    running : true
    repeat : true
    interval : 500
    onTriggered:{
         var js = "document.querySelector('input[role=combobox]').value"
         webEngine.runJavaScript(js,y=>{console.log(y)});
         // now i have to check y to see if its equals to hello world or what ever which is really bad idea to use a timer here
    }
}  

例如,您可以像这样观察 google 输入的变化:

var targetNode = document.querySelector('input[role=combobox]')
targetNode.oninput = function(e){this.setAttribute('value',targetNode.value)}
var config = { attributes: true, childList: true, subtree: true };
var callback = function(mutationsList, observer) {
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type == 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// Create an observer instance linked to the callback function
var observer = new MutationObserver(callback);

// Start observing the target node for configured mutations
observer.observe(targetNode, config); 

【问题讨论】:

  • 您的 MutationObserver 仅在用户进行更改而不是在以编程方式进行更改时才有效,您是否只想在用户进行更改时检测更改?
  • @eyllanesc 有没有办法以编程方式包含更改?突变或没有任何方式都可以

标签: qt qml qtwebengine


【解决方案1】:

策略是在页面加载时加载 qwebchannel.js,然后注入另一个脚本,使用 Qt WebChannel 与导出的对象建立连接

在 C++ 中不需要创建 QObject,你可以使用 QtObject。

ma​​in.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtWebEngine>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);
    QtWebEngine::initialize();

    QString JsWebChannel;
    QFile file(":///qtwebchannel/qwebchannel.js");
    if(file.open(QIODevice::ReadOnly))
        JsWebChannel = file.readAll();

    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("JsWebChannel", JsWebChannel);
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

ma​​in.qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtWebChannel 1.13
import QtWebEngine 1.1

Window {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")

    QtObject{
        id: listener
        WebChannel.id: "listener"
        property string text: ""
        onTextChanged: console.log(text) 

        property string script: "
            var listener;
            new QWebChannel(qt.webChannelTransport, function (channel) {
                listener = channel.objects.listener;

                var targetNode = document.querySelector('input[role=combobox]')
                targetNode.oninput = function(e){this.setAttribute('value',targetNode.value)}
                var config = { attributes: true, childList: true, subtree: true };
                var callback = function(mutationsList, observer) {
                    for(var mutation of mutationsList) {
                        if (mutation.type == 'childList') {
                            console.log('A child node has been added or removed.');
                        }
                        else if (mutation.type == 'attributes') {
                            console.log('The ' + mutation.attributeName + ' attribute was modified.');
                            listener.text = targetNode.value; // update qproperty
                        }
                    }
                };

                // Create an observer instance linked to the callback function
                var observer = new MutationObserver(callback);

                // Start observing the target node for configured mutations
                observer.observe(targetNode, config); 
            });
        "
    }
    WebChannel{
        id: channel
        registeredObjects: [listener]
        function inject(){
            webEngine.runJavaScript(JsWebChannel);
            webEngine.runJavaScript(listener.script);
        }
    }

    WebEngineView {
        id:webEngine
        url : "https://www.google.com/"
        profile.httpCacheType: WebEngineProfile.NoCache
        webChannel: channel
        anchors.fill: parent
        onLoadingChanged: {
            if(loadRequest.status == WebEngineView.LoadSucceededStatus){
                console.log("Page has successfully loaded")
                channel.inject()
            }
        }
    }
}

【讨论】:

  • ty,它确实有效,本杰明回答也有很大帮助,因为我在我的代码中使用了 cpp 对象。
【解决方案2】:

如果你想让javascript调用C++函数,你需要使用Qt WebChannel模块。

它会让你将任何QObject派生对象暴露给javascript,允许你从javascript调用暴露对象的任何槽函数。

在您的情况下,您可以在 javascript 中使用 MutationObserver 并将 C++ 函数用作回调。

这是一个基本的例子:

// C++
class MyObject: public QObject {
    Q_OBJECT
public slots:
    void mySlot();
}

int main() {
   ...

   // Create the channel and expose the object.
   // This part can also be done in QML
   QWebChannel channel;

   MyObject obj;
   channel.registerObject(QStringLiteral("myobject"), &obj);
   ...
}

// js
myobject = channel.objects.myobject;
observer = new MutationObserver(() => { myobject.mySlot(); }); // Will call MyObject::mySlot() in C++
observer.observe(...);

如果您想了解更多详细信息,可以查看以下示例:https://doc.qt.io/qt-5/qtwebchannel-standalone-example.html

Qt 文档中还有其他示例,但请记住,js 部分 Qt WebChannel 可以在 QWebEngine 或任何 Web 浏览器中使用。因此,您将找到的一些示例可能与您需要做的不同。

此外,您不能在函数调用中跨 C++/js 边界传递任何参数类型

【讨论】:

  • Mutation Observer 应该在WebEngine.runJavaScript("what ever mutation is ",bla bla) 中调用,所以我不能从那里调用 cpp 的 slot 你能解释在引擎中运行这个突变观察器吗?
  • @MahdiKhalili 是的,如果您使用 Qt WebChannels,您可以从那里调用一个插槽。这就是我回答的重点。我的示例中的 JS 部分可以从网页中的 JS 代码运行,也可以从用 runJavaScript() 注入的 JS 代码运行。
  • 您的大力帮助,eyllanesc 的答案更接近我正在寻找的内容
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-03-04
  • 1970-01-01
  • 1970-01-01
  • 2021-01-14
  • 2012-10-27
相关资源
最近更新 更多