将Python嵌入到Qt程序中

2015/01/20 12:10
阅读数 1.3W

(原文链接: http://doc.trolltech.com/qq/qq23-pythonqt.html   by Florian Link  译:  赖敬文

      将脚本语言嵌入C++ 程序已经变得非常普遍。在许多主流的应用程序,如Microsoft Office 与Macromedia Director 中, 都有一种上升的趋势,即提供小巧的,脚本给用户以提供一些更加专用的功能。

过去的几年,对于Qt 程序嵌入脚本只有两种主流的解决方案:由奇趣提供的 QSA (JavaScript 2.0) 和由Riverbank Computing  提供的PyQt (Python) 。在Qt Quarterly   的Scripting Qt   文章中已经给出了一个很好的关于QSA , PyQt  和其它解决方案。

自那篇文章写完之后,还有许多方案正在开发中,到目前为止,还有两种方案值得参考:  

  • QtScript,  一个自 Qt4.3 后的基于  ECMAScript 的解析器

  • PythonQt, MeVisLab 正在使用 ,  属于一个动态地 Python 解析器。

QtScript  与 PythonQt  出现使得在Qt 程序中嵌入脚本变得容易,这篇文章将集中描述PythonQt  

脚本的好处

将一个C++ 程序脚本化有如下一些好处:  

  • 一个设计得好的应用程序可以为初级跟专家级用户提供易于控制的能力。

  • 在不需要具有非常深厚的 C++  背景下,应用程序都可以很容易地扩展 .  

  • 脚本便于创建宏和批处理  

  • 自动化测试变得可能  

  • 脚本可跨平台,若应用程序可以运行于某个平台,脚本同样可以运行。  

  • 脚本可以使原型化的阶段更快实现,比如 ,   你的支持团队可以通过脚本来增加特性,这比使用 C++  开发并重新布暑更方便。  

脚本的API 具有多种形式:可以是一个对于能用任务的批处理,也可以是一个可以供用户定制及扩展菜单及对话框的功能更全的版本,甚至是可以访问程序的可以说功能(如,网络浏览器中的JavaScript ).  

当针对Qt 程序增加脚本时,以下几点被认为是有益的:  

  • 易于集成进 Qt  程序中  

  • 基于大家都知道的脚本语言,以降低学习一门新语言的门槛。  

  • Qt  框架的良好集成 ,  如,它应该知道 signals,slots   properties.  

  • 支持脚本语言与 Qt  之间的类型转换及处理,理想情况下,最好支持所有的 QVariant  类型

  • 支持调试 , 当脚本程序变大时,调试功能也变得重要。

关于PythonQt  

与 PyQt 和Qt Jambi 不同, PythonQt 不是作为编写独立的应用程序的支撑组件,而是提供嵌入python 解析器的能力,并且可以很容易地将应用程序的部分功能导出到Python 中。  

PythonQt  扩展了Qt 4 meta-object  系统的功能。因此,PythonQt 能够在不知道任何QObject 的情况下, 借助QMetaObject 提供的信息,动态地脚本化QObject 的功能。 这种动态方法允许多种不同的脚本绑定并嵌入到同一个应用程序中,每一种绑定可以使用同一个脚本API. 例如: JavaScript (QSA or QtScript)  和 Python.  

以下几个部分会重点介绍PythonQt 的一些核心特性。更具体的关于PythonQt 介绍,欢迎访问PythonQt 项目的主页.  

如何开始

下面这个应用程序展示将PythonQt 嵌入到你的Qt 应用程序的步骤  

     #include "PythonQt.h"
     #include <  QApplication >
      
     int main(int argc, char **argv)
     {
         QApplication  qapp(argc, argv);
      
         PythonQt::init();
         PythonQtObjectPtr mainModule =
                           PythonQt::self()->getMainModule();
         QVariant  result = mainModule.evalScript(
                         mainModule, "19*2+4", Py_eval_input);
         return 0;
     }

我们最先初始化一个PythonQt 的实例,这个实例初始化Python 解析器。然后我们从Python 的__main__ 模块中得到一个智能指针(PythonQtObjectPtr ) 并在此模块内测试一些Python 代码.  

此处的结果由Python 进行解析会是42 。

创建应用程序脚本API  

      程序脚本化的方法是找到最适合你的用户、开发者和员工的适合层面的API 。最基本的,你为你的应用程序创建了domain-specific 的语言,它可以使得你的用户很方便地在没有C++ 编译器的情况下得到它想要的功能.  

一个典型的PythonQt 的应用是让你的用户、开发者或者支持人员创建一些小的脚本,通过脚本来改变应用程序的某些方面。  

典型的,你可以创建一个新的基于QObject 的API 类,将之作为一个适配器用于你的应用程序的其它类中。 你也可以将所有的QObjects 都直接导出来,但通常情况下,这种导出方式导致很多的细节暴露给了脚本使用者,并且迫使你保持所有导出接口的稳定,否则你的脚本API 的改变将导致使用者在以后使用旧的脚本的时候因为API 不一致而不可用。

创建特定的API 对象也许是最优的解决方案,这使得你可以保持一个稳定的接口并且可以声明程序的哪部分是脚本可以使用的。PythonQt  支持所有的 QVariant   类型,因此你可以创建丰富的应用程序API ,比如返回值是QDateTime   与 QPixmap  类型, 甚至是包含了任意QVariant 值树状化的 QVariantMap   对象。

关于Python

Python (http://www.python.org )  是面向对象的程序语言,有持续增长的用户社区和大量的标准模块。Python  被设计成是“可扩展和可嵌入”的。用C/C++ 写成的库可以包装成Python 的包供Python 程序使用,而解析器也可以嵌入到应用程序中提供脚本功能。

以下是一些提供Python 脚本的比较著名的应用程序:  

GUI  脚本化

下面让我们创建一个简单的实例,包含Group box.  


 

 

C++  代码如下定义用户界面:  

     QGroupBox  *box = new QGroupBox ;
     QTextBrowser  *browser = new QTextBrowser (box);
     QLineEdit  *edit = new QLineEdit (box);
     QPushButton  *button = new QPushButton (box);
      
     button->setObjectName("button1");
     edit->setObjectName("edit");
     browser->setObjectName("browser");
      
     mainModule.addObject("box", box);


现在,我们利用PythonQt, 使用Python 脚本存取GUI 。首先,我们展示如何方便地存取Qt 的属性及子对象:
  
     # Set the title of the group box via the title property.
     box.title = 'PythonQt Example'
  
     # Set the HTML content of the QTextBrowser.
     box.browser.html = 'Hello <b>Qt</b>!'
  
     # Set the title of the button.
     box.button1.text = 'Append Text'
  
     # Set the line edit's text.
     box.edit.text = '42'

注意,当属性被设置成QString 属性后,每个Python string 会自动转化成QString.  

C++ 中的Signals 对象也可以转化成Python 函数。我们定义一个通常的函数,我们连接按钮的clicked() 信号与lineedit 的returnPressed() 槽:  

def  appendLine ():
         box.browser.append (box.edit.text)
  
     box.button1.connect ('clicked()' , appendLine)
     box.edit.connect ('returnPressed()' , appendLine)

group box  与通常情况一样,作为一个最顶层的控件:

     box.show ()

为了测试以上脚本,我们需要在主模块中调用一个特殊的函数。在这里,我们已经将这个文件在Qt 的资源系统中定义,因此,我们像正常情况下使用它:

     mainModule.evalFile (":GettingStarted.py" );

现在,只要你在Lineedit 按下了return 或者点击按钮,在lineedit 中的文字将会使用appendLine() 函数将文字添加到browser 中。

PythonQt  模块

脚本通常需要做的事情比处理数据,建立连接,调用函数要多。例如,脚本通常需要具有为程序创建特定类型的新对象的能力。  

为满足这种需求,PythonQt  仓储了一个叫PythonQt 的模块,你可以使用它来读取所有书籍对象的consturctors 和静态成员。   

这里展示了一些使用PythonQt 模块的例子:  

     from  PythonQt import  *
  
     # Access enum values of the Qt namespace.
     print  Qt .AlignLeft
  
     # Access a static QDate method.
     print  QDate .currentDate ()
  
     # Construct a QSize object
     a = QSize (1,2)

装饰器与C++ 包装

PythonQt 的动态方法的一个主要的问题是在Python 中只有slots 才是可调用的.  在这种方法中没有办法对C++ 方法进行动态地脚本化,因为Qt 的meta-object compiler (moc ) 并不为它们产生运行时信息。  

PythonQt 引入了"decorator slot" 这个概念,  它以一种直接的方式重用了支持constructors, destructors,  静态方法和非静态方法进行调用Qt slots 的机制。最基本的想法是引入了新的继承于QObject 的 "decorator objects" (not to be confused with Python's own decorators) , 它的 slots  遵循decorator 的命令规范并且使得PythonQt 可以将通常的constructors 可调用。

这使得任何C++ 类或者继承于QObejct 的类允许其在现有的类结构中扩展一些额外的成员函数。

下面这个类定义显示了一些decorators 例子:  

     class PyExampleDecorators : public QObject
     {
         Q_OBJECT
      
     public slots:
         QVariant  new_QSize (const QPoint  &p)
             { return QSize (p.x(), p.y()); }
      
         QPushButton  *new_QPushButton (const QString  &text,
                                      QWidget  *parent = 0)
             { return new QPushButton (text, parent); }
      
         QWidget  *static_QWidget_mouseGrabber ()
             { return QWidget::mouseGrabber() ; }
      
         void move(QWidget  *w, const QPoint  &p) { w->move(p); }
         void move(QWidget  *w, int x, int y) { w->move(x, y); }
      };

当将上面的示例decorator 在PythonQt 中注册之后 ( 通过 PythonQt::addDecorators() ), PythonQt  现在提供了:  

  • 一个以 QPoint 为参数的 QSize 构造函数

  • string 和父 widget 为参数的 QPushButton 的构造函数

  • 一个新的 QWidget 的静态成员 mouseGrabber();

  • Move() 本身在 QWidget 并不是 slot, 因此,现在为 QWidget 添加一个额外的 move()

  • 重载的可供调用的 QWidget      slot move()

这种装饰器的方法非常强大,因为它可以在你的类体系中随时增加一些新的功能,并且不需要手动地处理参数的转换。 ( 比如,  将constructor 参数从Python 转化成Qt).  将一个非slot 方法暴露到PythonQt 中变成了一句语句即可完成的事.  

其它特性

PythonQt  还提供另外一些更高级的特性,其中一些有趣的是:  

  • 对不是继承于 QObject   C++  对象进行包装

  • 支持自定义的 QVariant 类型

  • 创建你自有的导入方式的接口,比如, Python 脚本可以在执行前先注册或者校验。

在PythonQt 源代码的例子中,包含了许多我们没有在此文中提到的额外的特性。

未来方向

PythonQt  是为了使MeVisLab 脚本化而写的,当前已经达到了一个满意的成熟度。 它使得在现有的Qt 应用程序中嵌入Python 脚本变得非常容易,而它并不需要像PyQt 一样提供覆盖面很广的QtAPI 。  

我要谢谢我的公司 MeVis Research ,  是它允许我将PythonQt 作为一个开源项目在source forge 存在。PythonQt 以LGPL 协议发布,你可以到pythonqt.sourceforge.net  获得更多信息。

我正在寻求更多的开发者参与到这个项目中来。假如你想贡献的话,请联系我florian (at) mevis.de  if you would like to contribute!  

本文中所述的例子的源代码是PythonQt 包的一部分,你可以到sourceforge.net 下载。


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
3 收藏
0
分享
返回顶部
顶部