0.前言
在一些 QML 代码中,可以看到 createComponent 或者 createObject 这样的函数被调用,这就是动态创建 QML 对象的接口。QML 支持从 JavaScript 内部动态创建对象,这对于延迟对象的实例化很有用,缩短了应用程序的启动时间。它还允许根据用户输入或其他事件动态创建可视对象并将其添加到场景中。
本文主要参考自官方文档:
参考文档:https://doc.qt.io/qt-5/qtqml-javascript-dynamicobjectcreation.html
本文完整的代码: https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20201118_createComponent
1.动态创建
1.1 根据 QML 文件的 URL 创建
使用 Qt.createComponent() 可以从 URL 创建一个 Component 对象,然后调用 Component 的 createObject() 函数就可以创建该组件的实例:
object Qt::createComponent(url, mode, parent)
//如果未传递mode,第二个参数可以是parent
//mode有两个枚举值:
//Component.PreferSynchronous 最好立即加载/编译组件。这并不总是可能的。例如,远程URL将始终异步加载。
//Component.Asynchronous 在后台线程中加载/编译组件
//示例:var component = Qt.createComponent("TestItem.qml");
//调用create之后判断返回对象的status,为Component.Ready表示准备就绪,可以createObject
object Component::createObject(QtObject parent, object properties)
//可见的item需要绑定图形对象,不可兼得也可以绑定非图形对象
//示例:var item = component.createObject(flow,{"width":100,"height":100});
注意事项:
虽然这些创建的接口可以设置同步异步,但是只是作为参考值,详情参考官方文档。
createComponent 返回对象如果 status 为 Error ,那么绑定 statusChanged 会失败。
parent参数,如果创建是图形对象(Item系列),那么父对象也得是图形对象(有时也会显示,但是会提示错误)。
整体流程:
//通过url加载qml组件后再创建item
function createFromUrl(){
//object Qt::createComponent(url, mode, parent)
//如果未传递mode,第二个参数可以是parent
//mode有两个枚举值:
//Component.PreferSynchronous 最好立即加载/编译组件。这并不总是可能的。例如,远程URL将始终异步加载。
//Component.Asynchronous 在后台线程中加载/编译组件
//(TestItem中放了一个Rectangle{ color:red })
var component = Qt.createComponent("TestItem.qml");
//status有四个枚举值:
//Component.Null 该Component没有数据。调用loadUrl或setData添加QML内容。
//Component.Ready 已准备就绪,可以调用create
//Component.Loading 正在加载
//Component.Error 发生了错误
if (component.status === Component.Ready) {
//object Component::createObject(QtObject parent, object properties)
//可见的item需要绑定图形对象,不可兼得也可以绑定非图形对象
var item = component.createObject(flow,{"width":100,"height":100});
//... ...
}else if(component.status === Component.Loading){
//如果没有立即加载好就绑定到statusChanged,异步加载
//判断下Loading是因为Error状态绑定onStatusChanged会出错
//也可以component.statusChanged.connect(callback);
component.onStatusChanged = function(status) {
if (status === Component.Ready) {
var item = component.createObject(flow,{"width":100,"height":100});
//... ...
}
}
}
}
1.2 通过已有的 Component 创建
如果是本地 QML 文件,可以省略从 URL 加载的过程,QML 代码先创建一个 Component:
Component{
id: test_comp
Rectangle{
color: "green"
}
}
//通过Component直接创建item
function createFromComponent(){
//object Component::createObject(QtObject parent, object properties)
//可见的item需要绑定图形对象,不可兼得也可以绑定非图形对象
var item = test_comp.createObject(flow,{"width":100,"height":100});
//... ...
}
1.3 通过字符串创建
//通过qml代码创建item
function createFromQml(){
//object Qt::createQmlObject(string qml, object parent, string filepath)
//如果指定了filepath,它将用于创建对象的错误报告
var item = Qt.createQmlObject('import QtQuick 2.12; Rectangle {color: "blue"; width: 100; height: 100}',flow);
//... ...
}
使用这种方式不利于调试。
1.4 使用孵化器创建
在前面我们都是创建 Component 后再调用其 createObject 函数创建实例,这个函数没有设置异步的接口。可以通过 incubateObject 函数异步实例化新组件。
Component{
id: test_comp
Rectangle{
color: "green"
}
}
//通过孵化器创建新实例
function createFromIncubate(){
//object Component::incubateObject(Item parent, object properties, enumeration mode)
//为此组件的实例创建一个incubator孵化器,孵化器允许异步实例化新组件实例
//mode有两个枚举
//Qt.Synchronous 同步创建。也可能异步创建对象,比如组件本身就是异步创建的
//Qt.Asynchronous (默认)异步创建
//返回Incubator孵化器,可调用forceCompletion() 完成同步
var incubator=test_comp.incubateObject(flow,{"color":"yellow","width":100,"height":100});
if (incubator.status !== Component.Ready) {
incubator.onStatusChanged = function(status) {
if (status === Component.Ready) {
//... ...
}
}
} else {
//... ...
}
}
2.维护动态创建的对象
管理动态创建的对象时,必须确保创建上下文的寿命超过创建的对象。否则,如果先破坏了创建上下文,则动态对象中的绑定和信号处理程序将不再起作用。
实际的创建上下文取决于对象的创建方式:
如果使用 Qt.createComponent() ,则创建上下文是调用此方法的 QQmlContext
如果调用 Qt.createQmlObject(),则创建上下文是传递给此方法的父对象的上下文
如果定义了一个 Component 对象,并且调用了 createObject() 或 incubateObject(),则创建上下文就是在其中定义的上下文 Component
注意,虽然动态创建的对象可以与其他对象一样使用,但它们在 QML 中没有 ID。
3.动态删除
在许多用户界面中,将可视对象的不透明度设置为0或将其移出屏幕而不是删除它就足够了。但是,如果有许多动态创建的对象,则删除未使用的对象可以带来性能上的提升。
可以使用 destroy([msdelay=0]) 函数删除对象。此方法具有一个可选参数(默认为0),表示延迟毫秒数。默认情况下,不会立即销毁该对象,而是在代码块末尾到下一帧之间由框架处理。
注意,不要手动删除由 Loader 或者 Repeater 等 QML 动态创建的对象。另外,应避免删除不是动态创建的对象。
//销毁对象
function destroyItem(){
//item.destroy(msdelay=0)
//只有动态创建的对象才能动态销毁它们
//不应删除由Loader或者Repeater等创建的对象
//调用destroy时不会立即删除,延迟0时为代码块末尾到下一帧之间删除
if(flow.children.length>0)
flow.children[0].destroy(1000); //延迟1s删除
}