1. 程式人生 > >實現QT與HTML頁面通訊

實現QT與HTML頁面通訊

1.  前言

最近,C++和WEB本地混合應用開發模式逐漸流行起來,個人也認為標記語言描述的介面是介面開發的一個發展趨勢。WPF、Java FX,當然也少不了Html。基於Html的介面在開發效率,可移植性上都十分有優勢,所以也被很多程式採用

隨著HTML5技術風生水起,Qt開發團隊用近一年的時間開發了一個全新的基於Chromium的瀏覽器引擎Qt WebEngine,以支援面向未來的Hybrid應用開發,並完全支援桌面和嵌入式平臺。此外,Qt WebEngine不僅提供了易於使用的跨平臺API,還完全集成了Qt的圖形庫,允許網頁內容進行疊加,並與Qt使用者介面或OpenGL圖形效果混合。Qt 5.4仍然包含老舊的Qt WebKit模組,但在其後釋出的版本中將停止對於WebKit的支援,對此,Qt團隊建議開發者將專案統一從WebKit遷移至Qt WebEngine,而對於需要Web能力的新專案,最好直接採用Qt WebEngine開發。在現在下載的QT5.6 beta預編譯版本中已經沒有了Qt WebKit模組。


目前QT官方的文件中對如何從原來的WebKit遷移至QtWebEngine沒有提供足夠豐富的文件和指導。

本文記錄一些從WebKit遷移至QtWebEngine,實現C++與HTML和JS互動的一些經驗和例子。

2.  使用Qt WebEngine和WebChannel模組

在官方提供的Porting from QtWebKit to QtWebEngine---https://wiki.qt.io/Porting_from_QtWebKit_to_QtWebEngine文件中給出了從WebKit遷移至QtWebEngine的一些建議和指導,其中提到QWebFrame 已經被合併到QwebEnginePage中,Qt WebEngine不能和QnetworkAccessManager互動等。

原來在Webkit中使用的互動整合的類和方法也不能使用了,原來Webkit中常用的互動方式如下:

1)    Qt程式碼中載入並顯示該頁面

QWebView view; 

view.load(QUrl("test.html")); 

view.show();

官方推薦的使用方式:

    view->load(QUrl("http://qt-project.org/"));

   view->show();

2)    Qt物件中訪問web頁面元素

// myPlugin指向的物件可在HTML中用名字myPluginObject進行訪問

webView->page()->mainFrame()->addToJavaScriptWindowObject("myPluginObject",myPlugin); 

// 當訊號signalEmitted被觸發時,呼叫JavaScriptfunctionToCall函式

webView->page()->mainFrame()->evaluateJavaScript("myPluginObject.signalEmitted.connect(functionToCall);")

官方推薦的使用方式:

方法一:(const QString & scriptSource, FunctorOrLambdaresultCallback)

方法二:使用QtWebChannel方式,這是官方的推薦方式,他可以很方便的實現C++和HTML/JS的雙向通訊,同時實現C++和HTML/JS的解耦,方便開發人員的分工及系統整合,參見後面的例子。

3)    web頁面中訪問Qt物件

web頁面中可以通過類似於下的JavaScript程式碼訪問Qt物件:

<ahref="javascript:document.getElementByIdx_x("myLabel").setText("通過JavaScript訪問Qt物件");"mce_href="javascript:document.getElementByIdx_x("myLabel").setText("通過JavaScript訪問Qt物件");">點選訪問Qt物件</a>

官方推薦的使用方式:

使用QtWebChannel方式,這是官方的推薦方式,他可以很方便的實現C++和HTML/JS的雙向通訊,同時實現C++和HTML/JS的解耦,方便開發人員的分工及系統整合,參見後面的例子。

在QT5.5和QT5.6中,利用Qt的Qt WebEngine和WebChannel模組,你完全可以進行本地桌面與web混合應用開發,你可以自由地混合JavaScript,樣式表,Web內容和Qt元件。基於Chromium Qt WebEngine是一個非常成熟的web瀏覽引擎。你可以在C++ 中執行JavaScript,或者在網頁中整合C++物件,並且通過JavaScript和這些物件進行互動。

一個現代的HTML渲染引擎只是混合開發的一半,另一半就是本地應用和渲染物件的互動。QT的Chromium Qt WebEngine 整合提供了這種解決方案,當然目前還不是很完善。

從QT5.4開始就新增了Qt WebChannel模組,該模組提供了在QML/C++ 和 HTML/Javascript之間的一個簡單、易用的橋接,從而使得開發能夠使用Qt和Web技術進行混合開發,目前QT官方也推薦是用QtWebChannel來橋接C++和HTML,參見Qt WebChannel – bridging the gap betweenC++/QML and the web------https://www.youtube.com/watch?v=KnvnTi6XafA,這是2014年Qt開發者大會上的一段視屏演講。

通過QtWebChannel能夠實現C++/QML和HTML/javascript客戶端之間進行無縫橋接,目前主要支援兩種方式的橋接。

  • l  客戶機可以是任何支援WebSockets的JavaScript runtime機器和應用,客戶機可以是獨立的應用或瀏覽器。也就是說QtWebChannel是基於WebSockets協議之上。
  • l  通過IPC的方式實現C++/QML和HTML/javascript客戶端之間進行通訊。在QT應用內嵌入HTML或JS頁面的情況下使用基於IPC的WebChannel通訊效率更高效。

在官方演進路線中提到將來的整合解決方案將支援以下特性:

  • l  使用object標籤嵌入Qt Widgets元件。這可以讓使用C++程式碼的Qt元件包含在網頁中,作為網頁的部分外觀。
  • l  在JavaScript中訪問C++物件。你可以在JavaScript環境中插入C++物件,讓網頁尾本直接訪問你的資料結構。
  • l  在Qt中執行JavaScript。你可以在C++呼叫網頁環境中的JavaScript函式,觸發網頁事件。
  • l  共享客戶端儲存。在JavaScript和C++中你都具有訪問資料庫的能力,這樣當下線時也能共享大量資料。

3.  QtWebChannel實現C++和HTML/javascript頁面之間通訊例項

在例項中在一個介面裡實現整合QT C++元件和QWebEnginePage中嵌入的HTML頁面通訊,左邊的lineedit元件中輸入的內容通過一個document物件又QtWebChannel傳到web頁面,並又js解析在HTML頁面上顯示,右邊HTML頁面中的INPUT標籤中輸入的內容也由document物件通過QtWebChannel傳到QT C++介面。通過QtWebChannel使得JS能夠接收到QT C++中傳出的物件,及由C++發出的訊號,並與相對應的方法關聯,C++也能介紹到由JS傳來的物件,並呼叫相應的槽函式。

 

工程清單如下:


首先,定義一個document物件用於在C++和JS之間傳遞,該物件實現了訊號和槽:

Document.h內容:

#ifndefDOCUMENT_H
#defineDOCUMENT_H
#include<QObject>
#include<QString>
#include"ui_mainwidget.h"
namespaceUi{
classMainWidget;
}
classDocument:publicQObject
{
Q_OBJECT
Q_PROPERTY(QStringtextMEMBERs_textNOTIFYsendText)
public:
explicitDocument(QObject*parent=nullptr):QObject(parent){}
voidsetSendTextText(constQString&text);
voidsetUi(Ui::MainWidget*ui);
publicslots:
voidreceiveText(constQString&r_text);
signals:
voidsendText(constQString&text);
private:
voiddisplayMessage(constQString&message);
QStrings_text;
QStringrecieve_text;
Ui::MainWidget*mainUi;
};
#endif//DOCUMENT_H

Document.cpp內容:

#include"document.h"
voidDocument::setSendTextText(constQString&text)
{
s_text=text;
emitsendText(s_text);
}
voidDocument::displayMessage(constQString&message)
{
mainUi->editor->appendPlainText(message);
}
/*!
ThisslotisinvokedfromtheHTMLclientsideandthetextdisplayedontheserverside.
*/
voidDocument::receiveText(constQString&r_text)
{
displayMessage(QObject::tr("Receivedmessage:%1").arg(r_text));
}
voidDocument::setUi(Ui::MainWidget*ui)
{
mainUi=ui;

}

用QT Designer設計主介面,並實現主介面MainWidget類,內容如下:

mainwidget.h內容:

#ifndefMAINWIDGET_H
#defineMAINWIDGET_H
#include"document.h"
#include<QWidget>
#include<QString>
namespaceUi{
classMainWidget;
}
classMainWidget:publicQWidget
{
Q_OBJECT
public:
explicitMainWidget(QWidget*parent=0);
~MainWidget();
//publicQ_SLOTS:
//voidsetEnabled(bool);
privateslots:
voidon_pushButton_clicked();
private:
boolisModified()const;
Ui::MainWidget*ui;
Documentm_content;
};
#endif//MAINWIDGET_H

mainwidget.cpp內容:

#include"mainwidget.h"
#include"ui_mainwidget.h"
#include"previewpage.h"
#include"document.h"
#include<QFile>
#include<QWebChannel>
MainWidget::MainWidget(QWidget*parent):
QWidget(parent),
ui(newUi::MainWidget)
{
ui->setupUi(this);
PreviewPage*page=newPreviewPage(this);
ui->preview->setPage(page);
m_content.setUi(ui);
QWebChannel*channel=newQWebChannel(this);
channel->registerObject(QStringLiteral("content"),&m_content);
page->setWebChannel(channel);
ui->preview->setUrl(QUrl("qrc:/index.html"));
ui->editor->setPlainText("hello...\n");
}
MainWidget::~MainWidget()
{
deleteui;
}
boolMainWidget::isModified()const
{
returnui->editor->document()->isModified();
}
voidMainWidget::on_pushButton_clicked()
{
m_content.setSendTextText(ui->lineEdit->text());
}

再定義一個PreviewPage類用於載入HTML頁面,在主介面MainWidget類初始化的時候,將他主介面中的WebEngineView初始化為該例項物件,主要初始化程式碼如下:

PreviewPage*page=newPreviewPage(this);  //建立例項物件

ui->preview->setPage(page);                 //將物件設定到主介面

ui->preview->setUrl(QUrl("qrc:/index.html"));                               //設定載入的HTML頁面

previewpage.h內容:

#ifndefPREVIEWPAGE_H
#definePREVIEWPAGE_H
#include<QWebEnginePage>
classPreviewPage:publicQWebEnginePage
{
Q_OBJECT
public:
explicitPreviewPage(QObject*parent=nullptr):QWebEnginePage(parent){}
protected:
boolacceptNavigationRequest(constQUrl&url,NavigationTypetype,boolisMainFrame);
};
#endif//PREVIEWPAGE_H

previewpage.cpp內容:

#include"previewpage.h"

#include<QDesktopServices>

boolPreviewPage::acceptNavigationRequest(constQUrl&url,

QWebEnginePage::NavigationType/*type*/,

bool/*isMainFrame*/)

{

//Onlyallowqrc:/index.html.

if(url.scheme()==QString("qrc"))

returntrue;

QDesktopServices::openUrl(url);

returnfalse;

}

使用的web頁面index.html內容如下:

<!DOCTYPEhtml>

<html>

<head>

<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>

<scripttype="text/javascript"src="./qwebchannel.js"></script>

<scripttype="text/javascript">

//BEGINSETUP

functionoutput(message)

{

varoutput=document.getElementById("output");

output.innerHTML=output.innerHTML+message+"\n";

}

window.onload=function(){

output("settingupQWebChannel.");

newQWebChannel(qt.webChannelTransport,function(channel){

//makedialogobjectaccessibleglobally

varcontent=channel.objects.content;

document.getElementById("send").onclick=function(){

varinput=document.getElementById("input");

vartext=input.value;

if(!text){

return;

}

output("Sentmessage:"+text);

input.value="";

content.receiveText(text);

}

content.sendText.connect(function(message){

output("Receivedmessage:"+message);

});

content.receiveText("Clientconnected,readytosend/receivemessages!");

output("ConnectedtoWebChannel,readytosend/receivemessages!");

});

}

//ENDSETUP

</script>

<styletype="text/css">

html{

height:100%;

width:100%;

}

#input{

width:400px;

margin:010px00;

}

#send{

width:90px;

margin:0;

}

#output{

width:500px;

height:300px;

}

</style>

</head>

<body>

<textareaid="output"></textarea><br/>

<inputid="input"/><inputtype="submit"id="send"value="Send"onclick="javascript:click();"/>

</body>

</html>

主程式main.cpp的內容如下:

#include"document.h"

#include"mainwidget.h"

#include<QApplication>

intmain(intargc,char*argv[])

{

QApplicationa(argc,argv);

MainWidgetw;

w.show();

returna.exec();

}