在一個典型的應用程式中,並不會把所有代碼都編寫到一個檔案中。這樣會使多個開發者同事開發同一個項目和調試很困難,也會阻礙代碼重用。取而代之,使用多個MXML和ActionScript檔案來開發程式。這種架構會促進模組化設計、代碼重用、多人開發統一程式。MXML組件是一個MXML檔案,在其他檔案內部通過MXML標記來引用它。MXML組件的最大用處就是擴展已經存在的Flex組件的功能。
例如,Flex支持ComboBox控制項,你可以把它作為表單的一部分從客戶那裡收集地址信息。你可以使用ComboBox讓用戶從全世界的國家列表中選擇地址中的國家部分。在一個應用程式中有多個地方用戶需要輸入地址。使用世界上所有國家信息來創建和初始化多個ComboBox是非常乏味的事情。
Flex遵循這樣的原則:你創建一個自定義MXML組件,它包含一個ComboBox控制項並且所有國家的名字信息。那么,無論在那裡你增加一個國家選擇器,你都可以使用你自定義的MXML組件。
本文分為如下幾部分:
創建一個簡單的自定義組件
引用一個自定義組件的屬性和方法
創建一個複合的自定義組件
創建一個高級的自定義組件
創建一個簡單的自定義組件
你可以在一個MXML檔案中創建一個MXML組件,這個檔案的名字就變成了這個自定義組件的標籤名。比如:一個檔案名稱字叫做CountryComboBox.mxml,使用標籤<CountryComboBox>就可以定義這個組件。
一個MXML組件的根標籤,是一個組件的標籤,可以是Flex組件也可以是其他MXML組件。根標籤指定http://www.adobe.com/2006/mxml作為命名空間。例如,下邊的MXML組件擴展標準的Flex ComboBox控制項。可以把自定義組件的檔案放在項目的根目錄中,也可以放在一個子目錄中。Adobe推薦後一種方法作為最佳實踐。在這個例子中自定義組件被放置到componects資料夾中。在主程式的MXML檔案中,映射componects資料夾到custom命名空間,使用全標籤名<custom:CountryComboBox>來套用這個組件。
提示:在現實世界中,你可能看到自定義組件被放置在公司域名倒敘的目錄結構中。(比如, xmlns:custom="com.adobe.quickstarts.customcomponents.*")。這個習慣可以避免不同製造商在命名組件名稱相同時導致的衝突。例如,兩個組件庫可能各自包含Map組件,如果一個com.vendorA.*一個在com.vendorB.*那么他們就不會衝突
例子
components/CountryComboBox.mxml
<?xml version="1.0" Encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:dataProvider>
<mx:String>United States</mx:String>
<mx:String>United Kingdom</mx:String>
<!-- Add all other countries. -->
</mx:dataProvider>
</mx:ComboBox>
主檔案
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:custom="components.*"
width="220" height="115"
>
<custom:CountryComboBox/>
</mx:Application>
引用一個自定義組件的屬性和方法
CountryComboBox.mxml指定ComboBox作為其根標記,那么你可以引用所有ComboBox的屬性和方法,在自定義組件的MXML標記中,或者在<mx:Script>標記中的ActionScript中也可以。例如,下邊的例子為自定義組件指定了rowCount屬性和一個關閉事件的監聽器。
提示:在Flex中,MXML檔案在編譯成SWF檔案之前,先編譯成ActionScript類檔案。當你為自定義的MXML組件指定根標記後,你實際上使你的自定義組件類擴展了根標記的組件類。這就是為什麼自定義組件繼承了根標記組件的方法和屬性。
在下邊的例子中,CountryComboBox MXML檔案編譯為一個class檔案。Flex的命名習俗是組件名首字母大些,也對應類名。
例子
components/CountryComboBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:dataProvider>
<mx:String>United States</mx:String>
<mx:String>United Kingdom</mx:String>
<!-- Add all other countries. -->
</mx:dataProvider>
</mx:ComboBox>
主應用程式的MXML檔案
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:custom="components.*"
width="270" height="170"
>
<mx:Script>
<![CDATA[
import flash.events.Event;
private function handleCloseEvent(eventObj:Event):void
{
status.text = "You selected: \r" + countries.selectedItem as String;
}
]]>
</mx:Script>
<mx:Panel
title="Custom component inheritance"
paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10"
>
<custom:CountryComboBox
id="countries" rowCount="5"
close="handleCloseEvent(event);"
/>
<mx:Text id="status" text="Please select a country from the list." width="136"/>
</mx:Panel>
</mx:Application>
創建一個複合的自定義組件
複合自定義組件中包含多個組件定義。為了創建複合自定義組件,應該指定一個容器,作為根組件,然後添加Flex組件作為其子組件。
下邊例子中的AddressForm組件通過將Form容器作為根組件創建了一個地址表單。然後定義了幾個Form容器的子組件。這些子組件中包含其他自定義組件—CountryComboBox。
例子
components/AddressForm.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Form
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:custom="components.*"
>
<mx:FormItem label="Name">
<mx:TextInput/>
</mx:FormItem>
<mx:FormItem label="Street">
<mx:TextInput/>
</mx:FormItem>
<mx:FormItem label="City">
<mx:TextInput/>
</mx:FormItem>
<mx:FormItem label="State/County">
<mx:TextInput/>
</mx:FormItem>
<mx:FormItem label="Country">
<custom:CountryComboBox/>
</mx:FormItem>
</mx:Form>
components/CountryComboBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:dataProvider>
<mx:String>United States</mx:String>
<mx:String>United Kingdom</mx:String>
<!-- Add all other countries... -->
</mx:dataProvider>
</mx:ComboBox>
Main application MXML file
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:custom="components.*"
viewSourceURL="src/MxmlComponentComposite/index.html"
width="400" height="290"
>
<mx:Panel
title="Composite component"
paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10"
>
<custom:AddressForm />
</mx:Panel>
</mx:Application>
如果自定義組件檔案中的根標記包含子組件時,當你在其他MXML檔案中引用這個自定義組件標籤時,這個標籤不能包含子標籤。如果你在MXML檔案中定義了一個空容器,當你使用這個組件標籤時,這個標籤就可以包含子標籤。
例如,下邊的AddressForm的用法是不正確的,不能被編譯。
<!-- Incorrect -->
<custom:AddressForm>
<mx:FormItem label="Post code">
<mx:TextInput/>
</mx:FormItem>
</custom:AddressForm>
如果你必須擴展複合標籤的功能,可以創建一個以符合組件作為根標記的自定義組件。
注意:這個關於子組件的限制只是在子組件是可視組件的時候起作用。可是組件都是UIComponent組件的子類。你永遠都可以向不可視組件中插入標記,比如,ActionScript塊,style,effect,fomatters,validator,和其他類型的不可視組件。不管你如何定義的自定義組件。
創建可重用的MXML組件
創建自定義組件的一個公共目標是創建可配置的、可重用的組件。例如,你想要創建一個自定義組件,帶有屬性、分發事件、定義新的樣式屬性、有自定義皮膚,或者其他自定義功能。創建自定義組件的一個預期回報是可重用。也就是說,你希望自定義組件緊緊的關聯在你的程式中那?還是可以用在多個程式中?
為特定應用程式所編寫的緊耦合的組件,經常使它依賴應用程式的結構,變數名,或其他細節。如果你改變應用程式,你可能需要修改緊耦合的組件來反應這種變化。緊耦合的組件如果不重寫很難套用到其他應用程式中。
設計一個松耦合的組件來重用,它需要定義明確的接口來指定如何給組件傳遞信息,和組件如何向應用程式傳回結構。
典型的,通過定義松耦合組件的屬性,來向它傳送信息。屬性是通過定義固有的存取器(setter和getter方法)和指定參數的數據類型來實現的。在下邊的例子中,CountryComboBox自定義組件定義了useShortName公共屬性。這個屬性通過get useShortName和set useShortName存取器方法使_useShortName變數暴露。
_useShortNames私有屬性前的Inspectable元數據標籤,定義了一個屬性,這個屬性出現在Adobe Flex Builder中的屬性提示,和標籤內省中。也可以使用這個元數據標籤來限制對屬性值的訪問。
提示:所有公共屬性會暴露在MXML的代碼提示和屬性檢查器中。如果你有關於這個屬性的額外的信息,能夠幫助代碼提示或屬性檢查器(比如枚舉值,或隻字符串是一個路徑),那么也把這些而外信息添加到[Inspectable]元數據中。
MXML代碼提示和屬性檢查器的數據來源是一樣的。所以這兩個的顯示應該是一樣的。
另一方面,ActionScript的代碼提示是不需要元數據的,所以根據坐在的範圍,你一直可以看到適當的代碼提示。
Flex Builder 使用標識符(比如public private protected static)加上正確的上下文從而決定ActionScope的代碼提示。
定義返回信息到主程式的組件的最佳實踐是設計組件為傳送包含返回值的事件。那樣,主程式就能夠定義事件處理器,來處理時間並進行適當的動作。也可以在數據綁定中使用事件。下邊的例子中,使用Bindable元數據標籤,使userShortName作為一個綁定的屬性。useShortNames屬性的seter方法傳送改變事件,通過Flex Framework來實現數據綁定起作用。
例子
components/CountryComboBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
private var countryArrayShort:Array = ["US", "UK"];
private var countryArrayLong:Array = ["United States", "United Kingdom"];
// Determines display state. The inspectable metadata tag
// is used by Flex Builder 2
[Inspectable(defaultValue=true)]
private var _useShortNames:Boolean = true;
// Implicit setter
public function set useShortNames (state:Boolean):void
{
// Call method to set the dataProvider based on the name length.
_useShortNames = state;
if (state)
{
this.dataProvider = countryArrayShort;
}
else
{
this.dataProvider = countryArrayLong;
}
// Dispatch an event when the value changes
// (used in data binding.)
dispatchEvent(new Event("changeUseShortNames"));
}
// Allow other components to bind to this property
[Bindable(event="changeUseShortNames")]
public function get useShortNames():Boolean
{
return _useShortNames;
}
]]>
</mx:Script>
</mx:ComboBox>
應用程式MXML檔案
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:custom="components.*"
viewSourceURL="src/MxmlComponentAdvanced/index.html"
width="260" height="200"
>
<mx:Panel
title="Advanced custom components"
paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10"
>
<!-- Set a custom property on a custom component -->
<custom:CountryComboBox
id="countries"
useShortNames="false"
/>
<!--
Use data binding to display the latest state
of the property.
-->
<mx:Label text="useShortNames = {countries.useShortNames}"/>
<mx:ControlBar horizontalAlign="right">
<mx:Button
label="toggle Display"
click="countries.useShortNames = !countries.useShortNames;"
/>
</mx:ControlBar>
</mx:Panel>
</mx:Application>