一. 介紹
在瀏覽器擴展或者WebApp的項目經常用的腳本語言javascript有很多局限性,比如,javascript語言不能夠誇窗口訪問js對象,不能直接讀寫磁盤文件(這個也正是發明人設計的安全機制吧,要不然,誰還敢用瀏覽器啊,幾行代碼就可以把你偷窺的一覽無余),我們可能在我們的程序中需要擴展這個功能。
那么,我們怎么解決這些問題呢?或許你可以參考一下下面的設計架構。
UI利用Html + CSS + JavaScript編寫,核心業務層利用C++編寫,C++和JavaScript對象主要通過WebKit通信。讓相應的語言做它們擅長的事情,這就是這種架構的核心。
WebKit又是什么呢?簡單言之就是一種瀏覽器內核引擎,Safari、Chrome、FireFox等瀏覽器都采用WebKit引擎作為內核,由此可見WebKit的地位了,我們正是利用WebKit來做javascript的擴展的,Qt界又對WebKit做了一層封裝,這樣,開發者對WebKit就更加容易上手了。
更幸運的是,QtWebKit提供了一種將QObject對象擴展到Javascript運行環境的機制,這樣,JavaScript代碼將有權限訪問QObject對象, QObject對象的所有屬性也能在Javascript上下文中被訪問。
那么,如何利用QtWebKit使得js和c++相互通信呢?
二. 搭建框架
首先,我們需要一個js代碼能夠運行的環境
我們准備一個主窗口,在主窗口內部添加WebView控件,使得程序具有執行js的能力;
MainWindow的定義
- #include <QMainWindow>
- #include <QGraphicsView>
- #include <QGraphicsWebView>
- #include <QGraphicsScene>
- #include <QEvent>
- #include "External.h"
- class MainWindow : public QGraphicsView
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = 0);
- virtual ~MainWindow();
- private:
- QGraphicsWebView* m_pWebView;
- QGraphicsScene* m_pScene;
- };
- #endif // MAINWINDOW_H
MainWindow的實現
- #include "mainwindow.h"
- #include <QWebFrame>
- #include <QLayout>
- MainWindow::MainWindow(QWidget *parent)
- : QGraphicsView(parent)
- {
- this->resize( QSize( 800, 600) );
- m_pScene = new QGraphicsScene(this);
- if(NULL != m_pScene)
- {
- m_pWebView = new QGraphicsWebView;
- if( NULL != m_pWebView)
- {
- //enabled javascript
- QWebSettings *settings = m_pWebView->page()->settings();
- settings->setAttribute(QWebSettings::JavascriptEnabled,true);
- settings->setAttribute(QWebSettings::JavascriptCanAccessClipboard,true);
- settings->setAttribute(QWebSettings::DeveloperExtrasEnabled,true);
- settings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
- settings->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
- settings->setAttribute(QWebSettings::JavascriptCanCloseWindows, true);
- settings->setAttribute(QWebSettings::AutoLoadImages,true);
- m_pScene->addItem( m_pWebView );
- this->setScene( m_pScene );
- }
- }
- }
- MainWindow::~MainWindow()
- {
- }
QWebSettings類是用於配置Web運行環境,我們首先配置了JavaScriptEnable=true,使得這個web環境能夠運行js代碼,其他的選項不是必要的。
三. 添加QObject到js上下文
利用QWebFrame提供的方法我們可以很輕松的完成添加對象:
void addToJavaScriptWindowObject(const QString &name, QObject *object, ValueOwnership ownership = QtOwnership);
注解:
addToJavaScriptWindowObject這個方法可以使c++對象object在js的上下中以name為名字而出現,object對象將被當作這個QWebFrame的一個子對象,這樣,我們就可以把這個對象當作js的一個對象來使用了;
object對象的屬性和槽都作為js方法在js上下文中展開,因此,js拿到這個對象就可以很隨意的訪問這個對象的屬性和方法;
當這個頁面析構后,這個對象在js上下文中也將消失,監聽到信號javaScriptWindowObjectCleared() 后來重新調用下addToJavaScriptWindowObject即可。
我們先寫一個測試頁面index.html,主要功能就是測試Js是否能夠訪問external對象;
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>QWebKitDemo</title>
- <script type="text/javascript">
- window.onload = function(){
- var btnTest = document.getElementById("testCObj");
- btnTest.onclick = function() {
- alert(external);
- }
- }
- </script>
- </head>
- <body>
- <input type="button" id="testCObj" value="測試C++對象">
- </body>
- </html>
我們先來定義一個繼承自QObject的類External,其中,繼承自QObject很關鍵,否則我們無法完成我們的功能;
External類的定義,這里我定義了一個帶屬性、信號、槽、帶Q_INVOKABLE修飾的方法,這將在其他小節會用到
- #ifndef EXTERNAL_H
- #define EXTERNAL_H
- #include <QObject>
- class External : public QObject
- {
- Q_OBJECT
- Q_PROPERTY(QString msg READ getMsg WRITE setMsg) // 聲明靜態屬性msg
- public:
- explicit External(QObject *parent = 0);
- signals:
- void mouseClicked();
- public slots:
- void TestPassObject(QObject*);
- public:
- QString getMsg() const { return msg; }
- void setMsg( const QString& strMsg ) { msg = strMsg; }
- // 提供給Javascript方法,需要用Q_INVOKABLE修飾
- Q_INVOKABLE int VerifyUserAccount(const QString& userName, const QString& userPwd);
- Q_INVOKABLE QString GetPropMsg();
- Q_INVOKABLE void TestPassObjectToNative(QObject*);
- QString msg;
- };
- #endif // EXTERNAL_H
我們為MainWindow類添加成員External對象指針
- External* m_pExternal;
在MainWindow的構造方法中對m_pExternal實例化,並為這個頁面的javaScriptWindowObjectCleared信號關了槽函數AddJavascriptWindowObject
這樣,我們的MainWindow.h和MainWindow.cpp就成這樣子了:
MainWindow.h
- #include <QMainWindow>
- #include <QGraphicsView>
- #include <QGraphicsWebView>
- #include <QGraphicsScene>
- #include <QEvent>
- #include "External.h"
- class MainWindow : public QGraphicsView
- {
- Q_OBJECT
- public:
- MainWindow(QWidget *parent = 0);
- virtual ~MainWindow();
- public slots:
- void AddJavascriptWindowObject();
- private:
- QGraphicsWebView* m_pWebView;
- QGraphicsScene* m_pScene;
- External* m_pExternal;
- };
- #endif // MAINWINDOW_H
MainWindow.cpp
- #include "mainwindow.h"
- #include <QWebFrame>
- #include <QLayout>
- MainWindow::MainWindow(QWidget *parent)
- : QGraphicsView(parent)
- {
- this->resize( QSize( 800, 600) );
- m_pExternal = new External();
- m_pScene = new QGraphicsScene(this);
- if(NULL != m_pScene)
- {
- m_pWebView = new QGraphicsWebView;
- if( NULL != m_pWebView)
- {
- //enabled javascript
- QWebSettings *settings = m_pWebView->page()->settings();
- settings->setAttribute(QWebSettings::JavascriptEnabled,true);
- settings->setAttribute(QWebSettings::JavascriptCanAccessClipboard,true);
- settings->setAttribute(QWebSettings::DeveloperExtrasEnabled,true);
- settings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);
- settings->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);
- settings->setAttribute(QWebSettings::JavascriptCanCloseWindows, true);
- settings->setAttribute(QWebSettings::AutoLoadImages,true);
- m_pScene->addItem( m_pWebView );
- this->setScene( m_pScene );
- connect(m_pWebView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()),
- this, SLOT(AddJavascriptWindowObject()));
- m_pWebView->setUrl(QUrl("file:///Users/cblogs_code/QWebKitDemo/index.html"));
- }
- }
- }
- MainWindow::~MainWindow()
- {
- }
- void MainWindow::AddJavascriptWindowObject()
- {
- m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("external", m_pExternal);
- }
m_pWebView->setUrl(QUrl("file:///Users/cblogs_code/QWebKitDemo/index.html"));
這一行是加載index.html,請嘗試的同學自行修改成自己的index.html的絕對路徑。
當網頁加載時,會自動觸發javaScriptWindowObjectCleared信號,繼而我們的槽函數AddJavascriptWindowObject會被執行,上面關於AddJavaScriptWindowObject解釋說:當這個頁面析構后,這個對象在js上下文中也將消失,監聽到信號javaScriptWindowObjectCleared() 后來重新調用下addToJavaScriptWindowObject即可,因此,想讓external一直保持有效,這樣做就可以了。
然后,運行一下程序吧,點擊按鈕,你會看到如下結果:
三. 調用QObject的方法
- var ret = external.VerifyUserAccount("user01", "123456");
- if (ret == 1) {
- alert("驗證通過");
- }else {
- alert("用戶名或者密碼錯誤");
- }
這節我們來嘗試訪問一下External對象的msg屬性,並且修改TA,看看在Js層和C++層是否被修改。
還是先看看測試頁面吧:
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>QWebKitDemo</title>
- <script type="text/javascript">
- window.onload = function(){
- var btnTest = document.getElementById("testCObj");
- btnTest.onclick = function() {
- alert(external);
- }
- var btnVisit = document.getElementById("visitProp");
- btnVisit.onclick = function() {
- var output = "js層面 \tmsg: "+external.msg + "\n c++層面 \tmsg: "+external.GetPropMsg();
- alert(output);
- }
- var btnSetProp = document.getElementById("setProp");
- btnSetProp.onclick = function() {
- var tempValue = document.getElementById("msgProValue");
- external.msg = tempValue.value;
- }
- }
- </script>
- </head>
- <body>
- <input type="button" id="testCObj" value="測試C++對象">
- <p>測試屬性msg</p>
- <input type="button" id="visitProp" value="visit">
- <input type="text" id="msgProValue" value="修改后的msg">
- <input type="button" id="setProp" value="修改">
- </body>
- </html>
運行程序,我們先點擊visit按鈕,可以看到,顯示的msg屬性都是空值,然后再文本框中輸入字符,點擊修改按鈕,再點擊visit按鈕,試試看,彈出的msg是否有變化。
四. 綁定QObject對象的信號/槽
addJavaScriptWindowObject方法可以將對象的屬性、信號、槽統統映射到Js上下文中。
綁定信號的方式如下
- external.mouseClicked.connect(mouseClickedSlot);
- function mouseClickedSlot() {
- logger("mouse clicked");
- }
mouseClicked通過connect方法連接到js的方法(槽),當mouseClicked被觸發后,mouseClickedSlot將被執行
五. 為iframe添加QObject
- void frameCreated(QWebFrame*);
- connect(this->m_pWebView->page(), SIGNAL(frameCreated(QWebFrame*)),
- this, SLOT(ChildFrameCreated(QWebFrame*)));
- public slots:
- void AddJavascriptWindowObject();
- void ChildFrameCreated(QWebFrame *frame);
- void MainWindow::AddJavascriptWindowObject()
- {
- m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("external", m_pExternal);
- }
- void MainWindow::AddJavascriptWindowObject(QWebFrame *pFrame)
- {
- qDebug("AddJavascriptWindowObject");
- //add external object to main web frame
- pFrame->addToJavaScriptWindowObject("external", m_pExternal);
- }
- void MainWindow::ChildFrameCreated(QWebFrame *pFrame)
- {
- qDebug("ChildFrameCreated");
- if(pFrame == NULL)
- {
- qDebug("Child frame created, but it was NULL!");
- }
- else
- {
- AddJavascriptWindowObject(pFrame);
- }
- }
六. 總結
1、優勢:
- 高效率開發UI豐富的應用;
- 底層框架可復用;
- 可實現跨平台;
- 其他好處……
2、劣勢:
- 性能
- 還是性能
- 其他劣勢……