为 Qml WebEngineView 中的 Dom 变化创建监听器(Mutation Observer)
Create Listener For Dom Changes in Qml WebEngineView (Mutation Observer)
我有一个 html 页面包含这个 :
<div id="ajaxloader" style="display: none;">
<div>
hello world
</div>
</div>
我可以用document.getElementById('ajaxloader').style.display
读取它的显示值
我想在显示更改为 block
或 none
.
时收到通知
目前我正在使用一个使用计时器的愚蠢解决方案:
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);
如果要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 边界的函数调用中传递任何参数类型
策略是在加载页面时加载qwebchannel.js,然后注入另一个脚本,使用Qt WebChannel
与导出的对象建立连接
在C++中不需要创建QObject,可以使用QtObject。
main.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();
}
main.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()
}
}
}
}
我有一个 html 页面包含这个 :
<div id="ajaxloader" style="display: none;">
<div>
hello world
</div>
</div>
我可以用document.getElementById('ajaxloader').style.display
读取它的显示值
我想在显示更改为 block
或 none
.
时收到通知
目前我正在使用一个使用计时器的愚蠢解决方案:
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);
如果要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 边界的函数调用中传递任何参数类型
策略是在加载页面时加载qwebchannel.js,然后注入另一个脚本,使用Qt WebChannel
与导出的对象建立连接在C++中不需要创建QObject,可以使用QtObject。
main.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();
}
main.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()
}
}
}
}