一. 介绍

在浏览器扩展或者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. #include "External.h"
  7. class MainWindow : public QGraphicsView
  8. {
  9. Q_OBJECT
  10. public:
  11. MainWindow(QWidget *parent = 0);
  12. virtual ~MainWindow();
  13. private:
  14. QGraphicsWebView*   m_pWebView;
  15. QGraphicsScene*     m_pScene;
  16. };
  17. #endif // MAINWINDOW_H

MainWindow的实现

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

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. window.onload = function(){
  8. var btnTest = document.getElementById("testCObj");
  9. btnTest.onclick = function() {
  10. alert(external);
  11. }
  12. }
  13. </script>
  14. </head>
  15. <body>
  16. <input type="button" id="testCObj" value="测试C++对象">
  17. </body>
  18. </html>

我们先来定义一个继承自QObject的类External,其中,继承自QObject很关键,否则我们无法完成我们的功能;

External类的定义,这里我定义了一个带属性、信号、槽、带Q_INVOKABLE修饰的方法,这将在其他小节会用到

  1. #ifndef EXTERNAL_H
  2. #define EXTERNAL_H
  3. #include <QObject>
  4. class External : public QObject
  5. {
  6. Q_OBJECT
  7. Q_PROPERTY(QString msg READ getMsg WRITE setMsg) // 声明静态属性msg
  8. public:
  9. explicit External(QObject *parent = 0);
  10. signals:
  11. void mouseClicked();
  12. public slots:
  13. void TestPassObject(QObject*);
  14. public:
  15. QString getMsg() const { return msg; }
  16. void setMsg( const QString& strMsg ) { msg = strMsg; }
  17. // 提供给Javascript方法,需要用Q_INVOKABLE修饰
  18. Q_INVOKABLE int VerifyUserAccount(const QString& userName, const QString& userPwd);
  19. Q_INVOKABLE QString GetPropMsg();
  20. Q_INVOKABLE void TestPassObjectToNative(QObject*);
  21. QString msg;
  22. };
  23. #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. #include "External.h"
  7. class MainWindow : public QGraphicsView
  8. {
  9. Q_OBJECT
  10. public:
  11. MainWindow(QWidget *parent = 0);
  12. virtual ~MainWindow();
  13. public slots:
  14. void AddJavascriptWindowObject();
  15. private:
  16. QGraphicsWebView*   m_pWebView;
  17. QGraphicsScene*     m_pScene;
  18. External*           m_pExternal;
  19. };
  20. #endif // MAINWINDOW_H

MainWindow.cpp

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

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. window.onload = function(){
  8. var btnTest = document.getElementById("testCObj");
  9. btnTest.onclick = function() {
  10. alert(external);
  11. }
  12. var btnVisit = document.getElementById("visitProp");
  13. btnVisit.onclick = function() {
  14. var output = "js层面 \tmsg: "+external.msg + "\n c++层面 \tmsg: "+external.GetPropMsg();
  15. alert(output);
  16. }
  17. var btnSetProp = document.getElementById("setProp");
  18. btnSetProp.onclick = function() {
  19. var tempValue = document.getElementById("msgProValue");
  20. external.msg = tempValue.value;
  21. }
  22. }
  23. </script>
  24. </head>
  25. <body>
  26. <input type="button" id="testCObj" value="测试C++对象">
  27. <p>测试属性msg</p>
  28. <input type="button" id="visitProp" value="visit">
  29. <input type="text" id="msgProValue" value="修改后的msg">
  30. <input type="button" id="setProp" value="修改">
  31. </body>
  32. </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. void MainWindow::AddJavascriptWindowObject(QWebFrame *pFrame)
  6. {
  7. qDebug("AddJavascriptWindowObject");
  8. //add external object to main web frame
  9. pFrame->addToJavaScriptWindowObject("external", m_pExternal);
  10. }
  11. void MainWindow::ChildFrameCreated(QWebFrame *pFrame)
  12. {
  13. qDebug("ChildFrameCreated");
  14. if(pFrame == NULL)
  15. {
  16. qDebug("Child frame created, but it was NULL!");
  17. }
  18. else
  19. {
  20. AddJavascriptWindowObject(pFrame);
  21. }
  22. }

六. 总结

到目前为止,一个Hybrid模式的应用demo已经完成了,我们来分析一下这种模式的优劣;

1、优势:

  • 高效率开发UI丰富的应用;
  • 底层框架可复用;
  • 可实现跨平台;
  • 其他好处……
 

2、劣势:

  • 性能
  • 还是性能
  • 其他劣势……
 
随着硬件的强大、html5的发展,不论是在pc端还是移动端, 这种框架会得到普遍的认可,i think so。

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

[Qt] 利用QtWebKit完成JavaScript访问C++对象的更多相关文章

  1. JavaScript 访问对象属性和方法及区别

    这篇文章主要介绍了浅析JavaScript访问对象属性和方法及区别的相关资料,仅供参考 属性是一个变量,用来表示一个对象的特征,如颜色.大小.重量等:方法是一个函数,用来表示对象的操作,如奔跑.呼吸. ...

  2. 全面理解Javascript中Function对象的属性和方法

    http://www.cnblogs.com/liontone/p/3970420.html 函数是 JavaScript 中的基本数据类型,在函数这个对象上定义了一些属性和方法,下面我们逐一来介绍这 ...

  3. JavaScript访问修改css样式表

    1.访问元素中style属性的css样式 可以根据属性的ID或name标签利用dom操作直接访问到内部的css样式,直接使用style对象访问 <div id="myid" ...

  4. Qt Quick 组件和动态创建的对象具体的解释

    在<Qt Quick 事件处理之信号与槽>一文中介绍自己定义信号时,举了一个简单的样例.定义了一个颜色选择组件,当用户在组建内点击鼠标时,该组件会发出一个携带颜色值的信号,当时我使用 Co ...

  5. 第一百一十三节,JavaScript文档对象,DOM基础

    JavaScript文档对象,DOM基础 学习要点: 1.DOM介绍 2.查找元素 3.DOM节点 4.节点操作 DOM(Document Object Model)即文档对象模型,针对HTML和XM ...

  6. JQuery制作网页—— 第三章 JavaScript操作DOM对象

    1. DOM:Document Object Model(文档对象模型):          DOM操作:                   ●DOM是Document Object Model的缩 ...

  7. JavaScript进阶 - 第7章 JavaScript内置对象

    第7章 JavaScript内置对象 7-1 什么是对象 JavaScript 中的所有事物都是对象,如:字符串.数值.数组.函数等,每个对象带有属性和方法. 对象的属性:反映该对象某些特定的性质的, ...

  8. javascript中的对象字面量为啥这么酷

    原文链接 : Why object literals in JavaScript are cool 原文作者 : Dmitri Pavlutin 译者 : neal1991 个人主页:http://n ...

  9. Redis的C++与JavaScript访问操作

    上篇简单介绍了Redis及其安装部署,这篇记录一下如何用C++语言和JavaScript语言访问操作Redis 1. Redis的接口访问方式(通用接口或者语言接口) 很多语言都包含Redis支持,R ...

随机推荐

  1. 设置 debug 版本签名与生产版本一致

    debug 版本使用生产版本的签名 在开发过程中,app 直接跑到手机上,用的签名文件是 Android Studio 默认的自动生成的一个签名,与生产版本的 app 签名是不一样的.当接入华为推送的 ...

  2. Status bar and navigation bar appear over my view's bounds in iOS 7

    转自:http://stackoverflow.com/questions/17074365/status-bar-and-navigation-bar-appear-over-my-views-bo ...

  3. R语言之——字符串处理函数

    nchar 取字符数量的函数 length与nchar不同,length是取向量的长度 # nchar表示字符串中的字符的个数 nchar("abcd") [1] 4 # leng ...

  4. Boost家族

    大师Yoav Freund在文章<A decision-theoretic generalization of on-line leanring and an application to bo ...

  5. STL学习笔记— —无序容器(Unordered Container)

    简单介绍 在头文件<unordered_set>和<unordered_map> 中定义 namespace std { template <typename T, ty ...

  6. nginx upstream 常用的几种调度方式

    nginx可以根据客户IP进行负载均衡,在upstream里设置ip_hash,以可以对同一个C类地址段的客户端选择同一个后端服务器,除非那个后端服务器宕了才会换一个.C类地址:C类地址第1字节.第2 ...

  7. confluence数据库的配置文件

    mysql> select u.id,u.user_name,u.active from cwd_user u join cwd_membership m on u.id=m.child_use ...

  8. 菜鸟学数据库(五)——MySQL必备命令

    今天跟大家分享一下MySQL从连接到具体操作的一系列常用命令.可能有的人觉得现在有很多可视化的工具,没必要再学习那些具体的命令了,但是我不这么认为,不可否认那些工具的确让我们的工作更加方便快捷,但是如 ...

  9. 聊一聊 Spring 中的线程安全性

    Spring与线程安全 Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”.但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码. Sp ...

  10. python细节问题

    1.list添加元素 a = [1, 2] print(id(a)) a += [3] print(id(a)) a = a + [4] print(id(a)) a.append(5) print( ...