[Qt] 利用QtWebKit完成JavaScript訪問C++對象


一. 介紹         

 在瀏覽器擴展或者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的定義

 
  1. #include <QMainWindow>  
  2. #include <QGraphicsView>  
  3. #include <QGraphicsWebView>  
  4. #include <QGraphicsScene>  
  5. #include <QEvent>  
  6.   
  7. #include "External.h"  
  8.   
  9. class MainWindow : public QGraphicsView  
  10. {  
  11.     Q_OBJECT  
  12. public:  
  13.     MainWindow(QWidget *parent = 0);  
  14.     virtual ~MainWindow();  
  15.   
  16. private:  
  17.     QGraphicsWebView*   m_pWebView;  
  18.     QGraphicsScene*     m_pScene;  
  19. };  
  20.   
  21. #endif // MAINWINDOW_H  


MainWindow的實現

 

 
  1. #include "mainwindow.h"  
  2.   
  3. #include <QWebFrame>  
  4. #include <QLayout>  
  5.   
  6. MainWindow::MainWindow(QWidget *parent)  
  7.     : QGraphicsView(parent)  
  8. {  
  9.     this->resize( QSize( 800, 600) );  
  10.       
  11.     m_pScene = new QGraphicsScene(this);  
  12.     if(NULL != m_pScene)  
  13.     {  
  14.         m_pWebView = new QGraphicsWebView;  
  15.   
  16.         if( NULL != m_pWebView)  
  17.         {  
  18.             //enabled javascript  
  19.             QWebSettings *settings = m_pWebView->page()->settings();  
  20.   
  21.             settings->setAttribute(QWebSettings::JavascriptEnabled,true);    
  22.             settings->setAttribute(QWebSettings::JavascriptCanAccessClipboard,true);  
  23.             settings->setAttribute(QWebSettings::DeveloperExtrasEnabled,true);  
  24.             settings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);  
  25.             settings->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);  
  26.             settings->setAttribute(QWebSettings::JavascriptCanCloseWindows, true);  
  27.             settings->setAttribute(QWebSettings::AutoLoadImages,true);  
  28.   
  29.             m_pScene->addItem( m_pWebView );  
  30.             this->setScene( m_pScene );  
  31.         }  
  32.     }   
  33. }  
  34.   
  35. MainWindow::~MainWindow()  
  36. {  
  37.   
  38. }  

 

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對象;

 

 
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  5. <title>QWebKitDemo</title>  
  6. <script type="text/javascript">  
  7.   
  8. window.onload = function(){  
  9.   
  10.     var btnTest = document.getElementById("testCObj");  
  11.     btnTest.onclick = function() {  
  12.         alert(external);  
  13.     }  
  14. }  
  15. </script>  
  16. </head>  
  17. <body>  
  18.     <input type="button" id="testCObj" value="測試C++對象">  
  19. </body>  
  20. </html>  

 

 

我們先來定義一個繼承自QObject的類External,其中,繼承自QObject很關鍵,否則我們無法完成我們的功能;

External類的定義,這里我定義了一個帶屬性、信號、槽、帶Q_INVOKABLE修飾的方法,這將在其他小節會用到


 

 
  1. #ifndef EXTERNAL_H  
  2. #define EXTERNAL_H  
  3.   
  4. #include <QObject>  
  5.   
  6. class External : public QObject  
  7. {  
  8.     Q_OBJECT  
  9.     Q_PROPERTY(QString msg READ getMsg WRITE setMsg) // 聲明靜態屬性msg  
  10. public:  
  11.     explicit External(QObject *parent = 0);  
  12.   
  13. signals:  
  14.     void mouseClicked();  
  15. public slots:  
  16.     void TestPassObject(QObject*);  
  17.   
  18. public:  
  19.   
  20.     QString getMsg() const { return msg; }  
  21.     void setMsg( const QString& strMsg ) { msg = strMsg; }  
  22.   
  23.     // 提供給Javascript方法,需要用Q_INVOKABLE修飾  
  24.     Q_INVOKABLE int VerifyUserAccount(const QString& userName, const QString& userPwd);  
  25.   
  26.     Q_INVOKABLE QString GetPropMsg();  
  27.   
  28.     Q_INVOKABLE void TestPassObjectToNative(QObject*);  
  29.   
  30.     QString msg;  
  31. };  
  32.   
  33. #endif // EXTERNAL_H  

 

我們為MainWindow類添加成員External對象指針

 

  1. External* m_pExternal;  


在MainWindow的構造方法中對m_pExternal實例化,並為這個頁面的javaScriptWindowObjectCleared信號關了槽函數AddJavascriptWindowObject

 

這樣,我們的MainWindow.h和MainWindow.cpp就成這樣子了:

 

MainWindow.h

 

 
  1. #include <QMainWindow>  
  2. #include <QGraphicsView>  
  3. #include <QGraphicsWebView>  
  4. #include <QGraphicsScene>  
  5. #include <QEvent>  
  6.   
  7. #include "External.h"  
  8.   
  9. class MainWindow : public QGraphicsView  
  10. {  
  11.     Q_OBJECT  
  12. public:  
  13.     MainWindow(QWidget *parent = 0);  
  14.     virtual ~MainWindow();  
  15. public slots:  
  16.     void AddJavascriptWindowObject();  
  17.   
  18. private:  
  19.     QGraphicsWebView*   m_pWebView;  
  20.     QGraphicsScene*     m_pScene;  
  21.     External*           m_pExternal;  
  22. };  
  23.   
  24. #endif // MAINWINDOW_H  


MainWindow.cpp

 

 
  1. #include "mainwindow.h"  
  2.   
  3. #include <QWebFrame>  
  4. #include <QLayout>  
  5.   
  6. MainWindow::MainWindow(QWidget *parent)  
  7.     : QGraphicsView(parent)  
  8. {  
  9.     this->resize( QSize( 800, 600) );  
  10.   
  11.     m_pExternal = new External();  
  12.       
  13.     m_pScene = new QGraphicsScene(this);  
  14.     if(NULL != m_pScene)  
  15.     {  
  16.         m_pWebView = new QGraphicsWebView;  
  17.   
  18.         if( NULL != m_pWebView)  
  19.         {  
  20.             //enabled javascript  
  21.             QWebSettings *settings = m_pWebView->page()->settings();  
  22.   
  23.             settings->setAttribute(QWebSettings::JavascriptEnabled,true);    
  24.             settings->setAttribute(QWebSettings::JavascriptCanAccessClipboard,true);  
  25.             settings->setAttribute(QWebSettings::DeveloperExtrasEnabled,true);  
  26.             settings->setAttribute(QWebSettings::LocalContentCanAccessRemoteUrls, true);  
  27.             settings->setAttribute(QWebSettings::LocalContentCanAccessFileUrls, true);  
  28.             settings->setAttribute(QWebSettings::JavascriptCanCloseWindows, true);  
  29.             settings->setAttribute(QWebSettings::AutoLoadImages,true);  
  30.   
  31.             m_pScene->addItem( m_pWebView );  
  32.             this->setScene( m_pScene );  
  33.             connect(m_pWebView->page()->mainFrame(), SIGNAL(javaScriptWindowObjectCleared()),  
  34.             this, SLOT(AddJavascriptWindowObject()));  
  35.             m_pWebView->setUrl(QUrl("file:///Users/cblogs_code/QWebKitDemo/index.html"));  
  36.          }  
  37.     }   
  38. }  
  39.   
  40. MainWindow::~MainWindow()  
  41. {  
  42.   
  43. }  
  44.   
  45. void MainWindow::AddJavascriptWindowObject()  
  46. {  
  47.     m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("external", m_pExternal);  
  48. }  


m_pWebView->setUrl(QUrl("file:///Users/cblogs_code/QWebKitDemo/index.html"));

 

這一行是加載index.html,請嘗試的同學自行修改成自己的index.html的絕對路徑。

當網頁加載時,會自動觸發javaScriptWindowObjectCleared信號,繼而我們的槽函數AddJavascriptWindowObject會被執行,上面關於AddJavaScriptWindowObject解釋說:當這個頁面析構后,這個對象在js上下文中也將消失,監聽到信號javaScriptWindowObjectCleared() 后來重新調用下addToJavaScriptWindowObject即可,因此,想讓external一直保持有效,這樣做就可以了。

 

然后,運行一下程序吧,點擊按鈕,你會看到如下結果:

 

三. 調用QObject的方法

js要調用已經擴展的對象external的方法,需要一下約束:
 
1. 方法前需要Q_INVOKABLE修飾;
2. 方法返回值和參數必須是Qt內置類型或者c++內置類型,即使用typedef的也不行;
3. 方法返回值和參數可以是QObject*, 但參數不能傳遞js對象,傳js對象用QVariant代替。
 
js中調用external的方法
 
 
  1. var ret = external.VerifyUserAccount("user01", "123456");  
  2. if (ret == 1) {  
  3.      alert("驗證通過");  
  4.  }else {  
  5.        alert("用戶名或者密碼錯誤");  
  6. }  


操作QObject對象的屬性

這節我們來嘗試訪問一下External對象的msg屬性,並且修改TA,看看在Js層和C++層是否被修改。

還是先看看測試頁面吧:

 

 
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  5. <title>QWebKitDemo</title>  
  6. <script type="text/javascript">  
  7.   
  8. window.onload = function(){  
  9.   
  10.     var btnTest = document.getElementById("testCObj");  
  11.     btnTest.onclick = function() {  
  12.         alert(external);  
  13.     }  
  14.   
  15.     var btnVisit = document.getElementById("visitProp");  
  16.     btnVisit.onclick = function() {        
  17.         var output = "js層面 \tmsg: "+external.msg + "\n c++層面 \tmsg: "+external.GetPropMsg();  
  18.         alert(output);  
  19.     }  
  20.   
  21.     var btnSetProp = document.getElementById("setProp");  
  22.     btnSetProp.onclick = function() {  
  23.         var tempValue = document.getElementById("msgProValue");  
  24.         external.msg = tempValue.value;  
  25.     }  
  26. }  
  27. </script>  
  28. </head>  
  29. <body>  
  30.     <input type="button" id="testCObj" value="測試C++對象">  
  31.     <p>測試屬性msg</p>  
  32.     <input type="button" id="visitProp" value="visit">  
  33.     <input type="text" id="msgProValue" value="修改后的msg">  
  34.     <input type="button" id="setProp" value="修改">  
  35. </body>  
  36. </html>  


運行程序,我們先點擊visit按鈕,可以看到,顯示的msg屬性都是空值,然后再文本框中輸入字符,點擊修改按鈕,再點擊visit按鈕,試試看,彈出的msg是否有變化。

 

 

四. 綁定QObject對象的信號/槽

addJavaScriptWindowObject方法可以將對象的屬性、信號、槽統統映射到Js上下文中。
綁定信號的方式如下

 
  1. external.mouseClicked.connect(mouseClickedSlot);  
  2. function mouseClickedSlot() {  
  3.     logger("mouse clicked");  
  4. }  

mouseClicked通過connect方法連接到js的方法(槽),當mouseClicked被觸發后,mouseClickedSlot將被執行

五. 為iframe添加QObject

當iframe被創建時,mainFrame下的webpage的信號frameCreated會被觸發;
frameCreated的原型是:
  1. void frameCreated(QWebFrame*);  
因此,我們可以為frameCreated關聯一個槽,在這個槽中,為新創建的QWebFrame添加QObject;
代碼如下:
1. 在MainWindow的構造函數添加
 
  1. connect(this->m_pWebView->page(), SIGNAL(frameCreated(QWebFrame*)),  
  2.             this, SLOT(ChildFrameCreated(QWebFrame*)));  
 
2. 為MainWindow類添加槽函數
 
  1. public slots:  
  2.     void AddJavascriptWindowObject();  
  3.     void ChildFrameCreated(QWebFrame *frame);  
3. 為MainWindow類添加如下實現
 
  1. void MainWindow::AddJavascriptWindowObject()  
  2. {  
  3.     m_pWebView->page()->mainFrame()->addToJavaScriptWindowObject("external", m_pExternal);  
  4. }  
  5.   
  6. void MainWindow::AddJavascriptWindowObject(QWebFrame *pFrame)  
  7. {  
  8.     qDebug("AddJavascriptWindowObject");  
  9.     //add external object to main web frame  
  10.     pFrame->addToJavaScriptWindowObject("external", m_pExternal);  
  11. }  
  12.   
  13. void MainWindow::ChildFrameCreated(QWebFrame *pFrame)  
  14. {  
  15.     qDebug("ChildFrameCreated");  
  16.     if(pFrame == NULL)  
  17.     {  
  18.         qDebug("Child frame created, but it was NULL!");  
  19.     }  
  20.     else  
  21.     {  
  22.         AddJavascriptWindowObject(pFrame);  
  23.     }  
  24. }  


六. 總結

到目前為止,一個Hybrid模式的應用demo已經完成了,我們來分析一下這種模式的優劣;

1、優勢:

  • 高效率開發UI豐富的應用;
  • 底層框架可復用;
  • 可實現跨平台;
  • 其他好處……
 

2、劣勢:

  • 性能
  • 還是性能
  • 其他劣勢……
 
隨着硬件的強大、html5的發展,不論是在pc端還是移動端, 這種框架會得到普遍的認可,i think so。

 
轉自http://blog.csdn.net/longsir_area/article/details/42965565


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM