tag:blogger.com,1999:blog-69349493776589660402008-08-29T00:19:55.094+08:00Ticore's BlogFlash、Flex、ActionScript 相關研究心得{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comBlogger72125tag:blogger.com,1999:blog-6934949377658966040.post-52782036296790387692008-06-27T00:53:00.008+08:002008-07-14T18:17:47.921+08:002008-07-14T18:17:47.921+08:00Flex 技巧 - 將資料綁定封裝起來<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 之前介紹了 <a href="http://ticore.blogspot.com/2008/06/flex-databinding-manually.html">純手工設定 Flex DataBinding 的方式</a><br/> 不過那挺麻煩的<br/> 假如想要將 DataBinding 封裝起來,保留部分彈性<br/> 又不想要那麼麻煩的設定方式<br/> 不妨可以試試看以下的方式 </p> <span class="fullpost"> <p> 在這個例子中,完全的將 DataBinding 封裝在一個 MXML Component 中<br/> 必須要指定好目標物,Component 內的 DataBinding 才會發生作用<br/> 想要停止 DataBinding 也很簡單,只要將目標屬性設為 null 就好 </p> <p> 甚至可以對一份資料,準備多個 DataBinding Component<br/> 只要在執行期動態替換 Component,就能達到切換 DataBinding 行為的目的<br/> 其實還挺方便的 </p> <p> Main.mxml: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" verticalAlign="middle" backgroundColor="#FFFFFF" creationComplete="init();" fontSize="12"&gt; &lt;mx:Script&gt; &lt;![CDATA[ public var comp:BindingComp; public function init():void{ comp = new BindingComp(); comp.initialize(); } ]]&gt; &lt;/mx:Script&gt; &lt;mx:HBox&gt; &lt;mx:Label text="No 1:" /&gt; &lt;mx:NumericStepper id="no1" maximum="100" /&gt; &lt;/mx:HBox&gt; &lt;mx:HBox&gt; &lt;mx:Label text="No 2:" /&gt; &lt;mx:NumericStepper id="no2" maximum="100" /&gt; &lt;/mx:HBox&gt; &lt;mx:CheckBox id="chk" label="DataBinding Enabled" change="comp.target = chk.selected ? this : null;" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> BindingComp.mxml: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:UIComponent xmlns:mx="http://www.adobe.com/2006/mxml"&gt; &lt;mx:Script&gt; &lt;![CDATA[ [Bindable] public var target:Main; public function doBinding1(... args:*):void{ if (target) target.no2.value = target.no1.value; } public function doBinding2(... args:*):void{ if (target) target.no1.value = target.no2.value; } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Model&gt; {doBinding1(target.no1.value)} &lt;/mx:Model&gt; &lt;mx:Model&gt; {doBinding2(target.no2.value)} &lt;/mx:Model&gt; &lt;/mx:UIComponent&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p>線上測示範例:</p> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="200"> <param name="movie" value="http://riafiles.googlepages.com/EncapsulateDataBinding.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/EncapsulateDataBinding.swf" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="100%" height="200" wmode="opaque"></embed> </object> </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/06/flex-databinding-manually.html">Flex - 純手工設定 DataBinding 的方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-bindingmanager-usage.html">Flex 技巧 - BindingManager 使用方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-monitor-data-binding.html">Flex 技巧 - 觀察 Data Binding 資料變化</a><br/> <a href="http://ticore.blogspot.com/2008/03/flex-tip-bracket-operator-in-data.html">Flex Tip - 在 Data Binding 內使用 [...] 運算子</a><br/> <a href="http://ticore.blogspot.com/2007/01/flex-2-bindable-metadata-tag.html">Flex 2 Bindable Metadata Tag 背後實際作用</a><br/> <a href="http://ticore.blogspot.com/2006/12/flex-20-actionscript-30-data-binding.html">Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-16105622739236079052008-06-18T20:54:00.007+08:002008-07-14T18:29:45.175+08:002008-07-14T18:29:45.175+08:00Flex SDK 馬歇爾計畫<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> <a target="_blank" href="http://opensource.adobe.com/wiki/display/flexsdk/Marshall+Plan">Marshall Plan</a> 原文有點長,主要只有兩件事情:<br/> </p> <p> <ul> <li> <p>不同版本交互支援 (Cross-Versioning):<br/> 不同版本 Flex 編譯的 SWFs 可以被放在相同的 SecurityDomain 執行<br/> 但是卻可以有不同的 ApplicationDomain</p> </li> <li> <p>不信任的應用程式支援 (Untrusted Application):<br/> 被載入到不同 SecurityDomain 的 SWFs 將不能存取主應用程式或是 Stage 與其它受限的資源</p> </li> </ul> </p> <p> </p> <span class="fullpost"> <p> 這個計畫將可能會在 Flex 3.1 開始支援<br/> 未來不同版本的 Flex 應用程式可以做混搭了<br/> 個人猜測,這計畫背後更重要的意義是 Flash 與 Flex 應用程式混搭 </p> <p> 不過在那之前,Adobe 可能要先把 <a href="http://ticore.blogspot.com/2008/05/flex-remoteobject-applicationdomain-bug.html">ApplicationDomain 的 Bug</a> 處理掉吧~ </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-69051563164968526322008-06-14T02:00:00.006+08:002008-07-14T18:17:47.922+08:002008-07-14T18:17:47.922+08:00AS 技巧 - Layer BlendMode 的用處<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flash Player 8 新增的混合模式功能中的圖層模式 (Layer)<br/> 一般使用起來或許覺得沒有什麼特別的用處<br/> 說明文件上寫得更是令人霧茫茫<br/> 簡單的說,只是讓 DisplayObject 子物件預先混和顏色而已 </p> <span class="fullpost"> <p> 基本上,除了 Normal 之外的混合模式,都會強迫預先混和子物件顏色<br/> 但是其它混合模式都是有特殊效果的<br/> 假如不需要那些效果,但是又要強迫預先混合顏色時<br/> 就要用 Layer 混合模式了 </p> <p> 對於像是 Flex 這樣由大量的 DisplayObject 組合而成的組件<br/> 遇到需要淡入、淡出效果時<br/> 即使是在最外層設定 Alpha 透明度<br/> 每個子物件仍會先被單獨套用 Alpha 效果再疊合成一張圖<br/> 這樣就會有某些位置顏色特別突兀不透明 </p> </p> 此時 Layer 混合模式就非常好用了<br/> 它可以讓整個表單先疊合成一張圖再進行 Alpha 效果<br/> 可以確保組件顏色看起來不會特別突兀 </p> <p> 以下是用 Flex 作的簡單測試<br/> 可以容易觀察到多層巢狀組件在不同 Alpha, BlendMode 的效果 </p> <pre name="code" class="mxml:showcolumns:contentHeight[400px]"> &lt;?xml version="1.0"?&gt; &lt;mx:Application layout="vertical" fontSize="12" backgroundColor="#FFFFFF" xmlns:mx="http://www.adobe.com/2006/mxml"&gt; &lt;mx:Style&gt; HBox.whiteBox { paddingTop: 15px; paddingBottom: 15px; paddingLeft: 15px; paddingRight: 15px; backgroundColor: #000000; } &lt;/mx:Style&gt; &lt;mx:HBox styleName="whiteBox" alpha="{alphaSlider.value}" blendMode="{blendModeCb.value ? blendModeCb.value : 'normal'}"&gt; &lt;mx:HBox styleName="whiteBox"&gt; &lt;mx:HBox styleName="whiteBox"&gt; &lt;mx:HBox styleName="whiteBox" /&gt; &lt;/mx:HBox&gt; &lt;/mx:HBox&gt; &lt;/mx:HBox&gt; &lt;mx:HRule width="100%" /&gt; &lt;mx:HBox verticalAlign="middle"&gt; &lt;mx:Label text="BlendMode: " /&gt; &lt;mx:ComboBox id="blendModeCb"&gt; &lt;mx:dataProvider&gt; ["normal", "layer", "darken", "invert", "hardlight"] &lt;/mx:dataProvider&gt; &lt;/mx:ComboBox&gt; &lt;/mx:HBox&gt; &lt;mx:HBox verticalAlign="middle"&gt; &lt;mx:Label text="Alpha: " /&gt; &lt;mx:HSlider id="alphaSlider" value="0.5" tickValues="[0, 0.5, 1]" labels="[0, 0.5, 1]" minimum="0" maximum="1" liveDragging="true" /&gt; &lt;/mx:HBox&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 線上測示範例: </p> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="250"> <param name="wmode" value="opaque" /> <param name="movie" value="http://riafiles.googlepages.com/LayerBlendModeDemo.swf" /> <embed src="http://riafiles.googlepages.com/LayerBlendModeDemo.swf" width="100%" height="250" wmode="opaque" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash"></embed> </object> </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2007/11/flex-label-alpha-tip.html">Flex Label, TextField 半透明小技巧</a><br/> <a href="http://ticore.blogspot.com/2006/12/flash-8.html">Flash 8 半透明輸入、動態文字欄位</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-5212795716109427052008-06-10T00:56:00.007+08:002008-07-14T18:17:47.924+08:002008-07-14T18:17:47.924+08:00Flex - 純手工設定 DataBinding 的方式<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex 的 DataBinding Expression 功能雖然非常方便<br/> 不用自行呼叫 addEventListener,省去了不少程式碼<br/> 但是換個角度看,Flex DataBinding 其實是有點缺乏彈性<br/> 不用自行加入監聽事件,同時也意味著不能移除監聽事件 </p> <p> 然而,自行利用 addEventListener 方式實作的 DataBinding<br/> 感覺好像又沒有 Flex Compiler 產生的好~<br/> 於是想要觀察 Flex Compiler 產生的程式碼<br/> 進而自行模仿實作 DataBinding </p> <span class="fullpost"> <p> 想要觀察 Flex Compiler 產生的 ActionScript 很簡單<br/> 只要加入編譯參數 <code>-keep-generated-actionscript=true</code><br/> 就可以在 /src/generated 下找到了 </p> <p> 舉例來說,想要模擬以下的 DataBinding Expression: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF" layout="vertical" fontSize="12"&gt; &lt;mx:Label text="Binding Source:" /&gt; &lt;mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" /&gt; &lt;mx:Label text="Binding Destination:" /&gt; &lt;mx:HSlider id="slider2" snapInterval="1" maximum="100" value="{slider1.value}" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 步驟有點多~~</p> <ol> <li> <p>實作 <code>mx.binding.IBindingClient;</code> 介面</p> </li> <li> <p> 匯入必要的 Class:<br/> <pre> import mx.binding.*; import mx.binding.utils.*; import mx.core.mx_internal; </pre> </p> </li> <li> <p> 宣告 Bindings, Watchers 等屬性:<br/> <pre> mx_internal var _bindings : Array = []; mx_internal var _watchers : Array = []; mx_internal var _bindingsByDestination : Object = {}; mx_internal var _bindingsBeginWithWord : Object = {}; </pre> </p> </li> <li> <p> 建立 Bindings 與 Watchers:<br/> <pre> mx_internal::_bindings[0] = new Binding(this, function():* {return slider1.value;}, function(sourceReturnValue:*):void {slider2.value = sourceReturnValue;}, "slider2.value"); mx_internal::_watchers[0] = new PropertyWatcher("slider1", {propertyChange: true}, [mx_internal::_bindings[0]], function(propertyName:String):* { return this[propertyName]; }); mx_internal::_watchers[1] = new PropertyWatcher("value", {valueCommit: true, change: true}, [mx_internal::_bindings[0]], null); mx_internal::_watchers[0].updateParent(this); mx_internal::_watchers[0].addChild(mx_internal::_watchers[1]); mx_internal::_bindings[0].execute(); </pre> </p> </li> </ol> <p> 最後一個步驟看起來就有點複雜了<br/> Binding 的功能有點類似 Event Handler,負責執行 DataBinding 運算<br/> 而 Watcher 則是類似 Event Listener,負責監聽資料來源的變化<br/> 為什麼不用標準的 Event Listener 機制<br/> 看 Flex Source 上寫的是因為效能考量 </p> <p> 先看一下 mx.binding.Binding 類別的使用方式<br/> <code>public function Binding(document:Object, srcFunc:Function, destFunc:Function, destString:String)</code><br/> <ul> <li>document: binding 目標的文件</li> <li>srcFunc: 用來取值的函式</li> <li>destFunc: 將值指定到目的地的函式</li> <li>destString: 用來告訴 ValidationManager 驗證該欄位</li> </ul> </p> <p> 至於 Watcher 其實只是一個上層類別<br/> 實際使用時,需要視 Binding Source 種類決定使用哪一種子 Watcher<br/> <code>XMLWatcher, PropertyWatcher, StaticPropertyWatcher, RepeaterItemWatcher, RepeaterComponentWatcher, FunctionReturnWatcher, ArrayElementWatcher</code> </p> <p> Watcher 有一個最特別的地方是,它具有父子關係<br/> 上層的父 Watcher 會觸發下層的子 Watcher 物件<br/> 可以藉由以下 Watcher 函式設定:<br/> <pre> public function updateParent(parent:Object):void; public function addChild(child:Watcher):void; public function removeChildren(startingIndex:int):void; public function updateChildren():void; </pre> </p> <p> 綜合上述的步驟<br/> 完整手工設定的 DataBinding MXML 程式碼如下: </p> <pre name="code" class="mxml:showcolumns:contentHeight[400px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF" layout="vertical" fontSize="12" implements="mx.binding.IBindingClient" creationComplete="onCreateComplete();"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.binding.*; import mx.binding.utils.*; import mx.core.mx_internal; mx_internal var _bindings : Array = []; mx_internal var _watchers : Array = []; mx_internal var _bindingsByDestination : Object = {}; mx_internal var _bindingsBeginWithWord : Object = {}; public function onCreateComplete():void{ mx_internal::_bindings[0] = new Binding(this, function():* {return slider1.value;}, function(sourceReturnValue:*):void {slider2.value = sourceReturnValue;}, "slider2.value"); // mx_internal::_bindings[0].mx_internal::isEnabled = false; mx_internal::_watchers[0] = new PropertyWatcher("slider1", {propertyChange: true}, [mx_internal::_bindings[0]], function(propertyName:String):* { return this[propertyName]; }); mx_internal::_watchers[1] = new PropertyWatcher("value", {valueCommit: true, change: true}, [mx_internal::_bindings[0]], null); mx_internal::_watchers[0].updateParent(this); mx_internal::_watchers[0].addChild(mx_internal::_watchers[1]); mx_internal::_bindings[0].execute(); // BindingManager.debugBinding("slider2.value"); } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Label text="Binding Source:" /&gt; &lt;mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" /&gt; &lt;mx:Label text="Binding Destination:" /&gt; &lt;mx:HSlider id="slider2" snapInterval="1" maximum="100" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-encapsulate-databinding.html">Flex 技巧 - 將資料綁定封裝起來</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-bindingmanager-usage.html">Flex 技巧 - BindingManager 使用方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-monitor-data-binding.html">Flex 技巧 - 觀察 Data Binding 資料變化</a><br/> <a href="http://ticore.blogspot.com/2008/03/flex-tip-bracket-operator-in-data.html">Flex Tip - 在 Data Binding 內使用 [...] 運算子</a><br/> <a href="http://ticore.blogspot.com/2007/01/flex-2-bindable-metadata-tag.html">Flex 2 Bindable Metadata Tag 背後實際作用</a><br/> <a href="http://ticore.blogspot.com/2006/12/flex-20-actionscript-30-data-binding.html">Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding</a><br/> <a target="_blank" href="http://blogs.soph-ware.com/?p=3">Soph-Ware Associates Blog - Data Binding in Flex, Part I</a><br/> <a target="_blank" href="http://blogs.soph-ware.com/?p=7">Soph-Ware Associates Blog - Data Binding in Flex, Part II</a><br/> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-7552548072068413072008-06-08T17:46:00.004+08:002008-07-14T18:17:47.925+08:002008-07-14T18:17:47.925+08:00Flex 技巧 - BindingManager 使用方式<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex 的 mx.binding.*; 內有許多未公開的類別<br/> 主要都是給 MXML Compiler 使用的<br/> 其中 mx.binding.BindingManager 便是負責管理所有的 DataBinding 運作<br/> BindingManager 內有一些靜態函式還蠻有用的<br/> 列舉如下: </p> <span class="fullpost"> <ul> <li> <p> <code>BindingManager.setEnabled(document:Object, isEnabled:Boolean):void;</code><br/> 用來 停止/啟動 Flex Application/Component 內 Data Binding </p> </li> <li> <p> <code>BindingManager.executeBindings(document:Object, destStr:String, destObj:Object):void;</code><br/> 執行被指定 Data Binding Expression </p> </li> <li> <p> <code>BindingManager.debugBinding(destinationString:String):void;</code><br/> 對指定的 Data Binding Expression 進行除錯 </p> </li> </ul> <p> 以下是簡單的 BindingManager 使用示範程式: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF" layout="vertical" fontSize="12" creationComplete="onCreateComplete();"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.binding.*; public function onCreateComplete():void{ BindingManager.debugBinding("slider2.value"); } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Label text="Binding Source:" /&gt; &lt;mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" /&gt; &lt;mx:Label text="Binding Destination:" /&gt; &lt;mx:HSlider id="slider2" snapInterval="1" maximum="100" value="{slider1.value}" /&gt; &lt;mx:CheckBox id="chk" label="Binding Enabled" selected="true" change="BindingManager.setEnabled(this, chk.selected); btn.enabled = chk.selected;" /&gt; &lt;mx:Button id="btn" label="Execute DataBinding" click="BindingManager.executeBindings(this, 'slider2.value', null);" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 測試輸出結果: </p> <pre> Binding: destString = slider2.value, srcFunc result = 54 Binding: destString = slider2.value, srcFunc result = 59 Binding: destString = slider2.value, srcFunc result = 81 Binding: destString = slider2.value, srcFunc result = 41 </pre> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-encapsulate-databinding.html">Flex 技巧 - 將資料綁定封裝起來</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-databinding-manually.html">Flex - 純手工設定 DataBinding 的方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-monitor-data-binding.html">Flex 技巧 - 觀察 Data Binding 資料變化</a><br/> <a href="http://ticore.blogspot.com/2008/03/flex-tip-bracket-operator-in-data.html">Flex Tip - 在 Data Binding 內使用 [...] 運算子</a><br/> <a href="http://ticore.blogspot.com/2007/01/flex-2-bindable-metadata-tag.html">Flex 2 Bindable Metadata Tag 背後實際作用</a><br/> <a href="http://ticore.blogspot.com/2006/12/flex-20-actionscript-30-data-binding.html">Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding</a><br/> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-62571920058670399542008-06-06T22:00:00.010+08:002008-07-14T18:17:47.927+08:002008-07-14T18:17:47.927+08:00Flex 技巧 - 觀察 Data Binding 資料變化<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex Data Binding 功能讓開發者不用寫太多的程式碼<br/> 就能做到資料繫結功能<br/> 但是要除錯的時候就比較不太方便<br/> Data Binding Expression 區塊內很多語法都不能使用<br/> 當 Data Binding 被觸發時,想要觀察資料變化就不太方便 </p> <span class="fullpost"> <p> 以下分享一個小技巧,可以很簡單的觀察 Data Binding 前後資料的變化 </p> <pre name="code" class="mxml:showcolumns"> &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#FFFFFF" layout="vertical" fontSize="12"&gt; &lt;mx:Label text="Binding Source:" /&gt; &lt;mx:HSlider id="slider1" snapInterval="1" maximum="100" /&gt; &lt;mx:Label text="Binding Destination:" /&gt; &lt;mx:HSlider id="slider2" snapInterval="1" maximum="100" value="{trace('Before :', slider2.value), setTimeout(function():void{trace('After :', slider2.value);}, 0), slider1.value}"/&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 由於 Data Binding Expression 內不能使用 ";" 結尾的陳述式<br/> 所以使用 "," 作區隔,只有最後一個 Expression 會被當作值指定給目標<br/> 這樣在前面就可以 trace 到變化之前的值<br/> 配合 setTimeout 又可以觀察到變化之後的值 </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-encapsulate-databinding.html">Flex 技巧 - 將資料綁定封裝起來</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-databinding-manually.html">Flex - 純手工設定 DataBinding 的方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-bindingmanager-usage.html">Flex 技巧 - BindingManager 使用方式</a><br/> <a href="http://ticore.blogspot.com/2008/03/flex-tip-bracket-operator-in-data.html">Flex Tip - 在 Data Binding 內使用 [...] 運算子</a><br/> <a href="http://ticore.blogspot.com/2007/01/flex-2-bindable-metadata-tag.html">Flex 2 Bindable Metadata Tag 背後實際作用</a><br/> <a href="http://ticore.blogspot.com/2006/12/flex-20-actionscript-30-data-binding.html">Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding</a><br/> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-41472390101503548722008-05-28T16:30:00.007+08:002008-07-14T18:14:18.831+08:002008-07-14T18:14:18.831+08:00Flex - RemoteObject 與 ApplicationDomain 問題<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 問題出處:<a target="_blank" href="http://mmug.com.tw/forum/viewtopic.php?p=48930">SWFLoader載入問題,當要載入的swf有使用DataServices...</a><br/> 在 Flex App 內,以新的 ApplicationDomain 載入另一個 Flex App 之後<br/> 被讀入的 Flex App 使用 RemoteObject 呼叫會出現問題 </p> <p> 重新把問題程式碼簡化如下<br/> 因為這問題發生於 HTTP 請求之前,不必配置後端 </p> <span class="fullpost"> <p> LoadeeApp.mxml: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"&gt; &lt;mx:Button label="Submit" click="remoteObj.test(123);"/&gt; &lt;mx:RemoteObject id="remoteObj" destination="xxx" /&gt; &lt;/mx:Application&gt; </pre> <p> LoaderApp.mxml: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"&gt; &lt;mx:SWFLoader id="swfLdr" width="100%" height="100%"&gt; &lt;mx:creationComplete&gt; &lt;![CDATA[ swfLdr.loaderContext = new LoaderContext(); swfLdr.loaderContext.applicationDomain = new ApplicationDomain(); swfLdr.source = "LoadeeApp.swf"; ]]&gt; &lt;/mx:creationComplete&gt; &lt;/mx:SWFLoader&gt; &lt;/mx:Application&gt; </pre> <p> 編譯之後,執行 LoaderApp,按下按鈕就會得到錯誤訊息 </p> <pre> ArgumentError: Error #1063: Object/http://adobe.com/AS3/2006/builtin::hasOwnProperty() 上的引數個數不相符。需要 0 個,目前為 2 個。 at LoadeeApp/___LoadeeApp_Button1_click() </pre> <p> 假如改成 remoteObj.test(); 還會得到堆疊溢位錯誤呢 </p> <pre> Error: Error #1023: 發生堆疊溢位。 at Object$/_hasOwnProperty() at Object/http://adobe.com/AS3/2006/builtin::hasOwnProperty() at Object$/_hasOwnProperty() at Object/http://adobe.com/AS3/2006/builtin::hasOwnProperty() ... </pre> <p> 從錯誤訊息看起來,其實與 Flex 無關<br/> 問題很可能是出在 Flash ActionScript 3.0 內建 Class 上<br/> 由於 RemoteObject 是繼承 Proxy<br/> 再將問題簡化,剔除 Flex 相關的因素<br/> 僅使用 Flash ActionScript 3.0 以 new ApplicationDomain(); 載入另一個 Flash App<br/> 於被載入的 Flash App 呼叫自訂的 MyProxy 方法 </p> <p> LoaderFlash Class: </p> <pre name="code" class="as3:showcolumns"> package { import flash.display.Loader; import flash.display.Sprite; import flash.net.URLRequest; import flash.system.ApplicationDomain; import flash.system.LoaderContext; public class LoaderFlash extends Sprite { protected var ldr:Loader; protected var ldrCxt:LoaderContext; protected var req:URLRequest; public function LoaderFlash() { ldr = new Loader(); ldrCxt = new LoaderContext(false, new ApplicationDomain()); req = new URLRequest("LoadeeFlash.swf"); ldr.load(req, ldrCxt); } } } </pre> <p> LoadeeFlash Class: </p> <pre name="code" class="as3:showcolumns"> package { import flash.display.Sprite; public class LoadeeFlash extends Sprite { public function LoadeeFlash() { var proxy:MyProxy = new MyProxy(); proxy.test(123); //proxy.prop++; } } } </pre> <p> MyProxy Class: </p> <pre name="code" class="as3:showcolumns"> package { import flash.utils.Proxy; import flash.utils.flash_proxy; public dynamic class MyProxy extends Proxy { public function MyProxy() { super(); } override flash_proxy function callProperty(methodName:*, ... args):* { trace("callProperty :", methodName, args); return; } override flash_proxy function getProperty(name:*):* { trace("getProperty :", name); return; } override flash_proxy function hasProperty(name:*):Boolean{ trace("hasProperty :", name); return true; } } } </pre> <p> 執行 LoaderFlash.swf,結果還是會得到一樣的錯誤訊息<br/> 所以 flash.utils.Proxy 無法在 new ApplicationDomain(); 方式載入的 SWF 內使用 </p> <p> 變通方式,大概只能避免使用到 Proxy 的功能了<br/> 改以 NetConnection 等比較低階的方式使用 Remoting 功能 </p> <p> 以上的 Bug 至少會發生在以下版本的 Flash Player<br/> Flash Player 9.0.115.0<br/> Flash Player 9.0.124.0<br/> Flash Player 10.0.1.218 </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-14232651061227780562008-04-21T19:38:00.001+08:002008-07-14T18:14:19.110+08:002008-07-14T18:14:19.110+08:00讓 Flex 內不可選擇的文字超連結生效<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 在 Flash 內,不可選擇的 (unselectable) TextField 仍可保留 HTML 超連結功能<br/> 但是 Flex 卻不行<br/> 查文件上也有寫到 <a href='http://livedocs.adobe.com/flex/3/langref/mx/controls/Label.html#selectable' target='_blank'>Label.selectable</a> </p> <p> 其實不光是 Label, Text 組件不行<br/> 任何一個在 Flex App 下的 unselectable TextField 超連結都會失效<br/> 這樣需要用到不可選擇的超連結文字時就很不方便 </p> <span class="fullpost"> <p>Flex 超連結失效測試程式:</p> <pre name="code" class="mxml:showcolumns:contentHeight[300px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12" backgroundColor="#F0F0F0"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import flash.events.*; import mx.managers.FocusManager; public function onTxtLink(evtObj:Event):void{ textArea.text += evtObj + "\n"; } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Label selectable="false" link="onTxtLink(event)"&gt; &lt;mx:htmlText&gt; &lt;![CDATA[Flex Label : &lt;a href='event:linkEvent'&gt;Link Event Text&lt;/a&gt; | ]]&gt; &lt;![CDATA[&lt;a href='http://ticore.blogspot.com' target='_blank'&gt;Ticore's Blog&lt;/a&gt;]]&gt; &lt;/mx:htmlText&gt; &lt;/mx:Label&gt; &lt;mx:Text selectable="false" link="onTxtLink(event)"&gt; &lt;mx:htmlText&gt; &lt;![CDATA[Flex Text : &lt;a href='event:linkEvent'&gt;Link Event Text&lt;/a&gt; | ]]&gt; &lt;![CDATA[&lt;a href='http://ticore.blogspot.com' target='_blank'&gt;Ticore's Blog&lt;/a&gt;]]&gt; &lt;/mx:htmlText&gt; &lt;/mx:Text&gt; &lt;mx:Button label="Clear Log" click="textArea.text = '';" /&gt; &lt;mx:TextArea id="textArea" width="100%" height="100%" /&gt; &lt;/mx:Application&gt; </pre> <p> 於是花了不少力氣去追蹤原因<br/> 終於發現是 Flex 內的 <a href='http://livedocs.adobe.com/flex/3/langref/mx/managers/FocusManager.html' target='_blank'>FocusManager</a> 刻意攔截下 unselectable TextField Focus 事件<br/> 這也間接造成超連結失效 </p> <p> 既然知道問題是出在 FocusManager 上<br/> 問題就比較好處理了<br/> 以下是變通方式,讓 FocusManager 短暫失效一下~ </p> <pre name="code" class="mxml:showcolumns:contentHeight[300px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12" backgroundColor="#F0F0F0"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import flash.events.*; import mx.managers.FocusManager; public function onTxtLink(evtObj:Event):void{ textArea.text += evtObj + "\n"; } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Label selectable="false" link="onTxtLink(event)" rollOver="focusManager.deactivate()" rollOut="focusManager.activate()"&gt; &lt;mx:htmlText&gt; &lt;![CDATA[Flex Label : &lt;a href='event:linkEvent'&gt;Link Event Text&lt;/a&gt; | ]]&gt; &lt;![CDATA[&lt;a href='http://ticore.blogspot.com' target='_blank'&gt;Ticore's Blog&lt;/a&gt;]]&gt; &lt;/mx:htmlText&gt; &lt;/mx:Label&gt; &lt;mx:Text selectable="false" link="onTxtLink(event)" rollOver="focusManager.deactivate()" rollOut="focusManager.activate()"&gt; &lt;mx:htmlText&gt; &lt;![CDATA[Flex Text : &lt;a href='event:linkEvent'&gt;Link Event Text&lt;/a&gt; | ]]&gt; &lt;![CDATA[&lt;a href='http://ticore.blogspot.com' target='_blank'&gt;Ticore's Blog&lt;/a&gt;]]&gt; &lt;/mx:htmlText&gt; &lt;/mx:Text&gt; &lt;mx:Button label="Clear Log" click="textArea.text = '';" /&gt; &lt;mx:TextArea id="textArea" width="100%" height="100%" /&gt; &lt;/mx:Application&gt; </pre> <p> Online Demo: </p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="320" codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> <param name="movie" value="http://riafiles.googlepages.com/EnableFlexHyperLink.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/EnableFlexHyperLink.swf" width="100%" height="320" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer" wmode="opaque"></embed> </object> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-72964008629652402012008-04-11T21:29:00.007+08:002008-07-14T18:17:47.938+08:002008-07-14T18:17:47.938+08:00Flex 3 - 實作滑鼠可圈選的 TileList V3<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Mouse Selectable TileList V3<br/> 這次改版更新到 Flex 3<br/> 改善滑鼠圈選時效能<br/> 最主要的是加入 Shift/Ctrl 功能鍵<br/> </p> <span class="fullpost"> <p> Flex 3 MouseSelectable TileList V3: </p> <pre name="code" class="as3:showcolumns:contentHeight[400px]"> /* MouseSelectableTileList V3 for Flex 3 Ticore Shih http://ticore.blogspot.com/ MouseSelectableTileList Structure    │    └─┬mouseSelectRect      │      └listContent        │        ├items        │        └selectionLayer */ package com.ticore.uicomponents { import flash.display.*; import flash.events.*; import flash.geom.Point; import flash.utils.*; import mx.collections.CursorBookmark; import mx.collections.ItemResponder; import mx.collections.ItemWrapper; import mx.collections.ModifiedCollectionView; import mx.collections.errors.ItemPendingError; import mx.controls.TileList; import mx.controls.listClasses.*; import mx.core.*; import mx.events.*; use namespace mx_internal; public class MouseSelectableTileList extends TileList { public function MouseSelectableTileList():void{ super(); if (VERSION != "3.0.0.0") { throw new Error("Super class version incompatible !"); } init(); } protected function init():void{ this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown); this.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent); this.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent); } protected function onKeyboardEvent(evtObj:KeyboardEvent):void{ this.ctrlKey = evtObj.ctrlKey; this.shiftKey = evtObj.shiftKey; } override protected function createChildren():void{ super.createChildren(); if (!mouseSelectRect) { mouseSelectRect = new FlexSprite(); mouseSelectRect.name = "mouseSelectRect"; this.addChild(mouseSelectRect); } } //================================================================= // Protected Properties //================================================================= [Bindable] public var itemMargin:Number = 10; //================================================================= // Protected Properties //================================================================= private var mouseDragScrollingInterval:int = 0; protected var mouseSelectRect:FlexSprite; protected var startPoint:Point; protected var endPoint:Point; protected var startIndexPoint:Point; protected var endIndexPoint:Point; protected var ctrlKey:Boolean = false; protected var shiftKey:Boolean = false; protected var oldSelectedIndices:Array; //================================================================= // Mouse Event Handlers //================================================================= protected function onMouseDown(evtObj:MouseEvent = null):void{ //trace("onMouseDown();"); var point:Point = new Point(evtObj.localX, evtObj.localY); point = (evtObj.target as DisplayObject).localToGlobal(point); if (!listContent.hitTestPoint(point.x, point.y)) { onMouseUpHandler(); return; } // check if mouse press on items if (mouseEventToItemRenderer(evtObj)) { onMouseUpHandler(); return; } var listPoint:Point = new Point(0, 0); listPoint = listContent.localToGlobal(listPoint); point = mouseSelectRect.globalToLocal(point); stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler); stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler); this.addEventListener(ScrollEvent.SCROLL, onScrollHandler); beginMouseSelect(point); //dragScroll(); } protected function onMouseMoveHandler(evtObj:MouseEvent = null):void{ //trace("onMouseMoveHandler();"); var point:Point = new Point(evtObj.localX, evtObj.localY); point = (evtObj.target as DisplayObject).localToGlobal(point); point = mouseSelectRect.globalToLocal(point); updateMouseSelect(point); //dragScroll(); mouseDragScroll(); } protected function onMouseUpHandler(evtObj:MouseEvent = null):void{ //trace("onMouseUpHandler();"); stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler); stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler); this.removeEventListener(ScrollEvent.SCROLL, onScrollHandler); resetMouseDragScrolling(); endMouseSelect(); } //================================================================= // Mouse Drag Scroll //================================================================= protected function onScrollHandler(evtObj:ScrollEvent):void{ //trace("onScrollHandler();"); updateMouseSelect(); } protected function resetMouseDragScrolling():void{ if (mouseDragScrollingInterval != 0) { clearInterval(mouseDragScrollingInterval); mouseDragScrollingInterval = 0; } } protected function mouseDragScroll():void{ //trace("mouseDragScroll();"); // x 方向快速捲動,會造成 item 顯示不正常 var slop:Number = 0; var scrollInterval:Number; var oldPosition:Number; var d:Number; var scrollEvent:ScrollEvent; // if (mouseDragScrollingInterval == 0) return; const minScrollInterval:Number = 30; clearInterval(mouseDragScrollingInterval); if (mouseY &lt; slop) { oldPosition = verticalScrollPosition; verticalScrollPosition = Math.max(0, oldPosition - 1); d = Math.min(0 - mouseY - 30, 0); scrollInterval = 0.593 * d * d + 1 + minScrollInterval; mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval); if (oldPosition != verticalScrollPosition) { scrollEvent = new ScrollEvent(ScrollEvent.SCROLL); scrollEvent.detail = ScrollEventDetail.THUMB_POSITION; scrollEvent.direction = ScrollEventDirection.VERTICAL; scrollEvent.position = verticalScrollPosition; scrollEvent.delta = verticalScrollPosition - oldPosition; dispatchEvent(scrollEvent); } } else if (mouseY &gt; (unscaledHeight - slop)) { oldPosition = verticalScrollPosition; verticalScrollPosition = Math.min(maxVerticalScrollPosition, verticalScrollPosition + 1); d = Math.min(mouseY - unscaledHeight - 30, 0); scrollInterval = 0.593 * d * d + 1 + minScrollInterval; mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval); if (oldPosition != verticalScrollPosition) { scrollEvent = new ScrollEvent(ScrollEvent.SCROLL); scrollEvent.detail = ScrollEventDetail.THUMB_POSITION; scrollEvent.direction = ScrollEventDirection.VERTICAL; scrollEvent.position = verticalScrollPosition; scrollEvent.delta = verticalScrollPosition - oldPosition; dispatchEvent(scrollEvent); } } else if (mouseX &lt; slop) { oldPosition = horizontalScrollPosition; horizontalScrollPosition = Math.max(0, oldPosition - 1); d = Math.min(0 - mouseX - 30, 0); scrollInterval = 0.593 * d * d + 1 + minScrollInterval; mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval); if (oldPosition != horizontalScrollPosition) { scrollEvent = new ScrollEvent(ScrollEvent.SCROLL); scrollEvent.detail = ScrollEventDetail.THUMB_POSITION; scrollEvent.direction = ScrollEventDirection.HORIZONTAL; scrollEvent.position = horizontalScrollPosition; scrollEvent.delta = horizontalScrollPosition - oldPosition; dispatchEvent(scrollEvent); } } else if (mouseX &gt; (unscaledWidth - slop)) { oldPosition = horizontalScrollPosition; horizontalScrollPosition = Math.min(maxHorizontalScrollPosition, horizontalScrollPosition + 1); d = Math.min(mouseX - unscaledWidth - 30, 0); scrollInterval = 0.593 * d * d + 1 + minScrollInterval; mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval); if (oldPosition != horizontalScrollPosition) { scrollEvent = new ScrollEvent(ScrollEvent.SCROLL); scrollEvent.detail = ScrollEventDetail.THUMB_POSITION; scrollEvent.direction = ScrollEventDirection.HORIZONTAL; scrollEvent.position = horizontalScrollPosition; scrollEvent.delta = horizontalScrollPosition - oldPosition; dispatchEvent(scrollEvent); } } else { mouseDragScrollingInterval = setInterval(mouseDragScroll, 15); } } /*// // override ListBase.dragScroll() protected override function dragScroll():void { trace("dragScroll();"); trace("DragManager.isDragging : " + DragManager.isDragging); super.dragScroll(); //trace("collection : " + collection); //trace((mouseY &gt; unscaledHeight)); var slop:Number = 0; } //*/ /*/ protected override function mouseMoveHandler(event:MouseEvent):void{ trace("mouseMoveHandler();"); } //*/ //================================================================= // //================================================================= protected function beginMouseSelect(point:Point):void{ //trace("beginMouseSelect();", point); //adjustPoint(point); if (ctrlKey || shiftKey) { this.oldSelectedIndices = []; } else { this.selectedIndices = []; this.oldSelectedIndices = []; } endIndexPoint = startIndexPoint = pointToIndex(point); point.x += this.horizontalScrollPosition * this.columnWidth; point.y += this.verticalScrollPosition * this.rowHeight; endPoint = startPoint = point; drawMouseSelectRect(); } protected function updateMouseSelect(point:Point = null):void{ //trace("updateMouseSelect();"); //adjustPoint(point); if (!point) { point = new Point(mouseSelectRect.mouseX, mouseSelectRect.mouseY); } endIndexPoint = pointToIndex(point); point.x += this.horizontalScrollPosition * this.columnWidth; point.y += this.verticalScrollPosition * this.rowHeight; endPoint = point; drawMouseSelectRect(); //trace(startIndexPoint, endIndexPoint); var newSelectedIndices:Array = []; var i:Number, ix:Number, iy:Number; if (this.direction == TileBaseDirection.HORIZONTAL) { for (i = 0 ; i &lt; dataProvider.length ; ++i) { ix = i % this.columnCount; iy = Math.floor(i / this.columnCount); xFlag = false; if (startIndexPoint.x &lt;= endIndexPoint.x) { xFlag = ix &gt;= startIndexPoint.x &amp;&amp; ix &lt;= endIndexPoint.x; } else { xFlag = ix &lt;= startIndexPoint.x &amp;&amp; ix &gt;= endIndexPoint.x; } yFlag = false; if (startIndexPoint.y &lt;= endIndexPoint.y) { yFlag = iy &gt;= startIndexPoint.y &amp;&amp; iy &lt;= endIndexPoint.y; } else { yFlag = iy &lt;= startIndexPoint.y &amp;&amp; iy &gt;= endIndexPoint.y; } if (xFlag &amp;&amp; yFlag) { newSelectedIndices.push(i); } } } else { for (i = 0 ; i &lt; dataProvider.length ; ++i) { ix = Math.floor(i / this.rowCount); iy = i % this.rowCount; xFlag = false; if (startIndexPoint.x &lt;= endIndexPoint.x) { xFlag = ix &gt;= startIndexPoint.x &amp;&amp; ix &lt;= endIndexPoint.x; } else { xFlag = ix &lt;= startIndexPoint.x &amp;&amp; ix &gt;= endIndexPoint.x; } yFlag = false; if (startIndexPoint.y &lt;= endIndexPoint.y) { yFlag = iy &gt;= startIndexPoint.y &amp;&amp; iy &lt;= endIndexPoint.y; } else { yFlag = iy &lt;= startIndexPoint.y &amp;&amp; iy &gt;= endIndexPoint.y; } if (xFlag &amp;&amp; yFlag) { newSelectedIndices.push(i); } } } var xFlag:Boolean = false; var yFlag:Boolean = false; var deltaDict:Dictionary = new Dictionary(); var o:*; var selectedIndices:Array; //var oldSelectedIndices:Array = this.selectedIndices; // Update selectedIndices if (this.ctrlKey) { var index:Number; for (i = 0 ; i &lt; oldSelectedIndices.length ; ++i) { index = oldSelectedIndices[i]; deltaDict[index] = null; } oldSelectedIndices = newSelectedIndices; for (i = 0 ; i &lt; newSelectedIndices.length ; ++i) { index = newSelectedIndices[i]; if (index in deltaDict) { delete deltaDict[index]; } else { deltaDict[index] = null; } } selectedIndices = this.selectedIndices; for (i = 0 ; i &lt; selectedIndices.length ; ++i) { index = selectedIndices[i]; if (index in deltaDict) { delete deltaDict[index]; } else { deltaDict[index] = null; } } newSelectedIndices = []; for (o in deltaDict) { newSelectedIndices.push(o); } } else if (this.shiftKey) { var deltaAddDict:Dictionary = new Dictionary(); var deltaRemoveDict:Dictionary = new Dictionary(); for (i = 0 ; i &lt; oldSelectedIndices.length ; ++i) { deltaRemoveDict[oldSelectedIndices[i]] = null; } for (i = 0 ; i &lt; newSelectedIndices.length ; ++i) { index = newSelectedIndices[i]; deltaAddDict[index] = null; if (index in deltaRemoveDict) { delete deltaRemoveDict[index]; } } oldSelectedIndices = newSelectedIndices; selectedIndices = this.selectedIndices; for (i = 0 ; i &lt; selectedIndices.length ; ++i) { deltaDict[selectedIndices[i]] = null; } for (o in deltaAddDict) { deltaDict[o] = null; } for (o in deltaRemoveDict) { delete deltaDict[o]; } newSelectedIndices = []; for (o in deltaDict) { newSelectedIndices.push(o); } } else { selectedIndices = this.selectedIndices; this.oldSelectedIndices = newSelectedIndices.concat(); } // Check if selectedIndicesupdated outer: if (selectedIndices.length == newSelectedIndices.length) { selectedIndices.sort(Array.NUMERIC); newSelectedIndices.sort(Array.NUMERIC); for (i = 0 ; i &lt; selectedIndices.length ; ++i) { if (newSelectedIndices[i] != selectedIndices[i]) { break outer; } } return; } newSelectedIndices.reverse(); this.selectedIndices = newSelectedIndices; } protected function endMouseSelect():void{ //trace("endMouseSelect();"); var g:Graphics = mouseSelectRect.graphics; g.clear(); var evt:ListEvent = new ListEvent(ListEvent.CHANGE); dispatchEvent(evt); } //================================================================= // //================================================================= protected function drawMouseSelectRect():void{ var drawStartPoint:Point = new Point(); var drawEndPoint:Point = new Point(); drawStartPoint.x = this.startPoint.x - this.horizontalScrollPosition * this.columnWidth; drawStartPoint.y = this.startPoint.y - this.verticalScrollPosition * this.rowHeight; drawEndPoint.x = this.endPoint.x - this.horizontalScrollPosition * this.columnWidth; drawEndPoint.y = this.endPoint.y - this.verticalScrollPosition * this.rowHeight; var adjustDrawStartPoint:Point = adjustPoint(drawStartPoint); var adjustDrawEndPoint:Point = adjustPoint(drawEndPoint); var g:Graphics = mouseSelectRect.graphics; g.clear(); g.lineStyle(1, 0x0, 0.5, true); g.moveTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y); if (drawStartPoint.y &gt;= 0 &amp;&amp; drawStartPoint.y &lt;= listContent.height) { g.lineTo(adjustDrawEndPoint.x, adjustDrawStartPoint.y); } g.moveTo(adjustDrawEndPoint.x, adjustDrawStartPoint.y); g.lineTo(adjustDrawEndPoint.x, adjustDrawEndPoint.y); g.moveTo(adjustDrawEndPoint.x, adjustDrawEndPoint.y); g.lineTo(adjustDrawStartPoint.x, adjustDrawEndPoint.y); g.moveTo(adjustDrawStartPoint.x, adjustDrawEndPoint.y); if (drawStartPoint.x &gt;= 0 &amp;&amp; drawStartPoint.x &lt;= listContent.width) { g.lineTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y); } g.moveTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y); //*/ g.lineStyle(0, 0x0, 0, true); g.beginFill(0x0000FF, 0.1); g.drawRect(adjustDrawStartPoint.x, adjustDrawStartPoint.y, adjustDrawEndPoint.x - adjustDrawStartPoint.x, adjustDrawEndPoint.y - adjustDrawStartPoint.y); g.endFill(); //*/ } //================================================================= // //================================================================= protected function adjustPoint(p:Point):Point{ var newPoint:Point = new Point(); newPoint.x = p.x &lt; 0 ? 0 : p.x; newPoint.y = p.y &lt; 0 ? 0 : p.y; newPoint.x = newPoint.x &gt; listContent.width - 1 ? listContent.width - 1 : newPoint.x; newPoint.y = newPoint.y &gt; listContent.height - 1 ? listContent.height - 1 : newPoint.y; return newPoint; } protected function pointToIndex(p:Point):Point{ var indexPoint:Point = new Point(); indexPoint.x = Math.floor(p.x / this.columnWidth) + this.horizontalScrollPosition - 0.5; indexPoint.y = Math.floor(p.y / this.rowHeight) + this.verticalScrollPosition - 0.5; //*/ indexPoint.x += (p.x % this.columnWidth) &gt; itemMargin ? 0.5 : 0; indexPoint.y += (p.y % this.rowHeight) &gt; itemMargin ? 0.5 : 0; indexPoint.x += (p.x % this.columnWidth) &gt; (this.columnWidth - itemMargin) ? 0.5 : 0; indexPoint.y += (p.y % this.rowHeight) &gt; (this.rowHeight - itemMargin) ? 0.5 : 0; //*/ return indexPoint; } //================================================================= // Override TileBase Functions //================================================================= override protected function updateDisplayList (unscaledWidth:Number, unscaledHeight:Number):void{ super.updateDisplayList(unscaledWidth, unscaledHeight); //this.addChildAt(mouseSelectRect, this.numChildren); //this.addChild(mouseSelectRect); mouseSelectRect.x = listContent.x; mouseSelectRect.y = listContent.y; //this.listContent.visible = false; } override protected function makeRowsAndColumns(left:Number, top:Number, right:Number, bottom:Number, firstCol:int, firstRow:int, byCount:Boolean = false, rowsNeeded:uint = 0):Point { //trace(this, "makeRowsAndColumns " + left + " " + top + " " + right + " " + bottom + " " + firstCol + " " + firstRow); var numRows:int; var numCols:int; var colNum:int; var rowNum:int; var xx:Number; var yy:Number; var wrappedData:Object; var data:Object; var uid:String var oldItem:IListItemRenderer var item:IListItemRenderer; var more:Boolean; var valid:Boolean; var i:int; var rh:Number; var lastRowMade:int; var lastColumnMade:int; var bSelected:Boolean = false; var bHighlight:Boolean = false; var bCaret:Boolean = false; // trace("TileBase.makeRowsAndColumns, horizontalScrollPosition = " + horizontalScrollPosition + // ", iterator index = " + iterator.bookmark.getViewIndex() + ", iterator current = " + // iterator.current); if (columnWidth == 0 || rowHeight == 0) return null; invalidateSizeFlag = true; allowItemSizeChangeNotification = false; if (direction == TileBaseDirection.VERTICAL) { numRows = maxRows &gt; 0 ? maxRows : Math.max(Math.floor(listContent.heightExcludingOffsets / rowHeight), 1); numCols = Math.max(Math.ceil((listContent.widthExcludingOffsets)/ columnWidth), 1); setRowCount(numRows); setColumnCount(numCols); colNum = firstCol; xx = left; lastColumnMade = colNum - 1; more = (iterator != null &amp;&amp; !iterator.afterLast &amp;&amp; iteratorValid); while ((byCount &amp;&amp; rowsNeeded--) || (!byCount &amp;&amp; (colNum &lt; numCols + firstCol))) { rowNum = firstRow; yy = top; while (rowNum &lt; numRows) { valid = more; wrappedData = more ? iterator.current : null; data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData; more = moveNextSafely(more); if (!listItems[rowNum]) listItems[rowNum] = []; if (valid &amp;&amp; yy &lt; bottom) { uid = itemToUID(wrappedData); rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid); item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid); placeAndDrawItemRenderer(item,xx,yy,uid); lastColumnMade = Math.max(colNum,lastColumnMade); } else { oldItem = listItems[rowNum][colNum]; if (oldItem) { addToFreeItemRenderers(oldItem); listContent.removeChild(DisplayObject(oldItem)); // delete rowMap[oldItem.name]; listItems[rowNum][colNum] = null; } rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid); } yy += rowHeight; rowNum++; } colNum ++; if (firstRow) { // we're doing a row along the bottom so we have to skip the beginning of the next column for (i = 0; i &lt; firstRow; i++) more = moveNextSafely(more); } xx += columnWidth; } } else { // horizontal numCols = maxColumns &gt; 0 ? maxColumns : Math.max(Math.floor((listContent.widthExcludingOffsets)/ columnWidth), 1); numRows = Math.max(Math.ceil(listContent.heightExcludingOffsets / rowHeight), 1); setColumnCount(numCols); setRowCount(numRows); rowNum = firstRow; yy = top; more = (iterator != null &amp;&amp; !iterator.afterLast &amp;&amp; iteratorValid); lastRowMade = rowNum-1; while ((byCount &amp;&amp; rowsNeeded--) || (!byCount &amp;&amp; rowNum &lt; numRows + firstRow)) { colNum = firstCol; xx = left; rowInfo[rowNum] = null; while (colNum &lt; numCols) { valid = more; wrappedData = more ? iterator.current : null; data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData; more = moveNextSafely(more); if (!listItems[rowNum]) listItems[rowNum] = []; if (valid &amp;&amp; xx &lt; right) { uid = itemToUID(wrappedData); if (!rowInfo[rowNum]) rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid); item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid); placeAndDrawItemRenderer(item,xx,yy,uid); lastRowMade = rowNum; } else { if (!rowInfo[rowNum]) rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid); oldItem = listItems[rowNum][colNum]; if (oldItem) { addToFreeItemRenderers(oldItem); listContent.removeChild(DisplayObject(oldItem)); listItems[rowNum][colNum] = null; } } xx += columnWidth; colNum++; } rowNum ++; if (firstCol) { // we're doing a column along the side so we have to skip the beginning of the next column for (i = 0; i &lt; firstCol; i++) more = moveNextSafely(more); } yy += rowHeight; } } if (!byCount) { var a:Array; // prune excess rows and columns while (listItems.length &gt; numRows + offscreenExtraRowsTop) { a = listItems.pop(); rowInfo.pop(); for (i = 0; i &lt; a.length; i++) { oldItem = a[i]; if (oldItem) { if (oldItem.parent) listContent.removeChild(DisplayObject(oldItem)); addToFreeItemRenderers(oldItem); } } } if (listItems.length &amp;&amp; listItems[0].length &gt; numCols + offscreenExtraColumnsLeft) { for (i = 0; i &lt; numRows + offscreenExtraRowsTop; i++) { a = listItems[i]; while (a.length &gt; numCols + offscreenExtraColumnsLeft) { oldItem = a.pop(); if (oldItem) { if (oldItem.parent) listContent.removeChild(DisplayObject(oldItem)); addToFreeItemRenderers(oldItem); } } } } } allowItemSizeChangeNotification = true; invalidateSizeFlag = false; return new Point(lastColumnMade - firstCol + 1,lastRowMade - firstRow + 1); } private function moveNextSafely(more:Boolean):Boolean { if (iterator &amp;&amp; more) { try { more = iterator.moveNext(); } catch(e1:ItemPendingError) { lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0); e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, lastSeekPending)); more = false; iteratorValid = false; } } return more; } private function getPreparedItemRenderer(rowNum:int,colNum:int, wrappedData:Object, data:Object, uid:String):IListItemRenderer { var oldItem:IListItemRenderer = listItems[rowNum][colNum]; var item:IListItemRenderer; var rowData:ListData; if (oldItem) { // If we're running a data effect, do a more expensive check when // determining if we can reuse this item renderer if (runningDataEffect ? (dataItemWrappersByRenderer[oldItem] != wrappedData) : (oldItem.data != data)) addToFreeItemRenderers(oldItem); else item = oldItem; } if (!item) { // if we're allowed to re-use existing renderers if (allowRendererStealingDuringLayout) { // try to steal item renderer if it already exists, // but don't steal item renderers that have already // been used in the layout. (This will happen if there // are duplicate UIDs in the collection, which shouldn't // really happen, but nevertheless may happen). item = visibleData[uid]; // if we can't steal an item based on it's UID, // steal based on the UID of the underlying data if (!item &amp;&amp; (wrappedData != data)) item = visibleData[itemToUID(data)]; } // if we've stolen a renderer from somewhere else... if (item) { // update data structures so we're not pointing to it twice var ld:ListData = ListData(rowMap[item.name]); if (ld) { if (((direction == TileBaseDirection.HORIZONTAL) &amp;&amp; ((ld.rowIndex &gt; rowNum) || ((ld.rowIndex == rowNum) &amp;&amp; (ld.columnIndex &gt; colNum)))) || ((direction == TileBaseDirection.VERTICAL) &amp;&amp; ((ld.columnIndex &gt; colNum) || ((ld.columnIndex == colNum) &amp;&amp; (ld.rowIndex &gt; rowNum))))) listItems[ld.rowIndex][ld.columnIndex] = null; else item = null; } } if (!item) { item = getReservedOrFreeItemRenderer(wrappedData); if (item &amp;&amp; !isRendererUnconstrained(item)) { item.x = 0; item.y = 0; } } // if all else fails... if (!item) item = createItemRenderer(data); item.owner = this; item.styleName = listContent; item.visible = true; } rowData = ListData(makeListData(data, uid, rowNum, colNum)); rowMap[item.name] = rowData; if (item is IDropInListItemRenderer) IDropInListItemRenderer(item).listData = data ? rowData : null; item.data = data; if (wrappedData != data) dataItemWrappersByRenderer[item] = wrappedData; if (!item.parent) listContent.addChild(DisplayObject(item)); item.visible = true; if (uid) visibleData[uid] = item; listItems[rowNum][colNum] = item; UIComponentGlobals.layoutManager.validateClient(item, true); return item; } private function placeAndDrawItemRenderer(item:IListItemRenderer, xx:Number, yy:Number, uid:String):void { var bSelected:Boolean = false; var bHighlight:Boolean = false; var bCaret:Boolean = false; var rh:Number; rh = item.getExplicitOrMeasuredHeight(); if (item.width != columnWidth - itemMargin * 2 || rh != (rowHeight - cachedPaddingTop - cachedPaddingBottom - itemMargin * 2)) item.setActualSize(columnWidth - itemMargin * 2, rowHeight - cachedPaddingTop - cachedPaddingBottom - itemMargin * 2); // this is not really doing anything yet if (!isRendererUnconstrained(item)) item.move(xx + itemMargin, yy + cachedPaddingTop + itemMargin); bSelected = selectedData[uid] != null; if (runningDataEffect) { bSelected = bSelected || (selectedData[itemToUID(item.data)] != null); bSelected = bSelected &amp;&amp; (!getRendererSemanticValue(item,ModifiedCollectionView.REPLACEMENT)) &amp;&amp; (!getRendererSemanticValue(item,ModifiedCollectionView.ADDED)); } bHighlight = highlightUID == uid; bCaret = caretUID == uid; if (uid) drawItem(item, bSelected, bHighlight, bCaret); } //================================================================= // Override ListBase Functions //================================================================= //*/ override mx_internal function mouseEventToItemRendererOrEditor( event:MouseEvent):IListItemRenderer { var target:DisplayObject = DisplayObject(event.target); if (target == listContent) { var pt:Point = new Point(event.stageX, event.stageY); pt = listContent.globalToLocal(pt); if (pt.y % rowHeight &lt; itemMargin || pt.y % rowHeight &gt; columnWidth - itemMargin) { return null; } if (pt.x % columnWidth &lt; itemMargin || pt.x % columnWidth &gt; columnWidth - itemMargin) { return null; } var yy:Number = 0; var n:int = listItems.length; for (var i:int = 0; i &lt; n; i++) { if (listItems[i].length) { if (pt.y &lt; yy + rowInfo[i].height) { var m:int = listItems[i].length; // if (m == 1) return listItems[i][0]; var j:int = Math.floor(pt.x / columnWidth); return listItems[i][j]; } } yy += rowInfo[i].height; } } else if (target == highlightIndicator) { return lastHighlightItemRenderer; } while (target &amp;&amp; target != this) { if (target is IListItemRenderer &amp;&amp; target.parent == listContent) { if (target.visible) return IListItemRenderer(target); break; } if (target is IUIComponent) target = IUIComponent(target).owner; else target = target.parent; } return null; } //*/ //================================================================= // Overide ListBase draw Functions //================================================================= override protected function drawSelectionIndicator( indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void { /*/ super.drawSelectionIndicator( indicator, x, y + itemMargin, width, height - itemMargin * 2, color, itemRenderer); //*/ var g:Graphics = Sprite(indicator).graphics; g.clear(); g.beginFill(color, 0.5); g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20); g.endFill(); indicator.x = x; indicator.y = y + itemMargin; } override protected function drawHighlightIndicator( indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void { /*/ super.drawSelectionIndicator( indicator, x, y + itemMargin, width, height - itemMargin * 2, color, itemRenderer); //*/ var g:Graphics = Sprite(indicator).graphics; g.clear(); g.beginFill(color, 0.5); g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20); g.endFill(); indicator.x = x; indicator.y = y + itemMargin; } override protected function drawCaretIndicator( indicator:Sprite, x:Number, y:Number, width:Number, height:Number, color:uint, itemRenderer:IListItemRenderer):void { /*/ super.drawSelectionIndicator( indicator, x, y + itemMargin, width, height - itemMargin * 2, color, itemRenderer); //*/ var g:Graphics = Sprite(indicator).graphics; g.clear(); g.lineStyle(2, color, 1, true); g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20); indicator.x = x; indicator.y = y + itemMargin; } } } </pre> <p> Online Demo: </p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="400" codebase="http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"> <param name="movie" value="http://riafiles.googlepages.com/MouseSelectableTileList_0.0.9.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/MouseSelectableTileList_0.0.9.swf" width="100%" height="400" allowScriptAccess="always" type="application/x-shockwave-flash" pluginspage="http://www.adobe.com/go/getflashplayer" wmode="opaque"></embed> </object> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/02/flex-2-mouse-selectable-tilelist.html">Flex 2 - 實作滑鼠可圈選的 TileList</a><br/> <a href="http://ticore.blogspot.com/2008/02/flex-2-mouse-selectable-tilelist-v2.html">Flex 2 - 實作滑鼠可圈選的 TileList V2</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-9195377484996302912008-04-06T20:36:00.004+08:002008-07-14T18:14:19.188+08:002008-07-14T18:14:19.188+08:00Flex - Daily Task View by Constraint Layout<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 利用 Flex 3 新功能 Constraint Layout 實作 Daily Task View<br/> 之前曾經考慮過使用 DataGrid, TileList 等組件<br/> 但是都不是很適合<br/> 主要是因為 Daily Task View Column 數量不固定並且需要跨列 </p> <p> 後來發現 Flex 3 Constraint Layout 頗適合<br/> 因為 Constraint Column, Row 都不是真的建立 Column 或是 Row<br/> 只是建立類似參考位置的資料<br/> 所以不會消耗太多效能<br/> 而且 Constraint Layout 可以隨意的跨行、跨列 </p> <span class="fullpost"> <p> 以下是簡單實作 Daily Task View 例子: </p> <pre name="code" class="mxml:showcolumns:contentHeight[400px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="onInit();" backgroundColor="#E0E0E0"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.controls.HRule; import mx.containers.utilityClasses.ConstraintColumn; import mx.containers.utilityClasses.ConstraintRow; import mx.controls.*; public var taskData:Array = []; public var taskColumnData:Array = []; public var taskItems:Array = []; public function onInit():void{ createConsRows(); refresh(); } public function refresh():void{ createTaskData(); arrangeTaskColumn(); createConsCols(); createTaskItems(); } public function createTaskData():void{ taskData = []; var num:Number = Math.random() * 30 + 3; for (var i:Number = 0 ; i &lt; num ; ++i) { var task:Object = {}; task.title = "Task " + i; task.startDate = new Date(); task.endDate = new Date(); task.startDate.setHours(Math.random() * 24); task.endDate.setHours(task.startDate.hours + Math.random() * 24); task.startDate.setMinutes(Math.random() * 60); task.endDate.setMinutes(Math.random() * 60); taskData.push(task); } taskData.sortOn("startDate"); } public function arrangeTaskColumn():void{ taskColumnData = []; taskColumnData.push([]); loop1:for (var i:Number = 0 ; i &lt; taskData.length ; ++i) { var task:Object = taskData[i]; // 排除跨天 Task if (task.startDate.hours &gt; task.endDate.hours) { continue; } loop2:for (var j:Number = 0 ; j &lt; taskColumnData.length ; ++j) { var taskColumn:Array = taskColumnData[j]; loop3:for (var k:Number = 0 ; k &lt; taskColumn.length ; ++k) { var taskAdded:Object = taskColumn[k]; if (task.startDate.time &gt; taskAdded.endDate.time || task.endDate.time &lt; taskAdded.startDate.time) { } else { continue loop2; } } taskColumn.push(task); continue loop1; } // 既有 column 時間都重疊 -&gt; 建立新 column taskColumnData.push([task]); } } public function createConsCols():void{ canvas.constraintColumns = []; for (var i:Number = 0 ; i &lt; taskColumnData.length ; ++i) { var consCol:ConstraintColumn = new ConstraintColumn(); consCol.initialized(canvas, "col" + i); // consCol.width = 100; consCol.percentWidth = 100 / taskColumnData.length; canvas.constraintColumns.push(consCol); } } public function createTaskItems():void{ for each(var s:* in taskItems) { canvas.removeChild(s); } taskItems= []; for (var i:Number = 0 ; i &lt; taskColumnData.length ; ++i) { var taskColumn:Array = taskColumnData[i]; for (var j:Number = 0 ; j &lt; taskColumn.length ; ++j) { var task:Object = taskColumn[j]; var btn:Button = new Button(); taskItems.push(btn); btn.minWidth = 10; btn.minHeight = 20; btn.label = task.title + " (" + task.startDate.hours + ":" + task.startDate.minutes + "-" + task.endDate.hours + ":" + task.endDate.minutes + ")"; btn.setStyle("top", "row" + task.startDate.hours + ":" + int(task.startDate.minutes / 60 * 50)); btn.setStyle("bottom", "row" + (task.endDate.hours) + ":" + -int(task.endDate.minutes / 60 * 50 - 50)); /*/ btn.setStyle("top", "row" + task.startDate.hours + ":0"); if (task.startDate.hours == task.endDate.hours) { btn.setStyle("bottom", "row" + task.endDate.hours + ":0"); } else { btn.setStyle("bottom", "row" + (task.endDate.hours - 1) + ":0"); } //*/ btn.setStyle("left", "col" + i + ":0"); btn.setStyle("right", "col" + i + ":0"); canvas.addChild(btn); } } } public function createConsRows():void{ for (var i:Number = 0 ; i &lt; 24 ; ++i) { var consRow:ConstraintRow = new ConstraintRow(); consRow.setActualHeight(50); consRow.initialized(canvas, "row" + i); // consRow.percentHeight = 100 / 24; canvas.constraintRows.push(consRow); // create rule var label:Label = new Label(); label.text = "" + (i + 1); label.setStyle("bottom", "row" + i + ":0"); canvas.addChild(label); var rule:HRule = new HRule(); rule.setStyle("strokeColor", "#808080"); rule.height = 1; rule.percentWidth = 100; rule.setStyle("bottom", "row" + i + ":0"); canvas.addChild(rule); } } ]]&gt; &lt;/mx:Script&gt; &lt;mx:Canvas id="canvas" width="100%" height="100%" borderThickness="1" borderColor="#808080" borderStyle="solid" horizontalScrollPolicy="off" verticalScrollPolicy="auto"&gt; &lt;/mx:Canvas&gt; &lt;mx:Button label="Refresh" click="refresh();" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> Online Demo: </p> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="400"> <param name="movie" value="http://riafiles.googlepages.com/DailyTaskByConstraintLayout.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/DailyTaskByConstraintLayout.swf" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="100%" height="300" wmode="opaque"></embed> </object> </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/03/flex-button-with-constraint-layout.html">Flex Button with Constraint Layout</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-23126172847435130782008-04-05T20:54:00.009+08:002008-07-14T18:14:19.182+08:002008-07-14T18:14:19.182+08:00Flex Collapsible DataGrid<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex 3 AdvancedDataGrid 具有 Tree 與 跨欄 (columnSpan) 的功能<br/> 以下利用這兩種特性做出可收合的 DataGrid </p> <span class="fullpost"> <p> MyDataGridGroupItemRenderer Class: </p> <pre name="code" class="as3:showcolumns"> package com.ticore.uicomponents { import mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer; public class MyDataGridGroupItemRenderer extends AdvancedDataGridGroupItemRenderer { public function MyDataGridGroupItemRenderer() { super(); } override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); this.graphics.clear(); this.graphics.lineStyle(1, 0x808080, 1, true); this.graphics.drawRect(-1, -1, unscaledWidth + 1, unscaledHeight); } } } </pre> <p> Flex MXML Code: </p> <pre name="code" class="mxml:showcolumns:contentHeight[400px]"> &lt;?xml version="1.0"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#E0E0E0"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.controls.advancedDataGridClasses.*; import mx.collections.ArrayCollection; [Bindable] private var dpHierarchy:ArrayCollection = new ArrayCollection([ {Region:"Arizona", children: [ {Territory_Rep:"Barbara Jennings", Actual:38865, Estimate:40000}, {Territory_Rep:"Dana Binn", Actual:29885, Estimate:30000}]}, {Region:"Central California", children: [ {Territory_Rep:"Joe Smith", Actual:29134, Estimate:30000}]}, {Region:"Nevada", children: [ {Territory_Rep:"Bethany Pittman", Actual:52888, Estimate:45000}]}, {Region:"Northern California", children: [ {Territory_Rep:"Lauren Ipsum", Actual:38805, Estimate:40000}, {Territory_Rep:"T.R. Smith", Actual:55498, Estimate:40000}]}, {Region:"Southern California", children: [ {Territory_Rep:"Alice Treu", Actual:44985, Estimate:45000}, {Territory_Rep:"Jane Grove", Actual:44913, Estimate:45000}]} ]); public function groupLabelFunction(item:Object, column:AdvancedDataGridColumn):String{ if (item &amp;&amp; item.children) { return item[column.dataField] + " (" + item.children.length + ")"; } else { return item[column.dataField]; } } ]]&gt; &lt;/mx:Script&gt; &lt;mx:AdvancedDataGrid id="myADG" width="100%" height="100%" rowHeight="24" headerHeight="24" fontSize="12" displayItemsExpanded="true" paddingTop="0" paddingBottom="0" paddingLeft="0" paddingRight="0" folderClosedIcon="{null}" folderOpenIcon="{null}" defaultLeafIcon="{null}" &gt; &lt;mx:dataProvider&gt; &lt;mx:HierarchicalData source="{dpHierarchy}" /&gt; &lt;/mx:dataProvider&gt; &lt;mx:rendererProviders&gt; &lt;mx:AdvancedDataGridRendererProvider columnIndex="0" columnSpan="4" dataField="Region" renderer="com.ticore.uicomponents.MyDataGridGroupItemRenderer" /&gt; &lt;mx:AdvancedDataGridRendererProvider dataField="Territory_Rep" renderer="mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer" /&gt; &lt;/mx:rendererProviders&gt; &lt;mx:groupedColumns&gt; &lt;mx:AdvancedDataGridColumn id="col1" dataField="Region" width="20" labelFunction="groupLabelFunction" /&gt; &lt;mx:AdvancedDataGridColumn id="col2" dataField="Territory_Rep"/&gt; &lt;mx:AdvancedDataGridColumn dataField="Actual"/&gt; &lt;mx:AdvancedDataGridColumn dataField="Estimate"/&gt; &lt;/mx:groupedColumns&gt; &lt;/mx:AdvancedDataGrid&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> Online Demo: </p> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="300"> <param name="movie" value="http://riafiles.googlepages.com/CollapsibleDataGrid.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/CollapsibleDataGrid.swf" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="100%" height="300" wmode="opaque"></embed> </object> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-64317791803782063042008-04-02T01:02:00.004+08:002008-07-14T18:14:19.166+08:002008-07-14T18:14:19.166+08:00Flex - Tab Tree<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 幾年前很流行 Flash Tab Menu 組件<br/> 乍看之下,很像是 Accordion 組件<br/> 實際上是比較接近 Tree 組件<br/> </p> <span class="fullpost"> <p> 以下修改 Tree 組件行為,讓它變得比較類似 Tab Menu<br/> 不同深度節點有不同的背景色<br/> 相同深度下的節點同時只能打開一個 </p> <p> TreeItemRenderer Class: </p> <pre name="code" class="as3:showcolumns:contentHeight[400px]"> package com.ticore.uicomponents { import mx.controls.treeClasses.TreeItemRenderer; public class TreeItemRenderer extends mx.controls.treeClasses.TreeItemRenderer { override protected function commitProperties():void { super.commitProperties(); } override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void { super.updateDisplayList(unscaledWidth, unscaledHeight); if (listData) { var fillAlpha:Number; var fillColor:Number; switch (listData['depth']) { case 1: fillAlpha = 0.8; fillColor = 0xFFD0D0; break; case 2: fillAlpha = 0.6; fillColor = 0xD0FFD0; break; case 3: fillAlpha = 0.4; fillColor = 0xD0D0FF; break; } this.graphics.clear(); this.graphics.lineStyle(1, 0x808080, 0.5, true); this.graphics.beginFill(fillColor, fillAlpha); this.graphics.drawRect(0, 0, unscaledWidth - 1, unscaledHeight - 1); this.graphics.endFill(); } } } } </pre> <p> Flex MXML: </p> <pre name="code" class="mxml:showcolumns:contentHeight[400px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#F0F0F0" layout="horizontal"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.collections.ICollectionView; import mx.events.TreeEvent; private var openingItem:Object; public function onItemOpen(evtObj:TreeEvent):void{ openingItem = evtObj.item; // callLater(handleCoseItems); setTimeout(handleCoseItems, 100); } public function handleCoseItems():void{ var item:Object = openingItem; var parentItems:Object = tree.getParentItem(item) == null ? objData : tree.getParentItem(item); for (var i:Number = 0 ; i &lt; parentItems.children.length ; ++i) { var siblingItem:Object = parentItems.children[i]; if (siblingItem != item) { if (tree.isItemOpen(siblingItem)) { tree.expandItem(siblingItem, false, true); } } } } [Bindable] public var objData:Object ={children: [ {label: "Node", children: [ {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, ]}, {label: "Node", children: [ {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, ]}, {label: "Node", children: [ {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]}, ]}, ]}; ]]&gt; &lt;/mx:Script&gt; &lt;mx:Tree id="tree" width="100%" height="100%" indentation="0" showRoot="false" folderClosedIcon="{null}" folderOpenIcon="{null}" defaultLeafIcon="{null}" itemOpen="onItemOpen(event)" dataProvider="{objData}" verticalScrollPolicy="auto" fontSize="14" backgroundAlpha="0" paddingBottom="1" paddingTop="1" paddingLeft="0" paddingRight="0" labelField="label" itemRenderer="com.ticore.uicomponents.TreeItemRenderer" &gt; &lt;/mx:Tree&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="300"> <param name="movie" value="http://riafiles.googlepages.com/TabTree.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/TabTree.swf" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="100%" height="300" wmode="opaque"></embed> </object> </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2007/10/flex-auto-stretch-tree.html">Flex - 自動伸縮的 Tree</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-79970539312953988382008-03-31T16:37:00.002+08:002008-07-14T18:14:19.026+08:002008-07-14T18:14:19.026+08:00Flex Image, SWFLoader Align Bug<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex Image, SWFLoader 組件置中對齊 Bug </p> <p> Flex SDK Version:2.01, 3.0 </p> <p> 發生條件: </p> <p> Image or SWFLoader 的 scaleContent 設為 false<br/> horizontalAlign 設為 center<br/> verticalAlign 設為 middle<br/> 必須要指定 width, height<br/> 當內容物的大小超過指定的 width, height 時<br/> Image or SWFLoader 畫面排版就會錯亂 </p> <span class="fullpost"> <p> Flex Image Align Bug Demo Code: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal" backgroundColor="#E0E0E0"&gt; &lt;mx:HBox width="100%" height="100%" borderStyle="inset"&gt; &lt;mx:Image source="@Embed(source='../assets/img.jpg')" scaleContent="false" alpha="0.7" width="100%" height="100%" horizontalAlign="center" verticalAlign="middle"/&gt; &lt;/mx:HBox&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> Flex Image Align Bug Screen Capture: </p> <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_JHhe1vma7nY/R_Cncg1dXEI/AAAAAAAAAZw/hXcu6YFQ9jU/s1600-h/FlexImageAlignBugScr.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://bp1.blogger.com/_JHhe1vma7nY/R_Cncg1dXEI/AAAAAAAAAZw/hXcu6YFQ9jU/s320/FlexImageAlignBugScr.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5183827279395773506" /></a> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-55785597445168115992008-03-25T12:16:00.012+08:002008-07-14T18:14:19.081+08:002008-07-14T18:14:19.081+08:00Flex ListBase selectedIndices, selectedItems Bug<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex ListBase, AdvancedListBase 有一個很嚴重的 Bug<br/> 只要是繼承 ListBase, AdvancedListBase 的組件都會受到影響<br/> 像是 List, Menu, Tree, DataGrid, HorizontalList, TileList<br/> 與 AdvancedDataGrid, OLAPDataGrid </p> <p> selectedIndices, selectedItems Bug 描述: </p> <ul> <li> 當 dataProvider 內沒有資料時,指定 selectedIndices 為空陣列 []<br/> 會造成往後取用 selectedIndices 永遠得到空陣列 </li> <br/> <li> 當 dataProvider 內沒有資料時,指定 selectedItems 為空陣列 []<br/> 會造成往後取用 selectedItems 永遠得到空陣列 </li> </ul> <p> 以上的 Bug 在 Flex SDK 2.0.1, 3.0 都會發生 </p> <span class="fullpost"> <p> ListBase selectedIndices, selectedItems Bug Demo Code:</p> <pre name="code" class="mxml:showcolumns:contentHeight[300px]"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12" layout="vertical" verticalAlign="middle" backgroundColor="#F0F0F0"&gt; &lt;mx:Style&gt; Button { fontWeight: normal; } &lt;/mx:Style&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.collections.ArrayCollection; import mx.utils.ObjectUtil; [Bindable] public var itemData:ArrayCollection = new ArrayCollection(); public function addItems():void{ var num:Number = Math.random() * 20 + 3; for (var i:Number = 0 ; i &lt; num ; i ++) { itemData.addItem({label: i}); } } public function removeAllItems():void{ itemData.removeAll(); } ]]&gt; &lt;/mx:Script&gt; &lt;mx:HBox&gt; &lt;mx:Label text="list.selectedIndices.length : {list.selectedIndices.length}" /&gt; &lt;mx:Label text="list.selectedItems.length : {list.selectedItems.length}" /&gt; &lt;/mx:HBox&gt; &lt;mx:HRule width="100%" /&gt; &lt;mx:HBox&gt; &lt;mx:List id="list" width="100" dataProvider="{itemData}" /&gt; &lt;mx:VRule height="100%" /&gt; &lt;mx:VBox&gt; &lt;mx:Button label="Add Items" click="addItems()" /&gt; &lt;mx:Button label="Remove All Items" click="removeAllItems()" /&gt; &lt;mx:HRule width="100%"/&gt; &lt;mx:Button label="selectedIndices = []" click="list.selectedIndices = []" /&gt; &lt;mx:Button label="selectedItems = []" click="list.selectedItems = []" /&gt; &lt;mx:HRule width="100%"/&gt; &lt;mx:Button label="selectedIndex = -1" click="list.selectedIndex = -1" /&gt; &lt;mx:Button label="selectedItem = null" click="list.selectedItem = null" /&gt; &lt;/mx:VBox&gt; &lt;mx:VRule height="100%" /&gt; &lt;mx:VBox&gt; &lt;mx:Button label="trace(list.selectedIndices);" click="trace(ObjectUtil.toString(list.selectedIndices));" /&gt; &lt;mx:Button label="trace(list.selectedItems);" click="trace(ObjectUtil.toString(list.selectedItems));" /&gt; &lt;mx:Button label="trace(list.selectedIndex);" click="trace(list.selectedIndex);" /&gt; &lt;/mx:VBox&gt; &lt;/mx:HBox&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0" width="100%" height="300"> <param name="movie" value="http://riafiles.googlepages.com/ListBaseBugDemo.swf" /> <param name="wmode" value="opaque" /> <embed src="http://riafiles.googlepages.com/ListBaseBugDemo.swf" pluginspage="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="100%" height="300" wmode="opaque"></embed> </object> </p> <p> 只要在 List 沒有資料情況下,點一下 "selectedIndices = []", selectedItems = []" 按鈕<br/> 之後再按 "Add Items" 加入資料,點選 List 內的 Item<br/> 就會發現 selectedIndices, selectedItems 長度永遠是 0<br/> 無法復原</p> <p> 追蹤 ListBase 原始碼<br/> 發現問題是出在 bSelectedIndicesChanged, bSelectedItemsChanged<br/> ListBase 內唯一將這兩個變數設回 false 動作是發生在 adjustSelectionSettings 函式內</p> <pre> ... if (bSelectedIndicesChanged &amp;&amp; (collectionHasItems || (_selectedIndices == null))) { bSelectedIndicesChanged = false; bSelectedIndexChanged = false; commitSelectedIndices(_selectedIndices); } if (bSelectedItemsChanged &amp;&amp; (collectionHasItems || (_selectedItems == null))) { bSelectedItemsChanged = false; bSelectedIndexChanged = false; commitSelectedItems(_selectedItems); } ... </pre> <p> 但是 _selectedItems 根本不可能為 null<br/> 導致這兩個旗標沒有辦法正常的被設回 false....</p> <p> 解決方式,只好自行繼承 List 等相關組件<br/> 於設定 selectedIndices, selectedItems 時檢查了</p> <pre name="code" class="as3:showcolumns"> package com.ticore.uicomponents { import mx.controls.List; public class MyList extends List { public function MyList() { super(); } [Bindable("change")] [Bindable("valueCommit")] [Inspectable(category="General")] public override function get selectedIndices():Array { return super.selectedIndices; } public override function set selectedIndices(indices:Array):void{ if (this.collection &amp;&amp; this.collection.length &gt; 0) { super.selectedIndices = indices; } } [Bindable("change")] [Bindable("valueCommit")] [Inspectable(environment="none")] public override function get selectedItems():Array { return super.selectedItems; } public override function set selectedItems(items:Array):void{ if (this.collection &amp;&amp; this.collection.length &gt; 0) { super.selectedItems = items; } } } } </pre> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2007/11/flex-listbase-selecteditems.html">Flex ListBase selectedItems, selectedIndices 陷阱</a></p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-299298545186408722008-03-20T22:37:00.012+08:002008-07-14T18:17:47.941+08:002008-07-14T18:17:47.941+08:00Flex Tip - 在 Data Binding 內使用 [...] 運算子<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> Flex MXML 的 Data Binding 功能很好用<br/> 但是在 Expression 區塊內使用關聯陣列存取運算子會出現警告 </p> <pre> Data binding will not be able to detect changes when using square bracket operator. For Array, please use ArrayCollection.getItemAt() instead. </pre> <p> 這是由於 Data Binding 無法偵測到 Array 內元素的改變而跳出的警告訊息<br/> 依照它的建議,改用 ArrayCollection.getItemAt() 可以解決<br/> 但是,很多情況下,資料來源並不是陣列啊 </p> <span class="fullpost"> <p> 以下便是一個例子<br/> 資料來源是 ObjectProxy 物件<br/> objectProxy 物件整個被替換時,與 cb.value 改變時<br/> Data Binding 都會被正常觸發<br/> objectProxy.prop1 屬性單獨改變時則不會觸發<br/> 有時候,我們只需要這樣的功能<br/> 但是它仍然會出現警告訊息 </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#F0F0F0" layout="vertical"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.utils.ObjectProxy; import mx.controls.*; [Bindable] public var objectProxy:ObjectProxy = new ObjectProxy({prop1: "Prop 1", prop2: "Prop 2", prop3: "Prop 3"}); ]]&gt; &lt;/mx:Script&gt; &lt;mx:ComboBox id="cb" dataProvider="[1, 2, 3]" /&gt; &lt;mx:Label text="{objectProxy['prop' + cb.value]}" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 這個時候,就面臨兩難了<br/> 是要放棄好用的 Data Binding Expression? 或是對警告訊息視而無見? </p> <p> 解決方式 1. 利用匿名函式將關聯陣列存取運算子包起來 </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#F0F0F0" layout="vertical"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.utils.ObjectProxy; import mx.controls.*; [Bindable] public var objectProxy:ObjectProxy = new ObjectProxy({prop1: "Prop 1", prop2: "Prop 2", prop3: "Prop 3"}); ]]&gt; &lt;/mx:Script&gt; &lt;mx:ComboBox id="cb" dataProvider="[1, 2, 3]" /&gt; &lt;mx:Label text="{(function():*{return objectProxy['prop' + cb.value];})(objectProxy, cb.value)}" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 解決方式 2. 將關聯陣列存取運算子放到外部函式 </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#F0F0F0" layout="vertical"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.utils.ObjectProxy; import mx.controls.*; [Bindable] public var objectProxy:ObjectProxy = new ObjectProxy({prop1: "Prop 1", prop2: "Prop 2", prop3: "Prop 3"}); public function getProp(...args):*{ return objectProxy['prop' + cb.value]; } ]]&gt; &lt;/mx:Script&gt; &lt;mx:ComboBox id="cb" dataProvider="[1, 2, 3]" /&gt; &lt;mx:Label text="{getProp(objectProxy, cb.value)}" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's Blog - http://ticore.blogspot.com/ --&gt; </pre> <p> 重點來了,以上兩種方法都有一個共通的要點<br/> 那就是 objectProxy, cb.value 這兩個屬性都要在 Data Binding Expression 內曝光一下<br/> 這樣 Data Binding 才會抓到資料改變的觸發事件 </p> <p> 相關連結:<br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-encapsulate-databinding.html">Flex 技巧 - 將資料綁定封裝起來</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-databinding-manually.html">Flex - 純手工設定 DataBinding 的方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-bindingmanager-usage.html">Flex 技巧 - BindingManager 使用方式</a><br/> <a href="http://ticore.blogspot.com/2008/06/flex-tip-monitor-data-binding.html">Flex 技巧 - 觀察 Data Binding 資料變化</a><br/> <a href="http://ticore.blogspot.com/2007/01/flex-2-bindable-metadata-tag.html">Flex 2 Bindable Metadata Tag 背後實際作用</a><br/> <a href="http://ticore.blogspot.com/2006/12/flex-20-actionscript-30-data-binding.html">Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding</a> </p> </span>{id: "Ticore"}; //http://www.blogger.com/profile/01433005931305983346noreply@blogger.comtag:blogger.com,1999:blog-6934949377658966040.post-8961990248252651802008-03-20T00:36:00.002+08:002008-07-14T18:17:47.942+08:002008-07-14T18:17:47.942+08:00Flex 小技巧 - 讓 List.selectedIndex 預設值為 0<p align="right" class="author"><a href="http://ticore.blogspot.com/">Ticore's Blog</a></p> <p> 在使用 Flex List 或是其它具有 selectIndex 的組件時<br/> 常常會需要讓 List 預設自動選到第一個 Item </p> <p> 不過當 List.dataProvider 資料來源變更時<br/> 之前設定好的 selectedIndex 又會被清除掉<br/> 假如企圖在資料來源變更事件中主動設定 List.selectedIndex 也是沒有作用的<br/> 因為 List 需要一點時間套用新的資料<br/> 這個時間差並不好抓<br/> 使用 callLater 也無法保證 100% 一定可行 </p> <p> 以下分享一個小技巧<br/> 可以讓 List 每次資料變更後,就自動預設選取第一個 Item 上 </p> <span class="fullpost"> <p> 使用方式很簡單,讓 selectedIndex 自己綁定自己就好了 </p> <p> Flex MXML Code: </p> <pre name="code" class="mxml:showcolumns"> &lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#F0F0F0" layout="vertical"&gt; &lt;mx:Script&gt; &lt;![CDATA[ import mx.controls.*; [Bindable] public var listData:Array = []; public function doUpdateData():void{ listData = []; for (var i:Number = 0 ; i &lt; 16 ; ++i) { listData.push(Math.round(Math.random() * 10000)); } } ]]&gt; &lt;/mx:Script&gt; &lt;mx:List id="list" width="100" height="200" dataProvider="{listData}" selectedIndex="{list.selectedIndex &amp;lt; 0 ? 0 : list.selectedIndex}" /&gt; &lt;mx:Button label="Update Data" click="doUpdateData()" /&gt; &lt;/mx:Application&gt; &lt;!-- Ticore's BLog - http://ticore.blogspot.com/ --&gt; </pre> </span>