详解QML中组件的动态处理
本文最后更新于 16 天前,其中的信息可能已经有所发展或是发生改变。

1. 引言

QML(Qt Meta-Object Language)作为Qt框架中的声明式UI语言,与传统的命令式UI开发方式不同,QML通过声明式语法和JavaScript的结合,提供了极其灵活的组件动态处理能力。

QML提供的高响应式、动态并发的操作界面本身就是动态的,但是更多的是大家对组件中各属性的动态运用,对于组件整体的动态使用往往并不多,使用的少了也就很难把组件像属性一样的灵活使用

官网对于动态创建组件的文档:https://doc.qt.io/qt-6/qtqml-javascript-dynamicobjectcreation.html

在阐述之前,需要对以下概念区分开,不能混淆:

首先,有两个组件需要分清楚:

  • Item:QML中实例化得到Item,Button等系列组件都可以说继承自Item
  • Component:QML中封装为模板得到Component,模板可以实例化为组件

这两者都可以像组件一样使用,但两者并不是简单的继承关系。

其次,有两个概念需要分清楚:

  • 加载:Component的加载过程是从源解析成Component组件模板,不是直接使用的UI组件;

  • 实例化:根据已加载的组件模板创建具体的对象实例,设定具体的属性,用于直接的UI。

一个Component一次加载就可以多次实例化,像ButtonRectangle这样的内置基础组件可以说没有加载过程,或者说已从内部加载过了,甚至在使用时就已实例化。


2. 详解 Component 和 Loader

2.1 Component

官网文档:https://doc.qt.io/qt-6/qml-qtqml-component.html

Component 本身不是直接作用的组件,而是作为创建组件对象的模板

与直接声明的组件不同,Component 定义的组件不会立即实例化,只有在需要时才会创建。

属性

  • url:url,只读,此组件的url,用于创建此组件
  • status:enumeration,只读,反映此组件加载的状态
    • Component.Null:此组件内无数据
    • Component.Ready:此组件已被加载,可用于创建实例
    • Component.Loading :此组件正在被加载
    • Component.Error:此组件被加载时发生错误,通过errorString()返回错误描述
  • progress:real,只读,显示此组件被加载的进度,从0.0(没有被加载)到1.0(完成)

信号

  • completed():组件被实例化后触发。在其他组件中也有,就是常用的Component.onCompleted
  • destruction():组件被销毁后触发。

方法

  • object createObject(QtObject parent, object properties):创建并返回该组件的组件实例,该实例将具有给定的父组件和属性。parent指定父组件,properties是JsonObject类型的值,用于设定组件的属性,见示例。
  • object incubateObject(QtObject parent, object properties, enumeration mode):为此组件的实例创建孵化器,孵化器可用于实例化组件对象。孵化器允许使用mode指定是否异步实例化新的组件,使之不影响UI。见示例。
  • string errorString()
Component {
    id: blueButton
    Rectangle {
        width: 120
        height: 50
        color: "blue"
        radius: 5
        Text {
            text: "Click Me"
            anchors.centerIn: parent
            color: "white"
        }
    }
}

// 加载组件
function create(){
    const component = Qt.createComponent("Button.qml"); // 通用Component对象
    if(component.status === Component.Ready){
        component.createObject(parent, { x: 100, y: 100 }); // 实例化
    }
}

// 创建并使用孵化器
function incubate(){
    const component = Qt.createComponent("Button.qml"); // 通用Component对象
    const incubator = component.incubateObject(parent, { x: 10, y: 10 }); // 孵化器
    if (incubator.status !== Component.Ready) {
        incubator.onStatusChanged = function(status) {
            if (status === Component.Ready) {
                print("Object", incubator.object, "is now ready!");
            }
        };
    } else {
        print("Object", incubator.object, "is ready immediately!");
    }
}

2.2 Loader

官网文档:https://doc.qt.io/qt-6/qml-qtquick-loader.html

Loader 是动态加载组件的专用组件,它提供了加载和卸载组件的接口。可以使用 source 属性加载一个 QML 文件或者使用 sourceComponent 属性加载一个组件对象。

属性

  • active:bool,控制是否加载组件

  • source:url,指定要加载的QML文件作为组件

  • sourceComponent:Component,用于加载已定义的组件

  • item:QtObject,只读,指向已加载的组件实例,便于访问

  • status:enumeration,只读,反映加载组件的状态

    • Loader.Null:Loader处于非活动状态,组件未加载
    • Loader.Ready:组件已加载
    • Loader.Loading :组件正在加载中
    • Loader.Error:组件加载时发生失败
  • progress:real,只读,显示文件加载组件的进度,从0到1(因为一般QML文件比较小,所以变化很快)

  • asynchronous:bool,控制是否异步加载组件,默认为false

信号

  • loaded():当status变为Loader.Ready时触发

方法

  • object setSource(url source, object properties):手动加载组件,source为QML文件,properties是JsonObject类型的值,用于设定组件的属性,见示例。

注意:Loader不设定大小时自动匹配大小至加载的组件,与Loader交互时需要焦点在指定Loader中。

Loader {
    id: buttonLoader
    anchors.centerIn: parent
    width: active ? item.width : 0
    height: active ? item.height : 0
    onLoaded: console.log("buttonLoader is ready")
}

// 控制加载状态
function toggle() {
    active = !active
}

// 动态切换组件
function loadNewComponent() {
    source = "NewComponent.qml"
}

Component.onCompleted: {
    buttonLoader.setSource("MyButton.qml", {"text": "myButton"});
}


3 Qt提供的 create*() 方法

Qt提供了两种方法动态创建对象,可以调用Qt.createComponent()来动态创建Component对象,也可以使用Qt.createQmlObject()从QML字符串创建对象。

3.1 Qt.createComponent()

官网文档:https://doc.qt.io/qt-6/qml-qtqml-qt.html#createComponent-method

格式:Component createComponent(url url, enumeration mode, QtObject parent)

  • 返回一个Component的组件对象;
  • 通过url指定QML文件作为创建对象的源;
  • 通过mode指定是否异步创建,从而不影响UI操作;
  • 通过parent指定关联的父组件。
// 从QML文件创建Component
var fileComponent = Qt.createComponent("Button.qml")
if (fileComponent.status === Component.Ready) {
    var obj = fileComponent.createObject(parentItem)
}

3.2 Qt.createQmlObject()

官网文档:https://doc.qt.io/qt-6/qml-qtqml-qt.html#createQmlObject-method

格式:object createQmlObject(string qml, object parent, url url)

  • 直接返回一个新对象,如果创建组件或对象时发生错误,则返回空;
  • 通过qml指定QML字符串,用于直接编译到组件中,QML字符串是QML创建组件的语句,见下示例;
  • 通过parent指定关联的父组件;
  • 通过url指定对象的URL,标识对象的来源,可作为虚拟文件名,用于错误调试和报告,发生错误时会显示此URL。
// 直接从QML字符串创建对象
const newObject = Qt.createQmlObject(`
    import QtQuick

    Rectangle {
        color: "red"
        width: 20
        height: 20
    }
    `,
    parentItem,
    "myDynamicSnippet"
);


4. 组件的 destroy() 方法

动态创建的组件往往需要手动管理内存,直接对对象调用它的destroy()方法即可,提供了设定延时的参数,单位为毫秒。

// 销毁单个组件
function destroyComponent(obj) {
    obj.destroy()
    // 或者使用延迟销毁
    obj.destroy(1000) // 1秒后销毁
}

// 批量销毁示例
function cleanup() {
    for (var i = 0; i < dynamicChildren.length; i++) {
        dynamicChildren[i].destroy()
    }
    dynamicChildren = []
}

注意事项:

  • 销毁后对象引用变为undefined
  • 推荐在Component.onDestruction中执行清理操作
  • 避免在过渡动画期间销毁
  • 对于频繁创建销毁的对象,考虑对象池技术


5. 活用 children 和 data 属性

5.1 children

每个Item都有children属性,用于管理子元素,只有继承自Item的组件才有children属性,比如Rectangle、button等常用组件

类型为list<Item>,是一个列表,可以像列表一样使用。

可以认为是与parent属性对立的。

// 访问子项
Item {
    id: root
    Rectangle { id: rect1 }
    Text { id: text1 }
    Component.onCompleted: {
        console.log(children.length) // 2
        console.log(children[0] === rect1) // true
        console.log(children.includes(text1)) // true
    }
}

// 动态修改层级
Item {
    Repeater {
        model: ["red", "green", "blue"]
        delegate: Rectangle {
            color: modelData
            width: 50; height: 50
            z: index // 通过z控制实际渲染顺序
        }
    }
    function bringToFront(item) {
        // 将子项移到children数组末尾(最后渲染=显示在最前)
        item.parent = null
        item.parent = this
    }
}

5.2 data

所有QML对象都有data属性

类型为list<Object>,是一个列表,可以像列表一样使用。

默认包含显式声明的子对象以及通过 children属性(如果是 Item 派生类)暴露的子项。

// 显示访问子对象
Item {
    id: parentItem
    Rectangle { id: rect; width: 50 }
    Text { id: text; text: "Hello" }
    Component.onCompleted: {
        console.log(data.length)  // 输出 2(rect和text)
        console.log(data[0] === rect) // true
    }
}

// 动态添加对象
Item {
    Component.onCompleted: {
        var child = Qt.createQmlObject(`
            import QtQuick 2.0
            QtObject { property string name: "dynamic" }
        `, parentItem)
        console.log(data.includes(child)) // true
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇