Qt與H5的混合編程
自從Qt WebKit 被 Qt WebEngine替換后,就無法從C++ 直接訪問html的元素了。以往很多通過WebKit 類來對網頁內容的操作,要改為用JavaScript了。QWebEnginView加載網頁后,要從C++ 代碼中調用runJavaScript方法來調用頁面的javascript腳本。Qt發明了一種C++ 和Javascript 的溝通機制,這種機制不僅能從頁面獲得元素,也能實現:從C++ 調用Js方法,從js調用C++ 的方法,從C++ 傳遞變量給js,從js傳遞變量給C++ 。以下是應用場景。
1. js調用C++函數
C++的代碼需要實現一個類,類里包含將被js調用方法,且這個方法必須做為插槽。然后將這個類的對象注冊到類QWebChannel的對象中,再將這個QWebChannel的對象賦值給QWebEngineView類的對象的page()方法。代碼如下:
- class WebClass : public QObject
- {
- Q_OBJECT
- public slots:
- void jscallme()
- {
- QMessageBox::information(NULL,"jscallme","I'm called by js!");
- }
- };
- WebClass *webobj = new WebClass();
- QWebChannel *channel = new QWebChannel(this);
- channel->registerObject("webobj", webobj);
- view->page()->setWebChannel(channel);
js端應該實例化一個QWebChannel來調用C++的jscallme方法。代碼如下
- new QWebChannel(qt.webChannelTransport,
- function(channel){
- var webobj = channel.objects.webobj;
- window.foo = webobj;
- });
QWebChannel 是在qwebchannel.js中定義的,所以這個腳本應該首先在頁面中加載。這個文件可在qt安裝路徑下的example目錄找到,你也可以從文末的demo中獲得。在上面的代碼中,作為第二個參數傳入的function里,C++ 的對象(channel.objects.webobj)賦值給了js的變量webobj,然后又將其賦值給了window.foo,以便你在其它地方可以引用這個C++ 的對象。這段代碼執行后,js可以象下面這樣調用C++ 的槽函數了:
foo.jscallme();
2. js傳數據給C++
既然知道了如何調用C++ 的方法,那就應該找到給C++ 傳值的方法,比如,給函數傳參。我們將C++ 端的jscallme方法做如下改造:
- void jscallme(const QString &datafromjs)
- {
- QMessageBox::information(NULL,"jscallme","I'm called by js!");
- m_data=datafromjs;
- }
然后在js里的調用代碼為:
foo.jscallme(somedata);
注意參數前的const不能省略,否則會引發如下錯誤:
Could not convert argument QJsonValue(string, “sd”) to target type .
雖然數據可以做為函數的參數傳遞,但如果我們象下面的js這樣給屬性賦值會更方便,也符合習慣:
foo.someattribute="somedata";
當代碼執行后,我們期望somedata這個值會被賦值給C++ 類對象所暴露出來的的成員變量中。這可以通過在C++ 的類中聲明一個qtproperty來實現:
- class WebClass : public QObject
- {
- Q_OBJECT
- Q_PROPERTY(QString someattribute MEMBER m_someattribute)
- public slots:
- void jscallme()
- {
- QMessageBox::information(NULL,"jscallme","I'm called by js!");
- }
- private:
- QString m_someattribute;
- };
這樣你就可以在js中執行foo.someattribute="somedata",然后C++對象中的m_someattribute的值就變為"somedata"了。
3. C++傳數據給js
可以通過信號將數據從C++ 傳給js。我們觸發一個帶參數的信號,這個參數就是我們將要傳遞的數據。js必須將這個信號連接到一個function以接收這個數據。
- class WebClass : public QObject
- {
- Q_OBJECT
- Q_PROPERTY(QString someattribute MEMBER m_someattribute)
- public slots:
- void jscallme()
- {
- QMessageBox::information(NULL,"jscallme","I'm called by js!");
- }
- void setsomeattribute(QString attr)
- {
- m_someattribute=attr;
- emit someattributeChanged(m_someattribute);
- }
- signals:
- void someattributeChanged(QString & attr);
- private:
- QString m_someattribute;
- };
javascript代碼:
- var updateattribute=function(text)
- {
- $("#attrid").val(text);
- }
- new QWebChannel(qt.webChannelTransport,
- function(channel){
- var webobj = channel.objects.webobj;
- window.foo = webobj;
- webobj.someattributeChanged.connect(updateattribute);
- });
這行代碼webobj.someattributeChanged.connect(updateattribute) 將C++的信號函數someattributeChanged 和js的函數updateattribute連接起來了。注意,updateattribute只接收了一個text參數,而且沒有在connet方法中提供這個參數值。實際上,在信號接收到這個值前,我們都不知道傳遞給updateattribute的參數值。信號伴隨其參數"attr"出現,而其參數會傳遞給updateattribute的參數"text"。這時,如果調用 webobj->setsomeattribute(“hello”),就會在html端看到id為 “#attrid” 的html元素的值被改為“hello”。雖然在上面的例子中,將變量m_someattribute綁定到了qt的屬性someattribute,但這其實並不是必須的。信號機制自己就能發現傳遞的數據。
這里可以在聲明someattribute屬性時,通過添加NOTIFY 參數來簡化一些事情。
- class WebClass : public QObject
- {
- Q_OBJECT
- Q_PROPERTY(QString someattribute MEMBER m_someattribute NOTIFY someattributeChanged)
- public slots:
- void jscallme()
- {
- QMessageBox::information(NULL,"jscallme","I'm called by js!");
- }
- signals:
- void someattributeChanged(QString & attr);
- private:
- QString m_someattribute;
- };
這樣,在某段C++ 代碼中調用 weobj->setProperty("someattribute","hello") 時,信號“someattributeChanged”就會自動觸發,並且我們的頁面也會得到更新。
4. C++調用js的function
相對於從js中調用C++ 方法,從C++ 中調用js的function顯得更直接一些。直接運行“runJavaScript”方法並將function作為參數傳遞,如下:
view->page()->runJavaScript("jsfun();",[this](const QVariant &v) { qDebug()<<v.toString();});
假定jsfun已經在頁面中定義了,否則,應該直接在字符串參數中定義。返回值異步傳遞給了作為參數v的lambda表達式。
現在回到文章開始提出的問題:在C++中如何獲得頁面的元素?答案如下:
- view->page()->runJavaScript("function getelement(){return $('#elementid').val();} getelement();",[this](const QVariant &v) { qDebug()<<v.toString();});
腳本中用了jQuery方法,所以必須保證jQuery在頁面中正常被引入。當然,也可以使用其它js框架,例如vue。
示例下載 (代碼是Qt在Deepin 下實現的,Windows用戶請自行轉換工程)
本文參考:Communication between C++ and Javascript in Qt WebEngine