Как использовать 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.

Надеюсь, это поможет!

Я резюмирую ваши вопросы следующим образом:

  1. Нужен ли мне QWebChannel для регистрации функций JavaScript в WebEngine?
  2. Где найти QWebChannel.js
  3. Как передать 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 и устанавливает некоторые минимальные свойства, чтобы сделать его видимым. Страница из "Википедии"loaded внутри, и событие сигнала / слота подключается для печати некоторого журнала, когда страница наконец загружается.

Как вызывать 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, что QWebEngineViewHTML/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 не вызывается.

Есть идеи, почему это приложение?

Другие вопросы по тегам