QML 組件與對(duì)象動(dòng)態(tài)創(chuàng)建詳解
? ? Component 是由 Qt 框架或開發(fā)者封裝好的、只暴露了必要接口的 QML 類型,可以重復(fù)利用。一個(gè) QML 組件就像一個(gè)黑盒子,它通過屬性、信號(hào)、函數(shù)和外部世界交互。
? ? 一個(gè) Component 即可以定義在獨(dú)立的 qml 文件中,也可以嵌入到其它的 qml 文檔中來定義。通常我們可以根據(jù)這個(gè)原則來選擇將一個(gè) Component 定義在哪里:如果一個(gè) Component 比較小且只在某個(gè) qml 文檔中使用或者一個(gè) Component 從邏輯上看從屬于某個(gè) qml 文檔,那就可以采用嵌入的方式來定義該 Component 。你也可以與 C++ 的嵌套類對(duì)比來理解。
嵌入式定義組件? ??《Qt Quick 事件處理之信號(hào)與槽》一文中使用到 Component 的示例 QML 代碼如下:
[javascript]?view plain?copyimport?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;??????????}??????}??}??? ? 其中,顏色選擇組件的定義代碼如下:
[javascript]?view plain?copyComponent?{??????id:?colorComponent;??????Rectangle?{??????????id:?colorPicker;??????????width:?50;??????????height:?30;??????????signal?colorPicked(color?clr);??????????MouseArea?{??????????????anchors.fill:?parent??????????????onPressed:?colorPicker.colorPicked(colorPicker.color);??????????}??????}??}??? ? 如你所見,要在一個(gè) QML 文檔中嵌入 Component 的定義,需要使用 Component 對(duì)象。
? ? 定義一個(gè) Component 與定義一個(gè) QML 文檔類似, Component 只能包含一個(gè)頂層 item ,而且在這個(gè) item 之外不能定義任何數(shù)據(jù),除了 id 。比如上面的代碼中,頂層 item 是 Rectangle 對(duì)象,在 Rectangle 之外我定義了 id 屬性,其值為 colorComponent 。而頂層 item 之內(nèi),則可以包含更多的子元素來協(xié)同工作,最終形成一個(gè)具有特定功能的組件。
? ? Component 通常用來給一個(gè) view 提供圖形化組件,比如 ListView::delegate 屬性就需要一個(gè) Component 來指定如何顯示列表的每一個(gè)項(xiàng),又比如 ButtonStyle::background 屬性也需要一個(gè) Component 來指定如何繪制 Button 的背景。?
? ? Component 不是 Item 的派生類,而是從 QQmlComponent 繼承而來,雖然它通過自己的頂層 item 為其它的 view 提供可視化組件,但它本身是不可見元素。你可以這么理解:你定義的組件是一個(gè)新的類型,它必須被實(shí)例化以后才可能顯示。而要實(shí)例化一個(gè)嵌入在 qml 文檔中定義的組件,則可以通過 Loader 。后面我們?cè)敿?xì)講述 Loader ,這里先按下不表,我們要來看如何在一個(gè)文件中定義組件了。
在單獨(dú)文件中定義組件? ? 很多時(shí)候我們把一個(gè) Component 單獨(dú)定義在一個(gè) qml 文檔中,比如 Qt Quick 提供的 BusyIndicator 控件,其實(shí)就是在 BusyIndicator.qml 中定義的一個(gè)組件。下面是 BusyIndicator.qml 文件的內(nèi)容:
[javascript]?view plain?copyControl?{??????id:?indicator????????property?bool?running:?true????????Accessible.role:?Accessible.Indicator??????Accessible.name:?"busy"????????style:?Qt.createComponent(Settings.style?+?"/BusyIndicatorStyle.qml",?indicator)??}??? ? 我在《Qt Quick 簡單教程》一文中的顯示網(wǎng)絡(luò)圖片實(shí)例中,使用了 BusyIndicator 來提示用戶圖片正在加載中需要等候,你可以跳轉(zhuǎn)到那篇文章學(xué)習(xí) BusyIndicator 的用法。
? ? BusyIndicator 組件的代碼非常簡單,只是給 Control 元素(Qt Quick 定義的私有元素,用作其它控件的基類,如 ComboBox 、 BusyIndicator 等)增加了一個(gè)屬性、設(shè)置了幾個(gè)屬性的值,僅此而已。
? ? 不知你是否注意到了, BusyIndicator.qml 文件中的頂層 item 是 Control ,而我們使用時(shí)卻是以 BusyIndicator 為組件名(類名)。這是我們定義 Component 時(shí)要遵守的一個(gè)約定:組件名字必須和 qml 文件名一致。好嘛,和 Java 一樣啦,類名就是文件名。還有一點(diǎn),組件名字的第一個(gè)字母必須是大寫。對(duì)于在文件中定義一個(gè)組件,就這么簡單了,再?zèng)]有其它的特殊要求。 Qt Quick 提供的多數(shù)基本元素和特性,你都可以在定義組件時(shí)使用。
? ? 一旦你在文件中定義了一個(gè)組件,就可以像使用標(biāo)準(zhǔn) Qt Quick 元素一樣使用你的組件。比如我們給顏色選擇組件起個(gè)名字叫 ColorPicker ,對(duì)應(yīng)的 qml 文件為 ?ColorPicker.qml ,那么你就可以在其它 QML 文檔中使用 ColorPicker {...} 來定義 ColorPicker 的實(shí)例。
? ? 好啦,現(xiàn)在讓我們來看看在單獨(dú)文件中定義的 ColorPicker 組件:
[javascript]?view plain?copyimport?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();??????}??}??? ? 請(qǐng)注意上面的代碼,它和嵌入式定義有明顯不同: Component 對(duì)象不見咧!對(duì),就是醬紫滴:在單獨(dú)文件內(nèi)定義組件,不需要 Component 對(duì)象,只有在其它 QML 文檔中嵌入式定義組件時(shí)才需要 Component 對(duì)象。另外,為了能夠讓多個(gè) ColorPicker 組件可以正常的顯示焦點(diǎn)框,我還使用了 onClicked 信號(hào)處理器,新增了 onFocusChanged 信號(hào)處理器,在它們的實(shí)現(xiàn)中調(diào)用 configureBorder() 函數(shù)來重新設(shè)置邊框的寬度和顏色,新增 onReturnPressed 和 onSpacePressed 以便響應(yīng)回車和空格兩個(gè)按鍵。
? ? 你可以使用 Item 或其派生類作為組件的根 item 。 ColorPicker 組件使用 Rectangle 作為根 Item ?,F(xiàn)在讓我們看看如實(shí)在其它文件中使用新定義的 ColorPicker 組件。我修改了上節(jié)的示例,新的 qml 文件被我命名為 component_file.qml ,內(nèi)容如下:
[javascript]?view plain?copyimport?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 等標(biāo)準(zhǔn) Qt Quick 組件完全一致:可以給組件指定唯一的 id ,可以使用錨布局,可以使用 KeyNavigation 附加屬性……總之,自定義的組件和 Qt Quick 組件并無本質(zhì)不同。不過需要注意的是,組件實(shí)例的 id 和組成組件的頂層 item 的 id 是各自獨(dú)立的,以上面的例子來看, redColor 和 colorPicker 是兩個(gè)不同的 id ,前者指代組件對(duì)象(雖然組件的定義沒有使用 Component ),后者指代 ColorPicker 的 Rectangle 對(duì)象。
? ? 上面的代碼還演示兩種使用 qml 自定義信號(hào)的方式, redColor 使用信號(hào)處理器, greeColor 和 pinkColor 則使用了 signal 對(duì)象的 connect() 方法連接到 setTextColor() 方法上。
? ? 我把 ColorPicker.qml 和 component_file.qml 放在同一個(gè)文件下面,否則可能會(huì)報(bào)錯(cuò)。圖 1 是運(yùn)行 "qmlscene component_file.qml" 命令 的效果:
? ? ? ? ? ? 圖 1 在文件中定義組件并使用
? ? 對(duì)于定義在單獨(dú)文件中的 Component ,除了可以像剛剛介紹的那樣使用,也可以使用 Loader 來動(dòng)態(tài)加載,根據(jù)需要再創(chuàng)建對(duì)象。下面我們就來看 Loader 究竟是何方妖怪。
使用 LoaderLoader 的詳細(xì)介紹? ? Loader 用來動(dòng)態(tài)加載 QML 組件。
? ? Loader 可以使用其 source 屬性加載一個(gè) qml 文檔,也可以通過其 sourceComponent 屬性加載一個(gè) Component 對(duì)象。當(dāng)你需要延遲一些對(duì)象直到真正需要才創(chuàng)建它們時(shí), Loader 非常有用。 當(dāng) Loader 的 source 或 sourceComponent 屬性發(fā)生變化時(shí),它之前加載的 Component 會(huì)自動(dòng)銷毀,新對(duì)象會(huì)被加載。將 source 設(shè)置為一個(gè)空字符串或?qū)?sourceComponent 設(shè)置為 undefined ,將會(huì)銷毀當(dāng)前加載的對(duì)象,相關(guān)的資源也會(huì)被釋放,而 Loader 對(duì)象則變成一個(gè)空對(duì)象。
? ? Loader 的 item 屬性指向它加載的組件的頂層 item ,比如 Loader 加載了我們的顏色選擇組件,其 item 屬性就指向顏色選擇組件的 Rectangle 對(duì)象。對(duì)于 Loader 加載的 item ,它暴露出來的接口,如屬性、信號(hào)等,都可以通過 Loader 的 item 屬性來訪問。所以我們才可以這么使用:
[javascript]?view plain?copyLoader{??????id:?redLoader;??????anchors.left:?parent.left;??????anchors.leftMargin:?4;??????anchors.bottom:?parent.bottom;??????anchors.bottomMargin:?4;??????sourceComponent:?colorComponent;??????onLoaded:{??????????item.color?=?"red";??????}??}??? ? 上面的代碼在 Loader 對(duì)象使用 sourceComponent 屬性來加載 id 為 colorComponent 的組件對(duì)象,然后在 onLoaded 信號(hào)處理器中使用 item 屬性來設(shè)置顏色選擇組件的顏色。對(duì)于信號(hào)的訪問,我們則可以使用 Connections 對(duì)象,如下面的 qml 代碼所示:
[javascript]?view plain?copyConnections?{??????target:?redLoader.item;??????onColorPicked:{??????????coloredText.color?=?clr;??????}??}??? ? 我們創(chuàng)建的 Connections 對(duì)象,其 target 指向 redLoader.item ,即指向顏色選擇組件的頂層 item —— Rectangle ,所以可以直接響應(yīng)它的 colorPicked 信號(hào)。
? ? 雖然 Loader 本身是 Item 的派生類,但沒有加載 Component 的 Loader 對(duì)象是不可見的,沒什么實(shí)際的意義。而一旦你加載了一個(gè) Component , Loader 的大小、位置等屬性卻可以影響它所加載的 Component 。如果你沒有顯式指定 Loader 的大小,那么 Loader 會(huì)將自己的尺寸調(diào)整為與它加載的可見 item 的尺寸一致;如果 Loader 的大小通過 width 、 height 或 錨布局顯式設(shè)置了,那么它加載的可見 item 的尺寸會(huì)被調(diào)整以便適應(yīng) Loader 的大小。不管是哪種情況, Loader 和它所加載的 item 具有相同的尺寸,這確保你使用錨來布局 Loader 就等同于布局它加載的 item 。
? ? 我們改變一下顏色選擇器示例的代碼,兩個(gè) Loader 對(duì)象,一個(gè)設(shè)置尺寸一個(gè)不設(shè)置,看看是什么效果。新的 qml 文檔我們命名為 loader_test.qml ,內(nèi)容如下:
[javascript]?view plain?copyimport?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;??????????}??????}??}??? ? 注意上面的代碼中的注釋,方括號(hào)標(biāo)準(zhǔn)的 2 處修改,設(shè)置了紅色 Loader 的尺寸,效果如圖 1 所示:
? ? ? ? ? ? ? ? 圖 2 Loader 尺寸
? ? 如果 Loader 加載的 item 想處理按鍵事件,那么必須將 Loader 對(duì)象的 focus 屬性置 true 。又因?yàn)?Loader 本身也是一個(gè)焦點(diǎn)敏感的對(duì)象,所以如果它加載的 item 處理了按鍵事件,應(yīng)當(dāng)將事件的 accepted 屬性置 true ,以免已經(jīng)被吃掉的事件再傳遞給 Loader 。我們來修改下 loader_test.qml ,加入對(duì)焦點(diǎn)的處理,當(dāng)一個(gè)顏色組件擁有焦點(diǎn)時(shí),繪制一個(gè)邊框,此時(shí)如果你按下回車或空格鍵,會(huì)觸發(fā)其 colorPicked 信號(hào)。同時(shí)我們也處理左右鍵,在不同的顏色選擇組件之間切換焦點(diǎn)。將新代碼命名為 loader_focus.qml ,內(nèi)容如下:
[javascript]?view plain?copyimport?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;??????????}??????}??}??? ? 首先我讓顏色選擇組件處理按鍵事件(如忘記請(qǐng)參看《Qt Quick事件處理之鼠標(biāo)、鍵盤、定時(shí)器》),收到回車和空格鍵時(shí)發(fā)出 colorPicked 信號(hào)。我還給顏色選擇組件定義了一個(gè)名為 loader 的屬性,以便鼠標(biāo)點(diǎn)擊顏色選擇組件時(shí)可以改變 Loader 對(duì)象的焦點(diǎn)屬性。我們?cè)?Loader 的 onLoaded 信號(hào)處理器中給顏色選擇組件的 loader 屬性賦值。
? ? 顏色選擇組件根據(jù)焦點(diǎn)狀態(tài)決定是否繪制邊框,當(dāng)有焦點(diǎn)時(shí)繪制寬度為 2 的邊框。
? ? 對(duì)于 Loader ,我設(shè)置了 KeyNavigation 附加屬性,指定左右鍵和 tab 鍵如何切換焦點(diǎn),而當(dāng)焦點(diǎn)變化時(shí),同步改變顏色選擇組件的焦點(diǎn)。最后我設(shè)置 redLoader 擁有初始焦點(diǎn)。
? ? 圖 2 是運(yùn)行效果圖:
? ? ? ? ? ? ? ?圖 3 loader與按鍵、焦點(diǎn)
? ? 你可以使用 qmlscene loader_focus.qml 命令運(yùn)行看看效果,鼠標(biāo)點(diǎn)擊某個(gè)顏色選擇組件,會(huì)觸發(fā)焦點(diǎn)切換和邊框變化,左右鍵、 tab 鍵也會(huì)觸發(fā)焦點(diǎn)變化,而當(dāng)一個(gè)顏色選擇組件擁有焦點(diǎn)時(shí),回車、空格鍵都可以觸發(fā) "Hello World!" 改變顏色。
從文件加載組件? ? 之前介紹 Loader 時(shí),我們以嵌入式定義的 Component 為例子說明 Loader 的各種特性和用法,現(xiàn)在我們來看如何從文件加載組件。
? ? 對(duì)于定義在一個(gè)獨(dú)立文件中的 Component ,同樣可以使用 Loader 來加載,只要指定 Loader 的 source 屬性即可?,F(xiàn)在再來修改下我們的例子,使用 Loader 來加載 ColorPicker 組件。
? ? 新的 qml 文檔我命名為 loader_component_file.qml ,內(nèi)容如下:
[javascript]?view plain?copyimport?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;??????????????}??????????}??????}??}??? ? 代碼有幾處改動(dòng):
? ? 一處是將 sourceComponent 修改為 source ,其值為 "ColorPicker.qml" 。
? ? 一處是兩個(gè) Connections 對(duì)象,在 onColorPicked 信號(hào)處理器中,設(shè)置了 Loader 的焦點(diǎn)屬性,因?yàn)?只有 Loader 有焦點(diǎn),它加載的 item 才會(huì)有焦點(diǎn),如果你鼠標(biāo)點(diǎn)擊某個(gè)顏色選擇組件而加載它的 Loader 沒有焦點(diǎn),那么雖然顏色可以改變,但是焦點(diǎn)框出不來。
? ? 使用 Loader 加載定義在 qml 文檔中的組件,比直接使用組件名構(gòu)造對(duì)象要繁瑣得多,但如果你的應(yīng)用會(huì)根據(jù)特定的情景來決定某些界面元素是否顯示,這種方式也許可以滿足你。
利用 Loader 動(dòng)態(tài)創(chuàng)建與銷毀組件? ? 現(xiàn)在我們看看如何動(dòng)態(tài)創(chuàng)建、銷毀組件。下面是 dynamic_component.qml :
[javascript]?view plain?copyimport?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;??????????}??????????}??}??? ? 這次我們?cè)诮缑嫔戏乓粋€(gè)按鈕,通過按鈕來控制顏色選擇組件的創(chuàng)建與銷毀。啟動(dòng)應(yīng)用時(shí)沒有創(chuàng)建顏色選擇組件,如圖 4 所示:
? ? ? ? ? ? 圖 4 動(dòng)態(tài)創(chuàng)建組件初始效果
? ? 當(dāng)你點(diǎn)擊 "Show" 按鍵,代碼通過設(shè)置 redLoader 和 blueLoader 的 source 來創(chuàng)建顏色選擇組件,連接顏色組件的 colorPicked 信號(hào)到相應(yīng)的方法,同時(shí)將改變按鈕文字,也改變 rootItem 維護(hù)的顏色組件是否顯示的標(biāo)志位以便下次再點(diǎn)擊按鈕可以正常顯示。圖 5 是顏色選擇組件顯示后的效果圖:
? ? ? ? ? ? 圖 5 顏色組件創(chuàng)建后的效果
? ? 使用 Loader 控制組件的動(dòng)態(tài)創(chuàng)建與銷毀,只是 Qt Quick 提供的動(dòng)態(tài)維護(hù)對(duì)象的兩種方式中的一種。還有一種,是在 JavaScript 中動(dòng)態(tài)創(chuàng)建 QML 對(duì)象。
在 JavaScript 中動(dòng)態(tài)創(chuàng)建 QML 對(duì)象? ? QML 支持在 JavaScript 中動(dòng)態(tài)創(chuàng)建對(duì)象。這對(duì)于延遲對(duì)象的創(chuàng)建、縮短應(yīng)用的啟動(dòng)時(shí)間都是有幫助的。同時(shí)這種機(jī)制也使得我們可以根據(jù)用戶的輸入或者某些事件來動(dòng)態(tài)的將可見元素添加到應(yīng)用場景中。
? ? 在 JavaScript 中,有兩種方式可以動(dòng)態(tài)地創(chuàng)建對(duì)象:
使用?Qt.createComponent() 動(dòng)態(tài)地創(chuàng)建一個(gè)組件對(duì)象,然后使用 Component 的 createObject() 方法創(chuàng)建對(duì)象使用??Qt.createQmlObject() 從一個(gè) QML 字符串直接創(chuàng)建一個(gè)對(duì)象? ? 如果你在一個(gè) qml 文件中定義了一個(gè)組件(比如我們的 ColorPicker ),而你想動(dòng)態(tài)地創(chuàng)建它的實(shí)例,使用 Qt.createComponent() 是比較好的方式;而如果你的 QML 對(duì)象本身是在應(yīng)用運(yùn)行時(shí)產(chǎn)生的,那 Qt.createQmlObject() 可能是比較好的選擇。
從組件文件動(dòng)態(tài)創(chuàng)建 Component? ? Qt 對(duì)象的 createComponent() 方法可以根據(jù) QML 文件動(dòng)態(tài)的創(chuàng)建一個(gè)組件。一旦你擁有了組件對(duì)象,就可以調(diào)用它的 createObject() 方法創(chuàng)建一個(gè)組件的實(shí)例。下面是我們新的示例, qml 文件是 qt_create_component.qml :
[javascript]?view plain?copyimport?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 是示例啟動(dòng)后的界面:
? ? ? ? ? ? 圖 6 qt create component 初始效果
? ? 圖 7 是我點(diǎn)擊了 6 次 "add" 按鈕后的效果:
? ? ? ? ? ? ? ? 圖 7 動(dòng)態(tài)創(chuàng)建了顏色選擇組件
? ? 好啦,現(xiàn)在讓我們來看看代碼。
? ? 我在 qt_create_component.qml 中定義了?createColorPicker() 函數(shù),該函數(shù)的參數(shù)是顏色值,它根據(jù)顏色值來創(chuàng)建一個(gè)顏色選擇組件實(shí)例。首先它判斷 rootItem 的 component 屬性如果為 null ,就調(diào)用 Qt.createComponent() 創(chuàng)建一個(gè) ColorPicker 組件,然后調(diào)用 Component.createObject() 創(chuàng)建一個(gè)顏色選擇組件實(shí)例。 createObject() 方法有兩個(gè)參數(shù),第一個(gè)參數(shù)用來指定創(chuàng)建出來的 item 的 parent ,第二個(gè)參數(shù)用來傳遞初始化參數(shù)給待創(chuàng)建的 item ,這些參數(shù)以 key - value 的形式保存在一個(gè)對(duì)象中。我在創(chuàng)建顏色組件實(shí)例時(shí),傳遞顏色、 x 、 y 三個(gè)屬性給待創(chuàng)建的 item ,于是你看到了,那些色塊都在界面頂部。創(chuàng)建了顏色選擇組件實(shí)例,我調(diào)用 colorPicked 信號(hào)的 connect() 方法,連接 rootItem 的 changeTextColor 方法,以便用戶點(diǎn)擊色塊時(shí)改變 "Hello World!" ?文本的顏色。
? ? 再來看 "add" 按鈕,它的 onClicked 信號(hào)處理器,調(diào)用 Math 對(duì)象的隨機(jī)函數(shù) random() 和 Qt.rgba() ,隨機(jī)生成一個(gè) Color 對(duì)象,傳遞給 createColorPicker() 方法來創(chuàng)建指定顏色的顏色選擇組件實(shí)例。
? ? 提一下,對(duì)于嵌入在 qml 文檔內(nèi)定義的 Component ,因?yàn)?Component 對(duì)象是現(xiàn)成的,可以略去 Qt.createComponent() 調(diào)用,直接使用 createObject() 方法創(chuàng)建組件實(shí)例。
? ? 代碼就這么簡單,解說到此為止?,F(xiàn)在讓我們看看怎么使用 Qt.createQmlObject() 來創(chuàng)建對(duì)象。
從 QML 字符串創(chuàng)建對(duì)象? ? 如果你的軟件,需要在運(yùn)行過程中,根據(jù)應(yīng)用的狀態(tài)適時(shí)的生成用于描述對(duì)象的 QML 字符串,進(jìn)而根據(jù)這個(gè) QML 字符串創(chuàng)建對(duì)象,那么可以使用像下面這樣:[javascript]?view plain?copyvar?newObject?=?Qt.createQmlObject('import?QtQuick?2.0;?Rectangle?{color:?"red";?width:?20;?height:?20}',??????parentItem,?"dynamicSnippet1");??? ? createQmlObject 的第一個(gè)參數(shù)是要?jiǎng)?chuàng)建對(duì)象的 QML 字符串,就像一個(gè) QML 文檔一樣,你需要導(dǎo)入你用到的所有類型和模塊;第二個(gè)參數(shù)用于指定要?jiǎng)?chuàng)建的對(duì)象的父對(duì)象;第三個(gè)參數(shù),用于給新創(chuàng)建的對(duì)象關(guān)聯(lián)一個(gè)文件路徑,主要用于報(bào)告錯(cuò)誤。
? ? 對(duì)于動(dòng)態(tài)創(chuàng)建的對(duì)象,該如何銷毀呢?
銷毀動(dòng)態(tài)創(chuàng)建的對(duì)象? ? 有些軟件,在不需要一個(gè)動(dòng)態(tài)創(chuàng)建的 QML 對(duì)象時(shí),僅僅是把它的 visible 屬性設(shè)置為 false 或者把 opactity 屬性設(shè)置為 0 ,而不是刪除這個(gè)對(duì)象。如果動(dòng)態(tài)創(chuàng)建的對(duì)象很多,無用的對(duì)象都這么處理而不直接刪除,那會(huì)給軟件帶來比較大的性能問題,比如內(nèi)存占用增多,運(yùn)行速度變慢等等。所以呢,動(dòng)態(tài)創(chuàng)建的對(duì)象,不再使用時(shí),最好把它刪除掉。? ? 我們這里說的動(dòng)態(tài)創(chuàng)建的對(duì)象,特指使用 Qt.createComponent() 或 Qt.createQmlObject() 方法創(chuàng)建的對(duì)象, 使用 Loader 創(chuàng)建的對(duì)象,應(yīng)當(dāng)通過將 source 設(shè)置為空串或?qū)?sourceComponent 設(shè)置為 undefined 觸發(fā) Loader 銷毀它們。? ? 要?jiǎng)h除一個(gè)對(duì)象,可以調(diào)用其 destroy() 方法。 destroy() 方法有一個(gè)可選參數(shù),指定延遲多少毫秒再刪除這個(gè)對(duì)象,其默認(rèn)值為 0 。 destroy() 方法有點(diǎn)兒像 Qt C++ 中 QObject 的 deleteLater() 方法,即便你設(shè)定延遲為 0 去調(diào)用它,對(duì)象也并不會(huì)立即刪除,QML 引擎會(huì)在當(dāng)前代碼塊執(zhí)行結(jié)束后的某個(gè)合適的時(shí)刻刪除它們。所以呢,即便你在一個(gè)對(duì)象內(nèi)部調(diào)用 destroy() 方法也是安全的。? ? 現(xiàn)在讓我我們?cè)賮硪粋€(gè)實(shí)例,看看如何銷毀對(duì)象。新的 qml 文件命名為 delete_dynamic_object.qml ,從 qt_create_component.qml 拷貝而來,作了一點(diǎn)點(diǎn)修改。先看下:[javascript]?view plain?copyimport?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));??????????}??????}??}??? ? 修改的部分我用注釋標(biāo)注出來了:添加了三行代碼,新創(chuàng)建的顏色選擇組件實(shí)例,隔一個(gè)刪一個(gè), destroy(1000) ?調(diào)用指示對(duì)象在 1 秒后刪除。? ? 圖 8 是運(yùn)行后的效果圖:
? ? ? ? ? ? ? ? 圖 8 刪除動(dòng)態(tài)創(chuàng)建的對(duì)象? ? 我還制作了一個(gè)演示刪除動(dòng)態(tài)創(chuàng)建的對(duì)象的示例, qml 文件是 delete_dynamic_object2.qml ,我把點(diǎn)擊 "add" 按鈕創(chuàng)建的對(duì)象保存在一個(gè)數(shù)組中,當(dāng)你點(diǎn)擊 "del" 按鈕時(shí),刪除最后添加的那個(gè)顏色選擇組件實(shí)例。下面是代碼:[javascript]?view plain?copyimport?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:?colored