# 创建和销毁对象
Loader
元素可以动态填充用户界面的一部分内容。但是,界面的整体结构仍然是静态的。通过使用JavaScript,可以更进一步,完全动态地实例化QML元素。
在深入了解动态创建元素的细节之前,需要先了解它的工作流程。当从通过文件或者甚至通过网络加载一段QML时,会创建一个组件。该组件封装了解释型的QML代码,可用于创建项目。这意味着加载一段QML代码并从代码实例化项目是一个分为两个阶段的过程。首先,QML代码被解析成一个组件;然后,该组件用于实例化实际的项目对象。
除了从存储在文件或服务器中的QML代码创建元素之外,还可以直接从包含QML代码的文本字符串创建QML对象。一旦对象被实例化,动态创建的项目就会以类似的方式被处理。
# 动态加载并实例化项
当加载一段QML时,它首先被解释成一个组件。这个过程包括加载依赖项和验证代码。正在加载的QML的路径可以是本地文件、Qt资源,甚至可以是用URL指定的远程网络位置。这意味着,加载时间可以是即时的,例如,位于RAM中没有任何未加载依赖项的Qt资源;加载时间也可以非常长,例如,需要加载位于网速缓慢的服务器上且需要多个依赖项的一段代码.
正在创建的组件的状态可以通过它的status
属性来跟踪。 此属性可用的值有Component.Null
、Component.Loading
、Component.Ready
和Component.Error
。 从Null
到Loading
,再到Ready
是最常见的流程。在任何阶段,status
都可以转变为Error
。在变为Error
的情况下,该组件不能用于创建新的对象实例,Component.errorString()
函数可用于检索用户可读的错误描述。
当通过慢速连接加载组件时,可以使用progress
属性。它的范围从0.0
(表示没有加载任何内容)到1.0
(表示所有内容都已加载)。当组件的status
变为Ready
时,该组件可用于实例化对象。下面的代码演示了如何实现这一点,考虑了组件准备好与无法直接创建的事件,以及组件稍晚准备好的情况。
var component;
function createImageObject() {
component = Qt.createComponent("dynamic-image.qml");
if (component.status === Component.Ready || component.status === Component.Error) {
finishCreation();
} else {
component.statusChanged.connect(finishCreation);
}
}
function finishCreation() {
if (component.status === Component.Ready) {
var image = component.createObject(root, {"x": 100, "y": 100});
if (image === null) {
console.log("Error creating image");
}
} else if (component.status === Component.Error) {
console.log("Error loading component:", component.errorString());
}
}
上面的代码保存在一个单独的JavaScript源文件中,在主QML文件中引用。
import QtQuick
import "create-component.js" as ImageCreator
Item {
id: root
width: 1024
height: 600
Component.onCompleted: ImageCreator.createImageObject();
}
组件的createObject
函数用于创建对象实例,如上所示。 这不仅适用于动态加载的组件,还适用于QML代码中内联的Component
元素。 生成的对象可以像任何其他对象一样在QML场景中使用。 唯一的区别是它没有id
。
createObject
函数有两个参数。 第一个是Item
类型的parent
对象,第二个是格式为{"name": value, "name": value}
的属性和值列表。 这在下面的示例中演示。 请注意,属性参数是可选的。
var image = component.createObject(root, {"x": 100, "y": 100});
提示
动态创建的组件实例与内联的Component
元素没有什么不同。 内联的Component
元素也提供动态实例化对象的功能。
# 孵化组件
当使用createObject
创建组件时,对象组件的创建是阻塞式的。 这意味着复杂元素的实例化可能会阻塞主线程,从而导致视觉上的故障。 作为选择,复杂的组件可能必须使用Loader
元素分解并分阶段加载。
为了解决这个问题,可以使用incubateObject
方法实例化一个组件。 这可能就像createObject
一样工作,并立即返回一个实例;或者它可以在组件准备好时回调。 根据具体设置,这可能是解决与实例化相关的视觉动态故障的好方法,但也可能解决不了。
要使用孵化器,只需如同使用createComponent
一样使用即可。 但是,它返回的对象是一个孵化器,而不是对象实例本身。 当孵化器的状态成为Component.Ready
时,可以通过孵化器的object
属性获取对象。 这些使用都在下面的示例中有所展示:
function finishCreate() {
if (component.status === Component.Ready) {
var incubator = component.incubateObject(root, {"x": 100, "y": 100});
if (incubator.status === Component.Ready) {
var image = incubator.object; // Created at once
} else {
incubator.onStatusChanged = function(status) {
if (status === Component.Ready) {
var image = incubator.object; // Created async
}
};
}
}
}
# 从文本动态实例化项
有时,QML从文本字符串中实例化一个对象很方便。 这比将代码放在单独的源文件中运行要快。 为此,使用了Qt.createQmlObject
函数。
该函数接受三个参数:qml
、parent
和filepath
。 qml
参数,包含要实例化的QML代码字符串;parent
参数,为新创建的对象提供了一个父对象;filepath
参数,用于报告创建对象的任何错误。 从函数返回的结果要么是一个新对象,要么是null
。
警告
createQmlObject
函数总是立即返回。 要使函数成功,必须加载调用的所有依赖项。 这意味着如果传递给函数的代码引用了未加载的组件,则调用将失败并返回null
。 为了更好地解决这个问题,必须使用createComponent
/createObject
方法。
使用Qt.createQmlObject
函数创建的对象与任何其他动态创建的对象类似。 这意味着它与所有其他QML对象相同,除了没有id
。 在下面的示例中,当创建root
元素时,从内联QML代码中实例化了一个新的Rectangle
元素。
import QtQuick
Item {
id: root
width: 1024
height: 600
function createItem() {
Qt.createQmlObject("import QtQuick 2.5; Rectangle { x: 100; y: 100; width: 100; height: 100; color: \"blue\" }", root, "dynamicItem")
}
Component.onCompleted: root.createItem()
}
# 管理动态创建的元素
动态创建的对象可以被视为与QML场景中的任何其他对象相同。但是,需要注意一些陷阱。最重要的概念是创作上下文。
动态创建的对象的创建上下文就是被创建所在的上下文。这不一定与父对象所在的上下文相同。当创建上下文被销毁时,与对象相关的绑定也会被销毁。这意味着在代码中的哪个位置实现动态对象的创建在对对象的整个生命周期非常重要,在该位置对象被实例化。
动态创建的对象也可以被动态销毁。执行此操作时,有一条经验法则:永远不要尝试销毁尚未创建的对象。也不要尝试销毁不是使用诸如Component.createObject
或createQmlObject
之类的动态机制创建的元素。
一个对象通过调用它的destroy
函数被销毁。该函数采用一个可选参数,该参数是一个整数,指定对象在销毁之前应存在的毫秒数。这也很有用,例如,让对象完成最终过渡。
item = Qt.createQmlObject(...)
...
item.destroy()
提示
可以从内部销毁对象,例如可以给实例创建自毁弹出窗口。