Qt Script文档翻译(一)

Qt脚本

Qt支持应用程序利用ECMAScript编程。下面的说明涵盖了用EMCAScript和Qt编程的各个方面。

脚本类

下面的类可以使Qt应用程序具备脚本能力。

QScriptClass定义qt脚本对象自定义行为的接口
QScriptClassPropertyIterator自定义qt脚本对象的迭代器
QScriptContext表示一个qt脚本函数的调用
QScriptContextInfo关于QScriptContext的其它一些信息
QScriptEngineqt脚本代码执行环境
QScriptEngineAgent报告QScriptEngine执行的附属事件接口
QScriptEngineDebuggerQScriptEngine调试器
QScriptProgram装入一个qt脚本程序
QScriptStringQScriptEngine内部字符串的句柄
QScriptSyntaxCheckResult脚本语法校验结果
QScriptValueqt脚本数据类型的容器
QScriptValueIteratorjava风格的QScriptValue迭代器
QScriptable从qt的C++成员函数访问qt脚本环境

脚本语言概述

Qt脚本是基于ECMAScript脚本语言,语言标准见ECMA-262。微软的Jscript以及网景的JavaScript也是基于此标准。可以参考ECMA手册以便了解此标准的内容。如果你对ECMAScript语言不熟悉,可以参考现有的几个例子以及类似《JavaScript:The Definitive Guide》这类的书。

基本用法

要执行脚本代码,需要创建QScriptEngine对象并调用它的evalute()函数,将脚本代码作为参数传入。

QScriptEngine engine;
qDebug() << "the magic number is:" << engine.evaluate("1 + 2").toNumber();

返回值是执行结果(返回QScriptValue对象);该类型可以转换成标准C++和Qt类型。
自定义的“属性”通过脚本引擎注册后可以通过脚本访问。最简单的做法是将属性作为脚本引擎的全局对象

engine.globalObject().setProperty("foo", 123);
qDebug() << "foo times two is:" << engine.evaluate("foo * 2").toNumber();

这么做就把属性放到了脚本环境中,因此可以被脚本代码访问。

使脚本引擎可以访问QObject

任何派生自QObject的实例均可被脚本使用。
当一个QObject对象传递给QScriptEngine::newQObject()函数时,创建了一个Qt脚本包装对象,该对象使QObject的信号、槽、属性以及子对象均可被脚本访问。
下面的例子,将一个QObject子类的对象实例以名称“myObject”被脚本代码访问:

QScriptEngine engine;
QObject *someObject = new MyObject;
QScriptValue objectValue = engine.newQObject(someObject);
engine.globalObject().setProperty("myObject", objectValue);

这将在脚本环境中创建一个名为myObject的全局变量。这个变量作为它所包装的C++对象的代理。注意脚本变量的名称可以是任意指定,它不依赖于QObject::objectName()。
newObject()函数还有另外两个可选的参数:一个是所有权模式,还有一个参数是一个选项集合,允许你控制QScriptValue如何包装QObject。后面会回过头来介绍这些参数的使用。

- 使用信号和槽
Qt脚本引入了Qt的核心信号槽机制。用Qt脚本使用信号和槽有三种方法:

- 混合C++/脚本:
C++代码连接信号和脚本函数。脚本函数可以是,比如说,用户输入的或者从文件读入的。这种办法很有用,特别是在你有一个QObject,但是不想把他暴露给脚本环境时;你只希望脚本可以定义如何响应信号,然后在C++程序侧建立连接。
- 混合脚本/C++:
脚本可以在脚本环境中暴露的预定义对象间建立信号和槽的连接。在这种场景下,槽函数本身还是在c++程序中编写的,但是连接完全是动态定义的(由脚本定义的)。
- 完全脚本定义:
脚本既可以定义信号句柄函数(slots),也可以利用这些句柄建立连接。比如,脚本可以定义一个函数句柄指向QLineEdit::returnPressed()信号,然后连接这个信号到脚本函数。

用qScriptConnect()函数来连接C++信号和脚本函数。下面的例子定义了脚本信号处理句柄,用来处理QLineEdit::textChnaged()信号:

QScriptEngine eng;
QLineEdit *edit = new QLineEdit(...);
QScriptValue handler = eng.evaluate("(function(text) { print('text was changed to', text); })");
qScriptConnect(edit, SIGNAL(textChanged(const QString &)), QScriptValue(), handler);

qScriptConnect()函数的前两个参数和传给QObject::connect()创建通常的C++连接是一样的。第三个参数是脚本对象,当信号处理函数被调用后,该对象类似this对象;上面的例子我们传入了一个无效的脚本对象,因此this对象将是全局对象。第四个参数是脚本函数本身(“槽函数”)。下面的例子演示了this参数如何使用:

QLineEdit *edit1 = new QLineEdit(...);
QLineEdit *edit2 = new QLineEdit(...);

QScriptValue handler = eng.evaluate("(function() { print('I am', this.name); })");
QScriptValue obj1 = eng.newObject();
obj1.setProperty("name", "the walrus");
QScriptValue obj2 = eng.newObject();
obj2.setProperty("name", "Sam");

qScriptConnect(edit1, SIGNAL(returnPressed()), obj1, handler);
qScriptConnect(edit2, SIGNAL(returnPressed()), obj2, handler);

我们创建了两个QLineEdit对象,并且定义了一个信号处理函数。连接使用了同一个处理函数,但是处理函数根据哪个对象的信号被触发而调用相应的函数,因此打印出来的内容会不同。

在脚本代码中,Qt脚本使用了不同的语法来创建或者断开连接,而不是像QObject::connect()这样熟悉的C++语法。要连接信号,你引用一个信号作为发送对象的属性,然后调用它的connect()函数。有三个重载的connect()函数,每个函数对应一个disconnect()函数。下面的章节对着三种形式作了描述:

信号连接到函数
connect(function)
这个形式的连接,connect()函数的参数是要连接到信号的脚本函数。

function myInterestingScriptFunction() 
{
    // ...
}
// ...
myQObject.somethingChanged.connect(myInterestingScriptFunction);

参数可以像上例这样,是一个Qt脚本函数,也可以是QObject槽,就像下面的例子一样:

myQObject.somethingChanged.connect(myOtherQObject.doSomething);

当参数是QObject的槽时,信号和槽的参数类型不一定要匹配;Qt脚本会在必要时会将信号的参数类型进行转换以匹配槽函数的参数类型。
要从一个信号断开连接,你可以调用信号的disconnect()函数,将要断开的槽函数作为参数传入:

myQObject.somethingChanged.disconnect(myInterestingFunction);
myQObject.somethingChanged.disconnect(myOtherQObject.doSomething);

当脚本函数作为信号的响应被调用时,this对象是一个全局对象。

信号连接到成员函数
connect(thisObject,function)
这个形式的connect()函数,第一个参数是当第二个参数指定的函数被调用时,绑定到变量this上的对象。
如果你在form上有一个按钮,你通常想做一些和form有关的事情来响应按钮的点击信号,这种情况下把form作为this对象比较合理。

var obj = { x: 123 };
var fun = function() { print(this.x); };
myQObject.somethingChanged.connect(obj, fun);

与信号断开连接,将相同的参数传递给disconnect()即可:

myQObject.somethingChanged.disconnect(obj, fun);

信号连接到有名的成员函数
connect(thisObject,functionName)
这个形式的connect()函数,第一个参数是当用来响应信号的函数被调用时,绑定到变量this上的对象。第二个参数指定了连接到信号的函数名称,并且这个函数名称指向第一个参数的成员函数。
注意函数在连接建立时处理,不是在信号产生时。

var obj = { x: 123, fun: function() { print(this.x); } };
myQObject.somethingChanged.connect(obj, "fun");

与信号断开连接,将相同的参数传递给disconnect()即可:

myQObject.somethingChanged.disconnect(obj, "fun");

错误处理
当connect()或者disconnect()函数执行成功后,返回undefined;否则,它会抛出脚本异常。你可以从Error对象中得到异常信息。例子:

try {
    myQObject.somethingChanged.connect(myQObject, "slotThatDoesntExist");
} catch (e) {
    print(e);
}

从脚本中发射信号
要从脚本代码中发射信号,你只需要调用信号函数,传入相应的参数即可:

myQObject.somethingChanged("hello");

目前还不支持在脚本中定义新的信号;所有的信号必须在c++类中定义。

重载的信号和槽
当信号或槽是重载的,Qt Script会尝试基于调用函数的QScriptValue参数实际类型选择正确的重载函数。比如,如果你的类有一个槽myOverloadedSlot(int)和myOverloadedSlot(QString),下面的脚本代码会合理运行:

MyObject.myOverloadedSlot(10);//执行int版的重载函数
MyObject.myOverloadedSlot(“10”);//执行QString版的重载函数

你可以用数组风格的属性,用c++函数的“归一化申明”作为属性名来指定特定的重载函数:

myQObject['myOverloadedSlot(int)']("10");   //调用int版的重载函数; 参数会强转成int类型
myQObject['myOverloadedSlot(QString)'](10); //调用QString版重载函数; 参数会强转成QString类型

如果重载函数的参数数量不同,Qt Script会根据实际传入参数的数量选出最匹配的重载函数。
对于重载的信号,如果你试图通过名称来连接信号,Qt Script会抛出异常;你必须用全名引用。

- 访问属性
QObject的属性可以作为相应Qt Script对象的属性。当你在脚本代码中使用一个属性时,c++针对这个属性的get/set方法会被自动调用。比如,如果你的c++类里有个属性定义如下:

Q_PROPERTY(bool enabled READ enabled WRITE setEnabled)

脚本代码可以像如下这样做:

myQObject.enabled = true;
// ...
myQObject.enabled = !myQObject.enabled;

- 访问子QObjects
每一个QObject的有名子类(也就是,QObject::objectName()不是空字符)默认都可以作为Qt Script的包装对象的属性。比如,如果你有一个QDialog的子窗体,子窗体的objectName属性是“okButton”,你可以在脚本代码中通过如下的表达式访问这个对象

myDialog.okButton

既然objectName本身是一个Q_PROPERTY,你可以在脚本代码中用这个名字,比方说,给对象重命名:

myDialog.okButton.objectName = "cancelButton";
// from now on, myDialog.cancelButton references the button

你也可以用findChild()函数和findChildren()函数来查找子对象。这两个函数分别和QObject::findChild()和QObject::findchildren()完全一样。
比如,我们可以通过字符串和表达式,用这些函数来找到对象:

var okButton = myDialog.findChild("okButton");
if (okButton != null) {
   // do something with the OK button
}

var buttons = myDialog.findChildren(RegExp("button[0-9]+"));
for (var i = 0; i < buttons.length; ++i) {
   // do something with buttons[i]
}

当你操作一个使用了网格布局的窗体是,你通常会想用findChild();这样的话,脚本就不需要知道控件到底在那个布局中这样的细节了。

- 控制对象的所有权
Qt Script用垃圾回收机制来回收不再使用的脚本对象的内存;在脚本环境中对象不再被任何地方引用时,内存会被自动回收。当包装的对象被回收后(不管QObject是否被删除了),Qt Script允许你控制指向的(G:即被包装的)C++QOBject;你可以在创建对象时,将所有权模式作为QScriptEngine::newQObject()函数的第二个参数传给它来实现。

了解Qt Script如何处理所有权是很重要的,因为它可以帮助你避免下述情况的发生:c++对象应当删除却没有被删除时(会导致内存泄露),或者,c++对象在不该删除的地方被删除了(通常会core)。

Qt所有权
默认的,脚本引擎不会得到传给QScriptEngine::newQObject()函数的QObject的所有权;对象根据Qt的对象所有权机制(参考对象树和所有权)被管理。这个模式当,比如说,你在包装你应用程序核心部分的c++一些对象时,是合适的;这表示,脚本环境中无论发生什么,他们都应该留存。换个说法来解释就是c++对象必须在脚本引擎外部生存。

脚本所有权
指定QScriptEngine::ScriptOwnership作为所有权模式会使得脚本引擎对QObject拥有完全的所有权,并且可以在可行的情况下(比如在脚本代码中该对象没有其它的引用)删除它。这种所有权在如下情况是适用的,即:如果QObject没有父对象,并且/或者QObject是在脚本引擎上下文中创建的,不打算在脚本引擎外生存的对象。
比如,构造函数只在脚本环境中使用是个不错的选择:

QScriptValue myQObjectConstructor(QScriptContext *context, QScriptEngine *engine)
{
  // let the engine manage the new object's lifetime.
  return engine->newQObject(new MyQObject(), QScriptEngine::ScriptOwnership);
}

自动所有权
QScriptEngine::AutoOwnership 是基于QObject是否有父对象。如果Qt Script的垃圾回收器发现在脚本环境中QObject不再被引用,那么只有在QObject没有父对象,它才会被删除。

其它人删了QObject会怎样?
一个包装过的QObject可能在Qt Script控制之外被删除;比如没有考虑指定所有权模式。这种情况下,包装对象仍然是一个对象(不像被它包装的c++指针,脚本对象不会变成null)。任何尝试对于这个脚本对象的访问都会抛出脚本异常。

注意对于一个删除的Object,QScriptValue::isQObject()仍然返回true,因为它测试了脚本对象的类型,不管内部指针是否为非空。换句话说,如果QScriptValue::isQObject()返回true但是QScriptValue::toQObject()返回空指针,那么表示QObject在脚本外部被删掉了(可能是意外删的)。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值