• [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层面  msg: "+external.msg + "  c++层面  msg: "+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
  • 相关阅读:
    学生管理系统简化版
    图形用户界面编程——事件驱动编程
    图形界面编程
    集合框架
    多线程编程
    反射
    相关类
    异常(4.13)
    接口
    锁!代码锁
  • 原文地址:https://www.cnblogs.com/liushui-sky/p/7851654.html
Copyright © 2020-2023  润新知