Qt Quick 组件和动态创建的对象具体的解释

    在《Qt Quick 事件处理之信号与槽》一文中介绍自己定义信号时,举了一个简单的样例。定义了一个颜色选择组件,当用户在组建内点击鼠标时,该组件会发出一个携带颜色值的信号,当时我使用 Connections 对象连接到组件的 colorPicked 信号,改变文本的颜色。 当时用到的 Component 、 Loader 两个特性,一直没来得及介绍,可能非常多人都还在雾里看花呢。

这次呢。我们就来仔细致细地把他们讲清楚。

    版权全部 foruok ,转载请注明出处:http://blog.csdn.net/foruok

Components(组件)

    Component 是由 Qt 框架或开发人员封装好的、仅仅暴露了必要接口的 QML 类型,能够反复利用。

一个 QML 组件就像一个黑盒子,它通过属性、信号、函数和外部世界交互。

    一个 Component 即能够定义在独立的 qml 文件里,也能够嵌入到其他的 qml 文档中来定义。通常我们能够依据这个原则来选择将一个 Component 定义在哪里:假设一个 Component 比較小且仅仅在某个 qml 文档中使用或者一个 Component 从逻辑上看从属于某个 qml 文档,那就能够採用嵌入的方式来定义该 Component 。你也能够与 C++ 的嵌套类对照来理解。

嵌入式定义组件

    《Qt Quick 事件处理之信号与槽》一文中使用到 Component 的演示样例 QML 代码例如以下:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#C0C0C0";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Component {
        id: colorComponent;
        Rectangle {
            id: colorPicker;
            width: 50;
            height: 30;
            signal colorPicked(color clr);
            MouseArea {
                anchors.fill: parent
                onPressed: colorPicker.colorPicked(colorPicker.color);
            }
        }
    }
    
    Loader{
        id: redLoader;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "red";
        }
    }
    
    Loader{
        id: blueLoader;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "blue";
        }
    }
    
    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
    
    Connections {
        target: blueLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
}

    当中,颜色选择组件的定义代码例如以下:

    Component {
        id: colorComponent;
        Rectangle {
            id: colorPicker;
            width: 50;
            height: 30;
            signal colorPicked(color clr);
            MouseArea {
                anchors.fill: parent
                onPressed: colorPicker.colorPicked(colorPicker.color);
            }
        }
    }

    如你所见。要在一个 QML 文档中嵌入 Component 的定义,须要使用 Component 对象。

    定义一个 Component 与定义一个 QML 文档相似, Component 仅仅能包括一个顶层 item ,并且在这个 item 之外不能定义不论什么数据,除了 id 。

比方上面的代码中。顶层 item 是 Rectangle 对象,在 Rectangle 之外我定义了 id 属性。其值为 colorComponent 。而顶层 item 之内。则能够包括很多其他的子元素来协同工作,终于形成一个具有特定功能的组件。

    Component 通经常使用来给一个 view 提供图形化组件,比方 ListView::delegate 属性就须要一个 Component 来指定怎样显示列表的每个项,又比方 ButtonStyle::background 属性也须要一个 Component 来指定怎样绘制 Button 的背景。

 

    Component 不是 Item 的派生类,而是从 QQmlComponent 继承而来,尽管它通过自己的顶层 item 为其他的 view 提供可视化组件,但它本身是不可见元素。你能够这么理解:你定义的组件是一个新的类型,它必须被实例化以后才可能显示。而要实例化一个嵌入在 qml 文档中定义的组件,则能够通过 Loader 。

后面我们具体讲述 Loader ,这里先按下不表,我们要来看怎样在一个文件里定义组件了。

在单独文件里定义组件

    非常多时候我们把一个 Component 单独定义在一个 qml 文档中,比方 Qt Quick 提供的 BusyIndicator 控件。事实上就是在 BusyIndicator.qml 中定义的一个组件。以下是 BusyIndicator.qml 文件的内容:

Control {
    id: indicator

    property bool running: true

    Accessible.role: Accessible.Indicator
    Accessible.name: "busy"

    style: Qt.createComponent(Settings.style + "/BusyIndicatorStyle.qml", indicator)
}

    我在《Qt Quick 简单教程》一文中的显示网络图片实例中,使用了 BusyIndicator 来提示用户图片正在载入中须要等候。你能够跳转到那篇文章学习 BusyIndicator 的使用方法。

    BusyIndicator 组件的代码非常easy,仅仅是给 Control 元素(Qt Quick 定义的私有元素,用作其他控件的基类,如 ComboBox 、 BusyIndicator 等)添加了一个属性、设置了几个属性的值,仅此而已。

    不知你是否注意到了, BusyIndicator.qml 文件里的顶层 item 是 Control ,而我们使用时却是以 BusyIndicator 为组件名(类名)。这是我们定义 Component 时要遵守的一个约定:组件名字必须和 qml 文件名称一致。好嘛,和 Java 一样啦。类名就是文件名称。另一点,组件名字的第一个字母必须是大写。对于在文件里定义一个组件,就这么简单了。再没有其他的特殊要求。 Qt Quick 提供的多数基本元素和特性,你都能够在定义组件时使用。

    一旦你在文件里定义了一个组件,就能够像使用标准 Qt Quick 元素一样使用你的组件。比方我们给颜色选择组件起个名字叫 ColorPicker ,相应的 qml 文件为  ColorPicker.qml ,那么你就能够在其他 QML 文档中使用 ColorPicker {...} 来定义 ColorPicker 的实例。

    好啦,如今让我们来看看在单独文件里定义的 ColorPicker 组件:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    id: colorPicker;
    width: 50;
    height: 30;
    signal colorPicked(color clr);
    
    function configureBorder(){
        colorPicker.border.width = colorPicker.focus ? 2 : 0;  
        colorPicker.border.color = colorPicker.focus ? "#90D750" : "#808080"; 
    }
    
    MouseArea {
        anchors.fill: parent
        onClicked: {
            colorPicker.colorPicked(colorPicker.color);
            mouse.accepted = true;
            colorPicker.focus = true;
        }
    }
    Keys.onReturnPressed: {
        colorPicker.colorPicked(colorPicker.color);
        event.accepted = true;
    }
    Keys.onSpacePressed: {
        colorPicker.colorPicked(colorPicker.color);
        event.accepted = true;
    }
    
    onFocusChanged: {
        configureBorder();
    }
    
    Component.onCompleted: {
        configureBorder();
    }
}

    请注意上面的代码,它和嵌入式定义有明显不同: Component 对象不见咧。对,就是酱紫滴:在单独文件内定义组件,不须要 Component 对象。仅仅有在其他 QML 文档中嵌入式定义组件时才须要 Component 对象。另外。为了能够让多个 ColorPicker 组件能够正常的显示焦点框。我还使用了 onClicked 信号处理器,新增了 onFocusChanged 信号处理器。在它们的实现中调用 configureBorder() 函数来又一次设置边框的宽度和颜色,新增 onReturnPressed 和 onSpacePressed 以便响应回车和空格两个按键。

    你能够使用 Item 或其派生类作为组件的根 item 。 ColorPicker 组件使用 Rectangle 作为根 Item 。如今让我们看看如实在其他文件里使用新定义的 ColorPicker 组件。我改动了上节的演示样例,新的 qml 文件被我命名为 component_file.qml 。内容例如以下:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#EEEEEE";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    function setTextColor(clr){
        coloredText.color = clr;
    }
    
    ColorPicker {
        id: redColor;
        color: "red";
        focus: true;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;

        KeyNavigation.right: blueColor;
        KeyNavigation.tab: blueColor;  
        onColorPicked:{
            coloredText.color = clr;
        }      
    }
    
    ColorPicker {
        id: blueColor;
        color: "blue";
        anchors.left: redColor.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;

        KeyNavigation.left: redColor;
        KeyNavigation.right: pinkColor;
        KeyNavigation.tab: pinkColor;   
    }
    
    ColorPicker {
        id: pinkColor;
        color: "pink";
        anchors.left: blueColor.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;

        KeyNavigation.left: blueColor;
        KeyNavigation.tab: redColor;   
    }
    
    Component.onCompleted:{
        blueColor.colorPicked.connect(setTextColor);
        pinkColor.colorPicked.connect(setTextColor);
    }
}

    能够看到。 component_file.qml 使用 ColorPicker 组件的方式与使用 Rectangle 、 Button 、 Text 等标准 Qt Quick 组件全然一致:能够给组件指定唯一的 id ,能够使用锚布局,能够使用 KeyNavigation 附加属性……总之。自己定义的组件和 Qt Quick 组件并无本质不同。

只是须要注意的是,组件实例的 id 和组成组件的顶层 item 的 id 是各自独立的,以上面的样例来看, redColor 和 colorPicker 是两个不同的 id ,前者指代组件对象(尽管组件的定义没有使用 Component ),后者指代 ColorPicker 的 Rectangle 对象。

    上面的代码还演示两种使用 qml 自己定义信号的方式。 redColor 使用信号处理器, greeColor 和 pinkColor 则使用了 signal 对象的 connect() 方法连接到 setTextColor() 方法上。

    我把 ColorPicker.qml 和 component_file.qml 放在同一个文件以下,否则可能会报错。图 1 是执行 "qmlscene component_file.qml" 命令 的效果:


            图 1 在文件里定义组件并使用

    对于定义在单独文件里的 Component ,除了能够像刚刚介绍的那样使用,也能够使用 Loader 来动态载入,依据须要再创建对象。以下我们就来看 Loader 到底是何方妖怪。

使用 Loader

Loader 的具体介绍

    Loader 用来动态载入 QML 组件。

    Loader 能够使用其 source 属性载入一个 qml 文档,也能够通过其 sourceComponent 属性载入一个 Component 对象。当你须要延迟一些对象直到真正须要才创建它们时。 Loader 非常实用。

当 Loader 的 source 或 sourceComponent 属性发生变化时,它之前载入的 Component 会自己主动销毁,新对象会被载入。将 source 设置为一个空字符串或将 sourceComponent 设置为 undefined 。将会销毁当前载入的对象,相关的资源也会被释放。而 Loader 对象则变成一个空对象。

    Loader 的 item 属性指向它载入的组件的顶层 item ,比方 Loader 载入了我们的颜色选择组件,其 item 属性就指向颜色选择组件的 Rectangle 对象。对于 Loader 载入的 item ,它暴露出来的接口,如属性、信号等,都能够通过 Loader 的 item 属性来訪问。

所以我们才干够这么使用:

    Loader{
        id: redLoader;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "red";
        }
    }

    上面的代码在 Loader 对象使用 sourceComponent 属性来载入 id 为 colorComponent 的组件对象,然后在 onLoaded 信号处理器中使用 item 属性来设置颜色选择组件的颜色。对于信号的訪问,我们则能够使用 Connections 对象,如以下的 qml 代码所看到的:

    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }

    我们创建的 Connections 对象,其 target 指向 redLoader.item ,即指向颜色选择组件的顶层 item —— Rectangle ,所以能够直接响应它的 colorPicked 信号。

    尽管 Loader 本身是 Item 的派生类,但没有载入 Component 的 Loader 对象是不可见的,没什么实际的意义。

而一旦你载入了一个 Component , Loader 的大小、位置等属性却能够影响它所载入的 Component 。

假设你没有显式指定 Loader 的大小,那么 Loader 会将自己的尺寸调整为与它载入的可见 item 的尺寸一致。假设 Loader 的大小通过 width 、 height 或 锚布局显式设置了,那么它载入的可见 item 的尺寸会被调整以便适应 Loader 的大小。

无论是哪种情况, Loader 和它所载入的 item 具有相同的尺寸。这确保你使用锚来布局 Loader 就等同于布局它载入的 item 。

    我们改变一下颜色选择器演示样例的代码,两个 Loader 对象,一个设置尺寸一个不设置,看看是什么效果。新的 qml 文档我们命名为 loader_test.qml ,内容例如以下:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#C0C0C0";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Component {
        id: colorComponent;
        Rectangle {
            id: colorPicker;
            width: 50;
            height: 30;
            signal colorPicked(color clr);
            MouseArea {
                anchors.fill: parent
                onPressed: colorPicker.colorPicked(colorPicker.color);
            }
        }
    }
    
    Loader{
        id: redLoader;
        width: 80; // [1]
        height: 60;// [2]
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "red";
        }
    }
    
    Loader{
        id: blueLoader;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        onLoaded:{
            item.color = "blue";
        }
    }
    
    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
    
    Connections {
        target: blueLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
}

    注意上面的代码中的凝视。方括号标准的 2 处改动。设置了红色 Loader 的尺寸,效果如图 1 所看到的:


                图 2 Loader 尺寸

    假设 Loader 载入的 item 想处理按键事件。那么必须将 Loader 对象的 focus 属性置 true 。

又由于 Loader 本身也是一个焦点敏感的对象,所以假设它载入的 item 处理了按键事件。应当将事件的 accepted 属性置 true 。以免已经被吃掉的事件再传递给 Loader 。我们来改动下 loader_test.qml ,加入对焦点的处理,当一个颜色组件拥有焦点时,绘制一个边框,此时假设你按下回车或空格键,会触发其 colorPicked 信号。

同一时候我们也处理左右键,在不同的颜色选择组件之间切换焦点。将新代码命名为 loader_focus.qml ,内容例如以下:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#EEEEEE";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Component {
        id: colorComponent;
        Rectangle {
            id: colorPicker;
            width: 50;
            height: 30;
            signal colorPicked(color clr);
            property Item loader;
            border.width: focus ? 2 : 0;  
            border.color: focus ? "#90D750" : "#808080"; 
            MouseArea {
                anchors.fill: parent
                onClicked: {
                    colorPicker.colorPicked(colorPicker.color);
                    loader.focus = true;
                }
            }
            Keys.onReturnPressed: {
                colorPicker.colorPicked(colorPicker.color);
                event.accepted = true;
            }
            Keys.onSpacePressed: {
                colorPicker.colorPicked(colorPicker.color);
                event.accepted = true;
            }
        }
    }
    
    Loader{
        id: redLoader;
        width: 80;
        height: 60;
        focus: true;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        KeyNavigation.right: blueLoader;
        KeyNavigation.tab: blueLoader;
        
        onLoaded:{
            item.color = "red";
            item.focus = true;
            item.loader = redLoader;
        }
        onFocusChanged:{
            item.focus = focus;
        }
    }
    
    Loader{
        id: blueLoader;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        sourceComponent: colorComponent;
        KeyNavigation.left: redLoader;
        KeyNavigation.tab: redLoader;
        
        onLoaded:{
            item.color = "blue";
            item.loader = blueLoader;
        }
        onFocusChanged:{
            item.focus = focus;
        }        
    }

    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
    
    Connections {
        target: blueLoader.item;
        onColorPicked:{
            coloredText.color = clr;
        }
    }
}

    首先我让颜色选择组件处理按键事件(如忘记请參看《Qt Quick事件处理之鼠标、键盘、定时器》)。收到回车和空格键时发出 colorPicked 信号。我还给颜色选择组件定义了一个名为 loader 的属性,以便鼠标点击颜色选择组件时能够改变 Loader 对象的焦点属性。我们在 Loader 的 onLoaded 信号处理器中给颜色选择组件的 loader 属性赋值。

    颜色选择组件依据焦点状态决定是否绘制边框,当有焦点时绘制宽度为 2 的边框。

    对于 Loader ,我设置了 KeyNavigation 附加属性,指定左右键和 tab 键怎样切换焦点,而当焦点变化时。同步改变颜色选择组件的焦点。最后我设置 redLoader 拥有初始焦点。

    图 2 是执行效果图:


               图 3 loader与按键、焦点

    你能够使用 qmlscene loader_focus.qml 命令执行看看效果,鼠标点击某个颜色选择组件,会触发焦点切换和边框变化,左右键、 tab 键也会触发焦点变化。而当一个颜色选择组件拥有焦点时。回车、空格键都能够触发 "Hello World!" 改变颜色。

从文件载入组件

    之前介绍 Loader 时,我们以嵌入式定义的 Component 为样例说明 Loader 的各种特性和使用方法,如今我们来看怎样从文件载入组件。

    对于定义在一个独立文件里的 Component ,相同能够使用 Loader 来载入,仅仅要指定 Loader 的 source 属性就可以。如今再来改动下我们的样例。使用 Loader 来载入 ColorPicker 组件。

    新的 qml 文档我命名为 loader_component_file.qml ,内容例如以下:

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#EEEEEE";
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Loader{
        id: redLoader;
        width: 80;
        height: 60;
        focus: true;
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        source: "ColorPicker.qml";
        KeyNavigation.right: blueLoader;
        KeyNavigation.tab: blueLoader;
        
        onLoaded:{
            item.color = "red";
            item.focus = true;
        }
        
        onFocusChanged:{  
            item.focus = focus;
        }
    }
    
    Loader{
        id: blueLoader;
        focus: true;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        source: "ColorPicker.qml";
        KeyNavigation.left: redLoader;
        KeyNavigation.tab: redLoader;
        
        onLoaded:{
            item.color = "blue";
        }
        
        onFocusChanged:{
            item.focus = focus;
        }  
    }

    Connections {
        target: redLoader.item;
        onColorPicked:{
            coloredText.color = clr;
            if(!redLoader.focus){
                redLoader.focus = true;
                blueLoader.focus = false;
            }
        }
    }
    
    Connections {
        target: blueLoader.item;
        onColorPicked:{
            coloredText.color = clr;
            if(!blueLoader.focus){
                blueLoader.focus = true;
                redLoader.focus = false;
            }
        }
    }
}

    代码有几处改动:

    一处是将 sourceComponent 改动为 source 。其值为 "ColorPicker.qml" 。

    一处是两个 Connections 对象。在 onColorPicked 信号处理器中。设置了 Loader 的焦点属性,由于 仅仅有 Loader 有焦点,它载入的 item 才会有焦点。假设你鼠标点击某个颜色选择组件而载入它的 Loader 没有焦点。那么尽管颜色能够改变,可是焦点框出不来。

    使用 Loader 载入定义在 qml 文档中的组件,比直接使用组件名构造对象要繁琐得多,但假设你的应用会依据特定的情景来决定某些界面元素是否显示。这样的方式或许能够满足你。

利用 Loader 动态创建与销毁组件

    如今我们看看怎样动态创建、销毁组件。以下是 dynamic_component.qml :

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    width: 320;
    height: 240;
    color: "#EEEEEE";
    id: rootItem;
    property var colorPickerShow : false;
    
    Text {
        id: coloredText;
        anchors.horizontalCenter: parent.horizontalCenter;
        anchors.top: parent.top;
        anchors.topMargin: 4;
        text: "Hello World!";
        font.pixelSize: 32;
    }
    
    Button {
        id: ctrlButton;
        text: "Show";
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        
        onClicked:{
           if(rootItem.colorPickerShow){
               redLoader.source = "";
               blueLoader.source = "";
               rootItem.colorPickerShow = false;
               ctrlButton.text = "Show";
           }else{
               redLoader.source = "ColorPicker.qml";
               redLoader.item.colorPicked.connect(onPickedRed);
               blueLoader.source = "ColorPicker.qml";
               blueLoader.item.colorPicked.connect(onPickedBlue);
               redLoader.focus = true;
               rootItem.colorPickerShow = true;
               ctrlButton.text = "Hide";
           }
        }
    }
    
    Loader{
        id: redLoader;
        anchors.left: ctrlButton.right;
        anchors.leftMargin: 4;
        anchors.bottom: ctrlButton.bottom;
        
        KeyNavigation.right: blueLoader;
        KeyNavigation.tab: blueLoader;
        
        onLoaded:{
            if(item != null){
                item.color = "red";
                item.focus = true;
            }
        }
        
        onFocusChanged:{  
            if(item != null){
                item.focus = focus;
            }
        }
    }
    
    Loader{
        id: blueLoader;
        anchors.left: redLoader.right;
        anchors.leftMargin: 4;
        anchors.bottom: redLoader.bottom;

        KeyNavigation.left: redLoader;
        KeyNavigation.tab: redLoader;
        
        onLoaded:{
            if(item != null){
                item.color = "blue";
            }
        }
        
        onFocusChanged:{
            if(item != null){
                item.focus = focus;
            }
        }  
    }
    
    function onPickedBlue(clr){
        coloredText.color = clr;
        if(!blueLoader.focus){
           blueLoader.focus = true;
           redLoader.focus = false;
        }
    }
    
    function onPickedRed(clr){
        coloredText.color = clr;
        if(!redLoader.focus){
           redLoader.focus = true;
           blueLoader.focus = false;
        }    
    }
}

    这次我们在界面上放一个button,通过button来控制颜色选择组件的创建与销毁。

启动应用时没有创建颜色选择组件,如图 4 所看到的:


            图 4 动态创建组件初始效果

    当你点击 "Show" 按键,代码通过设置 redLoader 和 blueLoader 的 source 来创建颜色选择组件,连接颜色组件的 colorPicked 信号到相应的方法,同一时候将改变button文字,也改变 rootItem 维护的颜色组件是否显示的标志位以便下次再点击button能够正常显示。

图 5 是颜色选择组件显示后的效果图:


            图 5 颜色组件创建后的效果


    使用 Loader 控制组件的动态创建与销毁。仅仅是 Qt Quick 提供的动态维护对象的两种方式中的一种。

另一种,是在 JavaScript 中动态创建 QML 对象。

在 JavaScript 中动态创建 QML 对象

    QML 支持在 JavaScript 中动态创建对象。

这对于延迟对象的创建、缩短应用的启动时间都是有帮助的。同一时候这样的机制也使得我们能够依据用户的输入或者某些事件来动态的将可见元素加入到应用场景中。

    在 JavaScript 中。有两种方式能够动态地创建对象:

  1. 使用 Qt.createComponent() 动态地创建一个组件对象,然后使用 Component 的 createObject() 方法创建对象
  2. 使用  Qt.createQmlObject() 从一个 QML 字符串直接创建一个对象

    假设你在一个 qml 文件里定义了一个组件(比方我们的 ColorPicker )。而你想动态地创建它的实例。使用 Qt.createComponent() 是比較好的方式。而假设你的 QML 对象本身是在应用执行时产生的,那 Qt.createQmlObject() 可能是比較好的选择。

从组件文件动态创建 Component

    Qt 对象的 createComponent() 方法能够依据 QML 文件动态的创建一个组件。

一旦你拥有了组件对象,就能够调用它的 createObject() 方法创建一个组件的实例。

以下是我们新的演示样例, qml 文件是 qt_create_component.qml :

import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    id: rootItem;
    width: 360;
    height: 300;
    property var count: 0;
    property Component component: null;
    
    Text {
        id: coloredText;
        text: "Hello World!";
        anchors.centerIn: parent;
        font.pixelSize: 24;
    }
    
    function changeTextColor(clr){
        coloredText.color = clr;
    }
    
    function createColorPicker(clr){
        if(rootItem.component == null){
            rootItem.component = Qt.createComponent("ColorPicker.qml");
        }
        var colorPicker;
        if(rootItem.component.status == Component.Ready) {
            colorPicker = rootItem.component.createObject(rootItem, {"color" : clr, "x" : rootItem.count *55, "y" : 10});
            colorPicker.colorPicked.connect(rootItem.changeTextColor);
        }
        
        rootItem.count++;
    }
    
    Button {
        id: add;
        text: "add";
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        onClicked: {
            createColorPicker(Qt.rgba(Math.random(), Math.random(), Math.random(), 1));
        }
    }
}

    图 6 是演示样例启动后的界面:


            图 6 qt create component 初始效果

    图 7 是我点击了 6 次 "add" button后的效果:


                图 7 动态创建了颜色选择组件

    好啦。如今让我们来看看代码。

    我在 qt_create_component.qml 中定义了 createColorPicker() 函数,该函数的參数是颜色值,它依据颜色值来创建一个颜色选择组件实例。首先它推断 rootItem 的 component 属性假设为 null ,就调用 Qt.createComponent() 创建一个 ColorPicker 组件,然后调用 Component.createObject() 创建一个颜色选择组件实例。

createObject() 方法有两个參数。第一个參数用来指定创建出来的 item 的 parent ,第二个參数用来传递初始化參数给待创建的 item ,这些參数以 key - value 的形式保存在一个对象中。

我在创建颜色组件实例时,传递颜色、 x 、 y 三个属性给待创建的 item ,于是你看到了,那些色块都在界面顶部。

创建了颜色选择组件实例,我调用 colorPicked 信号的 connect() 方法。连接 rootItem 的 changeTextColor 方法,以便用户点击色块时改变 "Hello World!"  文本的颜色。

    再来看 "add" button,它的 onClicked 信号处理器,调用 Math 对象的随机函数 random() 和 Qt.rgba() 。随机生成一个 Color 对象,传递给 createColorPicker() 方法来创建指定颜色的颜色选择组件实例。

    提一下,对于嵌入在 qml 文档内定义的 Component ,由于 Component 对象是现成的,能够略去 Qt.createComponent() 调用,直接使用 createObject() 方法创建组件实例。

    代码就这么简单,讲解到此为止。如今让我们看看怎么使用 Qt.createQmlObject() 来创建对象。

从 QML 字符串创建对象

    假设你的软件。须要在执行过程中。依据应用的状态适时的生成用于描写叙述对象的 QML 字符串,进而依据这个 QML 字符串创建对象,那么能够使用像以下这样:
var newObject = Qt.createQmlObject('import QtQuick 2.0; Rectangle {color: "red"; width: 20; height: 20}',
    parentItem, "dynamicSnippet1");

    createQmlObject 的第一个參数是要创建对象的 QML 字符串,就像一个 QML 文档一样。你须要导入你用到的全部类型和模块;第二个參数用于指定要创建的对象的父对象;第三个參数。用于给新创建的对象关联一个文件路径,主要用于报告错误。

    对于动态创建的对象,该怎样销毁呢?

销毁动态创建的对象

    有些软件,在不须要一个动态创建的 QML 对象时,仅仅是把它的 visible 属性设置为 false 或者把 opactity 属性设置为 0 ,而不是删除这个对象。

假设动态创建的对象非常多,没用的对象都这么处理而不直接删除,那会给软件带来比較大的性能问题,比方内存占用增多,执行速度变慢等等。所以呢。动态创建的对象,不再使用时,最好把它删除掉。

    我们这里说的动态创建的对象,特指使用 Qt.createComponent() 或 Qt.createQmlObject() 方法创建的对象, 使用 Loader 创建的对象,应当通过将 source 设置为空串或将 sourceComponent 设置为 undefined 触发 Loader 销毁它们。

    要删除一个对象,能够调用其 destroy() 方法。 destroy() 方法有一个可选參数,指定延迟多少毫秒再删除这个对象。其默认值为 0 。 destroy() 方法有点儿像 Qt C++ 中 QObject 的 deleteLater() 方法。即便你设定延迟为 0 去调用它,对象也并不会马上删除,QML 引擎会在当前代码块执行结束后的某个合适的时刻删除它们。所以呢。即便你在一个对象内部调用 destroy() 方法也是安全的。
    如今让我我们再来一个实例,看看怎样销毁对象。新的 qml 文件命名为 delete_dynamic_object.qml ,从 qt_create_component.qml 拷贝而来。作了一点点改动。先看下:
import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    id: rootItem;
    width: 360;
    height: 300;
    property var count: 0;
    property Component component: null;
    
    Text {
        id: coloredText;
        text: "Hello World!";
        anchors.centerIn: parent;
        font.pixelSize: 24;
    }
    
    function changeTextColor(clr){
        coloredText.color = clr;
    }
    
    function createColorPicker(clr){
        if(rootItem.component == null){
            rootItem.component = Qt.createComponent("ColorPicker.qml");
        }
        var colorPicker;
        if(rootItem.component.status == Component.Ready) {
            colorPicker = rootItem.component.createObject(rootItem, {"color" : clr, "x" : rootItem.count *55, "y" : 10});
            colorPicker.colorPicked.connect(rootItem.changeTextColor);
            //[1] add 3 lines to delete some obejcts
            if(rootItem.count % 2 == 1) {
                colorPicker.destroy(1000);
            }
        }
        
        rootItem.count++;
    }
    
    Button {
        id: add;
        text: "add";
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        onClicked: {
            createColorPicker(Qt.rgba(Math.random(), Math.random(), Math.random(), 1));
        }
    }
}

    改动的部分我用凝视标注出来了:加入了三行代码,新创建的颜色选择组件实例,隔一个删一个。 destroy(1000)  调用指示对象在 1 秒后删除。
    图 8 是执行后的效果图:

                图 8 删除动态创建的对象
    我还制作了一个演示删除动态创建的对象的演示样例, qml 文件是 delete_dynamic_object2.qml ,我把点击 "add" button创建的对象保存在一个数组中。当你点击 "del" button时,删除最后加入的那个颜色选择组件实例。以下是代码:
import QtQuick 2.0
import QtQuick.Controls 1.1

Rectangle {
    id: rootItem;
    width: 360;
    height: 300;
    property var count: 0;
    property Component component: null;
    property var dynamicObjects: new Array();
    
    Text {
        id: coloredText;
        text: "Hello World!";
        anchors.centerIn: parent;
        font.pixelSize: 24;
    }
    
    function changeTextColor(clr){
        coloredText.color = clr;
    }
    
    function createColorPicker(clr){
        if(rootItem.component == null){
            rootItem.component = Qt.createComponent("ColorPicker.qml");
        }
        var colorPicker;
        if(rootItem.component.status == Component.Ready) {
            colorPicker = rootItem.component.createObject(rootItem, {"color" : clr, "x" : rootItem.dynamicObjects.length *55, "y" : 10});
            colorPicker.colorPicked.connect(rootItem.changeTextColor);
            rootItem.dynamicObjects[rootItem.dynamicObjects.length] = colorPicker;
            console.log("add, rootItem.dynamicObject.length = ", rootItem.dynamicObjects.length);
        }
    }
    
    Button {
        id: add;
        text: "add";
        anchors.left: parent.left;
        anchors.leftMargin: 4;
        anchors.bottom: parent.bottom;
        anchors.bottomMargin: 4;
        onClicked: {
            createColorPicker(Qt.rgba(Math.random(), Math.random(), Math.random(), 1));
        }
    }
    Button {
        id: del;
        text: "del";
        anchors.left: add.right;
        anchors.leftMargin: 4;
        anchors.bottom: add.bottom;
        onClicked: {
            console.log("rootItem.dynamicObject.length = ", rootItem.dynamicObjects.length);
            if(rootItem.dynamicObjects.length > 0){
                var deleted = rootItem.dynamicObjects.splice(-1, 1);
                deleted[0].destroy();
            }
        }
    }    
}

    你能够自己使用 qmlscene 执行 delete_dynamic_object2.qml 看看效果。

  
    版权全部 foruok 。转载请注明出处:http://blog.csdn.net/foruok 。
    本篇到此结束。



回想一下:

版权声明:本文foruok原创文章,博客,未经同意,不得转载。

posted @ 2015-08-21 18:42  phlsheji  阅读(3828)  评论(0编辑  收藏  举报