天天看点

QML 中神秘的 ComponentQML 中神秘的 Component

QML 中神秘的 Component

Component 内部定义的 qmltype,其实相当于类定义,没有实例化,所以外部无法直接访问,看起来像是闭包,外部无法访问,但是 Component 内部可以访问外部变量。

在 ListView 中有个代理模式,其中最为重要的是名为

delegate

的属性。

通过设置

delegate

以不同的方式,展示数据项。你能发现

ListView::delegate

的接口类型为

Component

,却不是

Item

下面通过一些代码为你一一道出 Component 不为人知的秘密

Component 包裹对象的作用域

这里的作用域亦可是可被访问的范围。

import QtQuick 2.0

Item {
    //! [0]
    id: root
    
    Component {
        //! [1]
        id: com
        Text {
            //! [2]
            id: label
            text: "I am Text"
            //! [2]
        }
        //! [1]
    }
    
    Loader {
        anchors.fill: parent
        sourceComponent: com
    }
    //! [0]
}
           

如上诉代码,其中有三个可访问的标识符:

root

com

label

root

在整个

是可以被访问的,

com

访问域也是在整个

区。但是

label

只能在

2

区被访问。刚好被

1

给包裹了。

其实,

label

就是作为

com

的模板对象(模板图元),可以在后续使用中,用来构造对应的对象。

Component 和 Loader 的配合

讲完之后。我们再来一次代码看一看。首先我们定义一个

Panel

,如下:

//~ Panel.qml
import QtQuick 2.0

Rectangle {
    width: 100
    height: 100

    color: "transparent"
    border.color: "black"
    border.width: 1

    property Component label

    Loader {
        id: loader
        anchors.fill: parent
        anchors.margins: parent.width * 0.1
        sourceComponent: label
    }
}
           

这里的

Loader

会从

label

那里获取到一个模板图元,然后依据这个模板图元进行对象构建,

Loader

作为父亲参数调用

label

object Component::createObject(Item parent, object properties)

函数,返回的对象设置给

Loader::item

属性。

Loader::sourceComponent

改变的时候,他会先销毁之前的根据

label

构建的对象,然后再重新构建新对象,设置到

Loader::item

下面是

Loader::setSourceComponent(Component c)

c++

伪码:

void Loader::setSourceComponent(Component c)
{
    if(this.sourceComponent != c) {
        if(this.item != null) {
            this.item.deleteLater();
            this.item = null;
        }
        this.sourceComponent = c;
        emit this.sourceComponentChanged();             // 信号触发后,就构建新的对象到 this.item
    }
}
           

Compoent 的自动装箱

测试代码如下:

import QtQuick 2.5

Item {
    width: 640
    height: 360

    Row {
        anchors.fill: parent

        Panel {
            id: panel1
            label: Component {
                id: greenCom
                Rectangle {
                    color: "green"
                    Component.onDestruction: {
                        console.log("Green Rectangle Destruction")
                    }
                }
            }
        }

        Panel {
            id: panel2
            label: Rectangle { color: "red" }
        }
    }
    Component {
        id: yellowCom
        Rectangle {
            color: "yellow"
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            panel1.label = yellowCom;
            if(time == 0) {
                console.log("set cache only once")
                cache = panel2.label;
            }
            panel2.label = yellowCom;
            ++time;
            console.log(cache, cache.progress)
        }
    }

    property int time: 0
    property var cache

    Component.onCompleted: {
        console.log(panel2.label)
    }
}
           

其中

panel2

lable

参数不是

Component

,但是 Qt 会为其构建一个

Component

进行自动封装(不是类型转换,类似 Java 的装箱)。

当你修改

panel2

lable

属性时,跟前面所说的一样,旧的对象会被销毁,并被新的对象替代。同理,

panel1

也是。

因为自动装箱而产生的 Component 回收情况

你会注意到,

panel2

之前自动产生

label

对象保存在

cache

中,并没有被销毁(访问

progress

不报空指针异常)。也就是装箱产生的封装类,不会被马上回收。

由于是 Qt 自动生成的,所以其对象所有权应该属于 QmlEngine,在满足一些条件时会被自动回收。可以尝试使用

ObjectOwnership QQmlEngine::objectOwnership(QObject * object)

查询对象的所有权,这里没有进行测试,有空再说啦~

继续阅读