Как использовать Qt WebEngine и QWebChannel?
Я использую новый WebEngine, чтобы тренироваться и учиться. Я пытался найти некоторые похожие методы, найденные с помощью Qt WebKit: addToJavaScriptWindowObject()
Я обнаружил, что используя Qt WebEngine, я должен использовать QWebChannel
зарегистрировать функции для объекта окна Javascript. Если это правильно, это подводит меня к следующему вопросу.
Я установил Qt 5.4.0 на свой компьютер. Я заметил, что qwebchannel.js
не найден в SDK, установленном на моем компьютере. Я нашел это в источнике Git.
Если у меня есть родное настольное приложение Qt с QWebEnginePage
а также QWebEngineView
Что мне нужно, чтобы иметь возможность зарегистрировать функции на объект окна Javascript?
Мое настольное приложение автоматически переходит на созданную мной http-страницу. Таким образом, у меня есть доступ к контенту, связанному с QWebEngineView
,
Какие шаги нужно предпринять, чтобы я мог сделать эту работу?
5 ответов
В Qt5.6, если вы хотите сделать C++ частью и JavaScript для взаимодействия, единственный способ сделать это - использовать QWebChannel в QWebEngineView, как вы сказали. Вы делаете это таким образом в .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);
И теперь вы получаете int как это на стороне JS:
var myIntInJSside= JSobject.myIntInCppSide;
Это очень простое объяснение, и я рекомендую вам посмотреть это видео, которое было очень полезно для меня. Также вам может понадобиться больше узнать об API JavaScript, предоставляемом QWebChannel, а также о документации по QWebChannel.
Надеюсь, это поможет!
Я резюмирую ваши вопросы следующим образом:
- Нужен ли мне QWebChannel для регистрации функций JavaScript в WebEngine?
- Где найти QWebChannel.js
- Как передать JS в C++ и C++ в JS
Во-первых, возьмем простой код для игры:
#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
ed внутри, и событие сигнала / слота подключается для печати некоторого журнала, когда страница наконец загружается.
Как вызывать JS-функции из C++?
Вы можете просто вызвать JS, используя QWebEnginePage::runJavaScript
следующим образом. Добавьте этот код в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 в браузере с идентификатором контекста. 42
, избегая столкновения с контекстом по умолчанию идентификатора страницы 0
. Скрипт меняет цвет фона каждой ссылки на желтый.
Как вызвать C++ из JS?
В этом случае нам нужен механизм 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"
Explanation: Этот код объявляет и определяет класс QObject с простым log
функция внутри. Важно объявить функциюQ_INVOKABLE
иначе JavaScript не сможет его найти!. Поскольку объявление находится внутри того же файла, что и остальной код, мы включаем файл auto-moc из QT после (это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/shared/qwebchannel.js). Вы можете загрузить это прямо на свою страницу.
В DECLARATION
раздел, добавьте:
QString qWebChannelJs()
{
return R"DELIMITER(
// COPY HERE ALL THE FILE
)DELIMITER";
}
И мы вводим его в наш код (добавляем в TEST CODE HERE
раздел):
browser.page()->runJavaScript(qWebChannelJs(), 42);
Нам нужно настроить QWebChannel
на стороне C++ (SETUP
раздел):
QWebChannel channel;
JsInterface jsInterface;
browser.page()->setWebChannel(&channel, 42);
channel.registerObject(QString("JsInterface"), &jsInterface);
Пояснение: Создаем канал, JsInterface
объект и зарегистрируйте их в браузере. Нам нужно использовать тот же идентификатор контекста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
выполняется.
Полный код
#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();
}
Похожие темы:
Как настроить QWebChannel JS API для использования в QWebEngineView?
Внешняя документация:
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
Вы должны добавить QWebSocketServer
к вашему приложению cpp, что QWebEngineView
HTML/Javascript соединится с использованием WebSocket. Тогда используйте QWebChannel
для двухстороннего общения.
Альтернативный и гораздо более простой способ связи со страницей - использовать runJavaScript
функция:
view->page()->runJavaScript("alert('Hello from C++');");
У него есть свои ограничения: вызов должен быть инициирован со стороны C++, и вы можете получить только синхронный ответ от JS. Но есть и положительный момент: не требуется никаких изменений основной веб-страницы.
Открыть текущую открытую веб-страницу можно с помощью QWebEngineView::page()
функция, как в примере выше. Во время навигации браузер не меняет страницу до тех пор, пока из сети не будет получена следующая, поэтому эта функция в любое время возвращает действительный объект страницы. Но ваш JS может по-прежнему прерывать загрузку новой страницы таким образом, что вы будете отображаться вdocument.readyState == 'loading'
где дерево DOM еще не построено, и некоторые сценарии на странице могли еще не быть запущены. В этом случае вам следует дождатьсяDOMContentLoaded
событие.
Я пробовал ваш пример, и все работает нормально. Но я нашел кое-что интересное. Если бы у меня было условие, QWebChannel, похоже, не работает (от JS до C++).
Вот пример (на основе вашего источника):
QWebEngineView * browser = new QWebEngineView;
browser->resize(QSize(800,600));
browser->show();
browser->load(QUrl("http://www.wikipedia.org"));
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);
}
Этот работает. У меня вывод интерфейса
LOG from JS: Hello from JavaScript
.
Но если я добавлю условие тестирования, оно не сработает:
bool testwebchannel_main = true;
if ( testwebchannel_main )
{
QWebEngineView * browser = new QWebEngineView;
browser->resize(QSize(800,600));
browser->show();
browser->load(QUrl("http://www.wikipedia.org"));
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);
}
}
Ничего со стороны JS .. JSinterface не вызывается.
Есть идеи, почему это приложение?