原文地址:http://doc.qt.digia.com/4.7-snapshot/qdeclarativedynamicobjects.html
QML提供了很多种方式动态创建和管理QML对象.Loader,Repeater,ListView,GridView和PathView都支持动态对象管理.对象也可在C++中创建并管理,这是QML\C++相结合的应用程序首选方法.
QML也支持在Javascript代码中动态创建对象.这在当前QML文件无法满足需要是很有用,而且不会卷入额外的C++组件.
本文中下面的动态场景范例展示了这个概念.
动态创建对象
在JavaScript中动态创建对象有两种方式.可调用Qt.createComponent()动态创建一个组件对象,或使用Qt.createQmlObject()从一个描述QML的字符串创建元素.最好是有一个定义在.qml文件中的组件,需要动态创建这个组件的实例.否则,就需要在运行时使用一个QML描述字符串创建元素.
动态创建组件
要动态加载QML文件中的组件,可在QML全局对象中调用Qt.createComponent()函数.这个函数只有一个指向QML文件的URL参数并创建这个URL指向的组件对象.
创建了组件后,可以调用其createObject()方法创建组件的实例.这个函数有一个或两个参数:
第一个参数是新元素的parent.由于图形项(graphical item)在场景中必须有parent,否则就无法显示,还是推荐使用这种方式设置一个parent.然而,如果希望稍后在设置可以传递一个null实参.
第二个参数是可选的属性名称-值的映射列表,用来初始化元素的属性值.这个参数指定的属性值在对象构造完成之前被赋给对象的属性,避免了有些属性在用于绑定前必须被初始化而发生的错误.在属性用于绑定前就已经设置了值.另外,与对象创建后再设置属性值和绑定相比,具有稍微的效率优势.
下面是一个范例,首先定义Sprite.qml文件:
import QtQuick 1.0
Rectangle { width: 80; height: 50; color: "red" }
主应用程序文件是main.qml,导入可创建Spite对象的componentCreation.js JavaScript文件:
import QtQuick 1.0
import "componentCreation.js" as MyScript
Rectangle {
id: appWindow
width: 300; height: 300
Component.onCompleted: MyScript.createSpriteObjects();
}
下面是componentCreation.js. 注意在调用createObject()前先判断组件状态是否为Component.Ready,如果QML是从网上加载的,不会立刻完成的,必须等待.
var component;
var sprite;
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
if (component.status == Component.Ready)
finishCreation();
else
component.statusChanged.connect(finishCreation);
}
function finishCreation() {
if (component.status == Component.Ready) {
sprite = component.createObject(appWindow, {"x": 100, "y": 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
} else if (component.status == Component.Error) {
// Error Handling
console.log("Error loading component:", component.errorString());
}
}
如果从本地文件加载QML文件,可以忽略finishCreation()函数直接调用createObject():
function createSpriteObjects() {
component = Qt.createComponent("Sprite.qml");
sprite = component.createObject(appWindow, {"x": 100, "y": 100});
if (sprite == null) {
// Error Handling
console.log("Error creating object");
}
}
注意这两个实例中,createObject()函数的第一个参数是appWindow,使新创建的对象作为main.qml中的appWindow对象的子对象.否则,新创建的对象不会在场景中显示.
当用相对路径引用文件时,路径是相对于执行Qt.CreatComponent()函数的文件而言的.
要连接动态创建对象上的信号(或槽),可使用信号的connect()方法.
基于QML的字符串创建对象
如果在运行时还没有建立QML文件,可以使用Qt.CreateQmlObject()函数使用QML描述字符串来创建QML对象,如下所示:
var newObject = Qt.createQmlObject('import QtQuick 1.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem, "dynamicSnippet1");
第一个参数是创建QML的字符串.与创建新的QML文件一样,需要导入必要的类型.第二个参数是新对象的parent;这个parent必须在场景中存在.第三个参数新对象相关的文件连接,用于导出错误报表.
如果QML中使用相对路径导入文件,路径是相对于定义了parent对象的文件而言的.
维护动态创建的对象
要管理动态创建的对象,必须要确保创建上下文的生命周期比被创建的对象长.否则当创建上下文被释放后,对动态对象的绑定不在生效.
事实上创建上下文依赖于对象是如何创建的:
如果使用Qt.createComponent(),创建上下文是调用这个方法的QDeclarativeContext
如果调用Qt.createQmlObject(),创建上下文是传递给这个函数的parent参数的上下文
如果定义了Component{} 对象并在其上调用了createObject(),创建上下文是定义的那个Component
同时主要动态创建对象可像其他对象一样使用,在QML中没有id属性.
删除动态对象
在很多用户接口中,将对象的opacity设置为0或将其移动到屏幕之外,而不是直接删除对象.如果创建了很多动态对象,删除无用的对象会获得显著的效率提升.
主要不要手动删除由QML元素动态创建的对象(如Loader和Repeater).同时,避免删除不是由你创建的动态对象.
对象可调用destroy()方法删除.这个方法有一个可选参数(默认值为0)指定对象被删除的大概延时(毫秒).
下面是一个范例.application.qml创建了五个SelfDestroyingRect.qml组件.每个实例都会运行一个NumberAnimation动画,动画完成后,在其根对象上调用destroy()方法释放自己:
application.qml
import QtQuick 1.0
Item {
id: container
width: 500; height: 100
Component.onCompleted: {
var component = Qt.createComponent("SelfDestroyingRect.qml");
for (var i=0; i<5; i++) {
var object = component.createObject(container);
object.x = (object.width + 10) * i;
}
}
}
SelfDestroyingRect.qml
import QtQuick 1.0
Rectangle {
id: rect
width: 80; height: 80
color: "red"
NumberAnimation on opacity {
to: 0
duration: 1000
onRunningChanged: {
if (!running) {
console.log("Destroying...")
rect.destroy();
}
}
}
}
也可以在application.qml中调用object.destroy()删除其创建的对象.
注意在一个对象上调用destroy()是安全的.在对象上调用destroy()不会马上释放,而是在脚本阻塞后或下一帧时执行清理(除非设置了非0的延时).
注意如果SelfDestroyRect如下方式创建:
Item {
SelfDestroyingRect {
// ...
}
}
由于只能动态释放动态创建的对象,这时会发生错误.
使用Qt.createQmlObject()创建的对象也同样使用destroy()释放:
var newObject = Qt.createQmlObject('import QtQuick 1.0; Rectangle {color: "red"; width: 20; height: 20}',
parentItem, "dynamicSnippet1");
newObject.destroy(1000);