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原創文章,博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM