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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
verticalAlign="middle" backgroundColor="#FFFFFF"
creationComplete="init();" fontSize="12">
<mx:Script>
<![CDATA[
public var comp:BindingComp;
public function init():void{
comp = new BindingComp();
comp.initialize();
}
]]>
</mx:Script>
<mx:HBox>
<mx:Label text="No 1:" />
<mx:NumericStepper id="no1" maximum="100" />
</mx:HBox>
<mx:HBox>
<mx:Label text="No 2:" />
<mx:NumericStepper id="no2" maximum="100" />
</mx:HBox>
<mx:CheckBox id="chk" label="DataBinding Enabled"
change="comp.target = chk.selected ? this : null;" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</pre>
<p>
BindingComp.mxml:
</p>
<pre name="code" class="mxml:showcolumns">
<?xml version="1.0" encoding="utf-8"?>
<mx:UIComponent xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![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;
}
]]>
</mx:Script>
<mx:Model>
{doBinding1(target.no1.value)}
</mx:Model>
<mx:Model>
{doBinding2(target.no2.value)}
</mx:Model>
</mx:UIComponent>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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]">
<?xml version="1.0"?>
<mx:Application layout="vertical" fontSize="12" backgroundColor="#FFFFFF"
xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Style>
HBox.whiteBox {
paddingTop: 15px;
paddingBottom: 15px;
paddingLeft: 15px;
paddingRight: 15px;
backgroundColor: #000000;
}
</mx:Style>
<mx:HBox styleName="whiteBox" alpha="{alphaSlider.value}"
blendMode="{blendModeCb.value ? blendModeCb.value : 'normal'}">
<mx:HBox styleName="whiteBox">
<mx:HBox styleName="whiteBox">
<mx:HBox styleName="whiteBox" />
</mx:HBox>
</mx:HBox>
</mx:HBox>
<mx:HRule width="100%" />
<mx:HBox verticalAlign="middle">
<mx:Label text="BlendMode: " />
<mx:ComboBox id="blendModeCb">
<mx:dataProvider>
["normal", "layer", "darken", "invert", "hardlight"]
</mx:dataProvider>
</mx:ComboBox>
</mx:HBox>
<mx:HBox verticalAlign="middle">
<mx:Label text="Alpha: " />
<mx:HSlider id="alphaSlider" value="0.5"
tickValues="[0, 0.5, 1]" labels="[0, 0.5, 1]"
minimum="0" maximum="1" liveDragging="true" />
</mx:HBox>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF" layout="vertical" fontSize="12">
<mx:Label text="Binding Source:" />
<mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" />
<mx:Label text="Binding Destination:" />
<mx:HSlider id="slider2" snapInterval="1" maximum="100" value="{slider1.value}" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF" layout="vertical" fontSize="12"
implements="mx.binding.IBindingClient"
creationComplete="onCreateComplete();">
<mx:Script>
<![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");
}
]]>
</mx:Script>
<mx:Label text="Binding Source:" />
<mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" />
<mx:Label text="Binding Destination:" />
<mx:HSlider id="slider2" snapInterval="1" maximum="100" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF" layout="vertical" fontSize="12"
creationComplete="onCreateComplete();">
<mx:Script>
<![CDATA[
import mx.binding.*;
public function onCreateComplete():void{
BindingManager.debugBinding("slider2.value");
}
]]>
</mx:Script>
<mx:Label text="Binding Source:" />
<mx:HSlider id="slider1" snapInterval="1" maximum="100" value="50" />
<mx:Label text="Binding Destination:" />
<mx:HSlider id="slider2" snapInterval="1" maximum="100" value="{slider1.value}" />
<mx:CheckBox id="chk" label="Binding Enabled" selected="true"
change="BindingManager.setEnabled(this, chk.selected); btn.enabled = chk.selected;" />
<mx:Button id="btn" label="Execute DataBinding"
click="BindingManager.executeBindings(this, 'slider2.value', null);" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#FFFFFF" layout="vertical" fontSize="12">
<mx:Label text="Binding Source:" />
<mx:HSlider id="slider1" snapInterval="1" maximum="100" />
<mx:Label text="Binding Destination:" />
<mx:HSlider id="slider2" snapInterval="1" maximum="100"
value="{trace('Before :', slider2.value),
setTimeout(function():void{trace('After :', slider2.value);}, 0), slider1.value}"/>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Button label="Submit" click="remoteObj.test(123);"/>
<mx:RemoteObject id="remoteObj" destination="xxx" />
</mx:Application>
</pre>
<p>
LoaderApp.mxml:
</p>
<pre name="code" class="mxml:showcolumns">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:SWFLoader id="swfLdr" width="100%" height="100%">
<mx:creationComplete>
<![CDATA[
swfLdr.loaderContext = new LoaderContext();
swfLdr.loaderContext.applicationDomain = new ApplicationDomain();
swfLdr.source = "LoadeeApp.swf";
]]>
</mx:creationComplete>
</mx:SWFLoader>
</mx:Application>
</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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
fontSize="12" backgroundColor="#F0F0F0">
<mx:Script>
<![CDATA[
import flash.events.*;
import mx.managers.FocusManager;
public function onTxtLink(evtObj:Event):void{
textArea.text += evtObj + "\n";
}
]]>
</mx:Script>
<mx:Label selectable="false" link="onTxtLink(event)">
<mx:htmlText>
<![CDATA[Flex Label : <a href='event:linkEvent'>Link Event Text</a> | ]]>
<![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
</mx:htmlText>
</mx:Label>
<mx:Text selectable="false" link="onTxtLink(event)">
<mx:htmlText>
<![CDATA[Flex Text : <a href='event:linkEvent'>Link Event Text</a> | ]]>
<![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
</mx:htmlText>
</mx:Text>
<mx:Button label="Clear Log" click="textArea.text = '';" />
<mx:TextArea id="textArea" width="100%" height="100%" />
</mx:Application>
</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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
fontSize="12" backgroundColor="#F0F0F0">
<mx:Script>
<![CDATA[
import flash.events.*;
import mx.managers.FocusManager;
public function onTxtLink(evtObj:Event):void{
textArea.text += evtObj + "\n";
}
]]>
</mx:Script>
<mx:Label selectable="false" link="onTxtLink(event)"
rollOver="focusManager.deactivate()" rollOut="focusManager.activate()">
<mx:htmlText>
<![CDATA[Flex Label : <a href='event:linkEvent'>Link Event Text</a> | ]]>
<![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
</mx:htmlText>
</mx:Label>
<mx:Text selectable="false" link="onTxtLink(event)"
rollOver="focusManager.deactivate()" rollOut="focusManager.activate()">
<mx:htmlText>
<![CDATA[Flex Text : <a href='event:linkEvent'>Link Event Text</a> | ]]>
<![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
</mx:htmlText>
</mx:Text>
<mx:Button label="Clear Log" click="textArea.text = '';" />
<mx:TextArea id="textArea" width="100%" height="100%" />
</mx:Application>
</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 < 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 > (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 < 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 > (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 > 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 < dataProvider.length ; ++i) {
ix = i % this.columnCount;
iy = Math.floor(i / this.columnCount);
xFlag = false;
if (startIndexPoint.x <= endIndexPoint.x) {
xFlag = ix >= startIndexPoint.x && ix <= endIndexPoint.x;
} else {
xFlag = ix <= startIndexPoint.x && ix >= endIndexPoint.x;
}
yFlag = false;
if (startIndexPoint.y <= endIndexPoint.y) {
yFlag = iy >= startIndexPoint.y && iy <= endIndexPoint.y;
} else {
yFlag = iy <= startIndexPoint.y && iy >= endIndexPoint.y;
}
if (xFlag && yFlag) {
newSelectedIndices.push(i);
}
}
} else {
for (i = 0 ; i < dataProvider.length ; ++i) {
ix = Math.floor(i / this.rowCount);
iy = i % this.rowCount;
xFlag = false;
if (startIndexPoint.x <= endIndexPoint.x) {
xFlag = ix >= startIndexPoint.x && ix <= endIndexPoint.x;
} else {
xFlag = ix <= startIndexPoint.x && ix >= endIndexPoint.x;
}
yFlag = false;
if (startIndexPoint.y <= endIndexPoint.y) {
yFlag = iy >= startIndexPoint.y && iy <= endIndexPoint.y;
} else {
yFlag = iy <= startIndexPoint.y && iy >= endIndexPoint.y;
}
if (xFlag && 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 < oldSelectedIndices.length ; ++i) {
index = oldSelectedIndices[i];
deltaDict[index] = null;
}
oldSelectedIndices = newSelectedIndices;
for (i = 0 ; i < newSelectedIndices.length ; ++i) {
index = newSelectedIndices[i];
if (index in deltaDict) {
delete deltaDict[index];
} else {
deltaDict[index] = null;
}
}
selectedIndices = this.selectedIndices;
for (i = 0 ; i < 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 < oldSelectedIndices.length ; ++i) {
deltaRemoveDict[oldSelectedIndices[i]] = null;
}
for (i = 0 ; i < 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 < 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 < 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 >= 0 && drawStartPoint.y <= 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 >= 0 && drawStartPoint.x <= 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 < 0 ? 0 : p.x;
newPoint.y = p.y < 0 ? 0 : p.y;
newPoint.x = newPoint.x > listContent.width - 1 ? listContent.width - 1 : newPoint.x;
newPoint.y = newPoint.y > 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) > itemMargin ? 0.5 : 0;
indexPoint.y += (p.y % this.rowHeight) > itemMargin ? 0.5 : 0;
indexPoint.x += (p.x % this.columnWidth) > (this.columnWidth - itemMargin) ? 0.5 : 0;
indexPoint.y += (p.y % this.rowHeight) > (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 > 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 && !iterator.afterLast && iteratorValid);
while ((byCount && rowsNeeded--) || (!byCount && (colNum < numCols + firstCol))) {
rowNum = firstRow;
yy = top;
while (rowNum < 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 && yy < 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 < firstRow; i++)
more = moveNextSafely(more);
}
xx += columnWidth;
}
} else { // horizontal
numCols = maxColumns > 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 && !iterator.afterLast && iteratorValid);
lastRowMade = rowNum-1;
while ((byCount && rowsNeeded--) || (!byCount && rowNum < numRows + firstRow)) {
colNum = firstCol;
xx = left;
rowInfo[rowNum] = null;
while (colNum < 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 && xx < 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 < firstCol; i++)
more = moveNextSafely(more);
}
yy += rowHeight;
}
}
if (!byCount) {
var a:Array;
// prune excess rows and columns
while (listItems.length > numRows + offscreenExtraRowsTop) {
a = listItems.pop();
rowInfo.pop();
for (i = 0; i < a.length; i++) {
oldItem = a[i];
if (oldItem) {
if (oldItem.parent)
listContent.removeChild(DisplayObject(oldItem));
addToFreeItemRenderers(oldItem);
}
}
}
if (listItems.length && listItems[0].length > numCols + offscreenExtraColumnsLeft) {
for (i = 0; i < numRows + offscreenExtraRowsTop; i++) {
a = listItems[i];
while (a.length > 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 && 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 && (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) &&
((ld.rowIndex > rowNum) || ((ld.rowIndex == rowNum) && (ld.columnIndex > colNum)))) ||
((direction == TileBaseDirection.VERTICAL) &&
((ld.columnIndex > colNum) || ((ld.columnIndex == colNum) && (ld.rowIndex > rowNum)))))
listItems[ld.rowIndex][ld.columnIndex] = null;
else
item = null;
}
}
if (!item) {
item = getReservedOrFreeItemRenderer(wrappedData);
if (item && !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 && (!getRendererSemanticValue(item,ModifiedCollectionView.REPLACEMENT))
&& (!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 < itemMargin || pt.y % rowHeight > columnWidth - itemMargin) {
return null;
}
if (pt.x % columnWidth < itemMargin || pt.x % columnWidth > columnWidth - itemMargin) {
return null;
}
var yy:Number = 0;
var n:int = listItems.length;
for (var i:int = 0; i < n; i++) {
if (listItems[i].length) {
if (pt.y < 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 && target != this) {
if (target is IListItemRenderer && 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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
initialize="onInit();" backgroundColor="#E0E0E0">
<mx:Script>
<![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 < 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 < taskData.length ; ++i) {
var task:Object = taskData[i];
// 排除跨天 Task
if (task.startDate.hours > task.endDate.hours) {
continue;
}
loop2:for (var j:Number = 0 ; j < taskColumnData.length ; ++j) {
var taskColumn:Array = taskColumnData[j];
loop3:for (var k:Number = 0 ; k < taskColumn.length ; ++k) {
var taskAdded:Object = taskColumn[k];
if (task.startDate.time > taskAdded.endDate.time || task.endDate.time < taskAdded.startDate.time) {
} else {
continue loop2;
}
}
taskColumn.push(task);
continue loop1;
}
// 既有 column 時間都重疊 -> 建立新 column
taskColumnData.push([task]);
}
}
public function createConsCols():void{
canvas.constraintColumns = [];
for (var i:Number = 0 ; i < 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 < taskColumnData.length ; ++i) {
var taskColumn:Array = taskColumnData[i];
for (var j:Number = 0 ; j < 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 < 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);
}
}
]]>
</mx:Script>
<mx:Canvas id="canvas" width="100%" height="100%"
borderThickness="1" borderColor="#808080" borderStyle="solid"
horizontalScrollPolicy="off" verticalScrollPolicy="auto">
</mx:Canvas>
<mx:Button label="Refresh" click="refresh();" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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]">
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#E0E0E0">
<mx:Script>
<![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 && item.children) {
return item[column.dataField] + " (" + item.children.length + ")";
} else {
return item[column.dataField];
}
}
]]>
</mx:Script>
<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}" >
<mx:dataProvider>
<mx:HierarchicalData source="{dpHierarchy}" />
</mx:dataProvider>
<mx:rendererProviders>
<mx:AdvancedDataGridRendererProvider columnIndex="0" columnSpan="4" dataField="Region"
renderer="com.ticore.uicomponents.MyDataGridGroupItemRenderer" />
<mx:AdvancedDataGridRendererProvider dataField="Territory_Rep"
renderer="mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer" />
</mx:rendererProviders>
<mx:groupedColumns>
<mx:AdvancedDataGridColumn id="col1" dataField="Region" width="20"
labelFunction="groupLabelFunction" />
<mx:AdvancedDataGridColumn id="col2" dataField="Territory_Rep"/>
<mx:AdvancedDataGridColumn dataField="Actual"/>
<mx:AdvancedDataGridColumn dataField="Estimate"/>
</mx:groupedColumns>
</mx:AdvancedDataGrid>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F0F0F0" layout="horizontal">
<mx:Script>
<![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 < 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"}]},
]},
]};
]]>
</mx:Script>
<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" >
</mx:Tree>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="horizontal" backgroundColor="#E0E0E0">
<mx:HBox width="100%" height="100%" borderStyle="inset">
<mx:Image source="@Embed(source='../assets/img.jpg')"
scaleContent="false" alpha="0.7" width="100%" height="100%"
horizontalAlign="center" verticalAlign="middle"/>
</mx:HBox>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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]">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12"
layout="vertical" verticalAlign="middle" backgroundColor="#F0F0F0">
<mx:Style>
Button {
fontWeight: normal;
}
</mx:Style>
<mx:Script>
<![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 < num ; i ++) {
itemData.addItem({label: i});
}
}
public function removeAllItems():void{
itemData.removeAll();
}
]]>
</mx:Script>
<mx:HBox>
<mx:Label text="list.selectedIndices.length : {list.selectedIndices.length}" />
<mx:Label text="list.selectedItems.length : {list.selectedItems.length}" />
</mx:HBox>
<mx:HRule width="100%" />
<mx:HBox>
<mx:List id="list" width="100" dataProvider="{itemData}" />
<mx:VRule height="100%" />
<mx:VBox>
<mx:Button label="Add Items" click="addItems()" />
<mx:Button label="Remove All Items" click="removeAllItems()" />
<mx:HRule width="100%"/>
<mx:Button label="selectedIndices = []" click="list.selectedIndices = []" />
<mx:Button label="selectedItems = []" click="list.selectedItems = []" />
<mx:HRule width="100%"/>
<mx:Button label="selectedIndex = -1" click="list.selectedIndex = -1" />
<mx:Button label="selectedItem = null" click="list.selectedItem = null" />
</mx:VBox>
<mx:VRule height="100%" />
<mx:VBox>
<mx:Button label="trace(list.selectedIndices);"
click="trace(ObjectUtil.toString(list.selectedIndices));" />
<mx:Button label="trace(list.selectedItems);"
click="trace(ObjectUtil.toString(list.selectedItems));" />
<mx:Button label="trace(list.selectedIndex);"
click="trace(list.selectedIndex);" />
</mx:VBox>
</mx:HBox>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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
&& (collectionHasItems || (_selectedIndices == null)))
{
bSelectedIndicesChanged = false;
bSelectedIndexChanged = false;
commitSelectedIndices(_selectedIndices);
}
if (bSelectedItemsChanged
&& (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 && this.collection.length > 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 && this.collection.length > 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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F0F0F0" layout="vertical">
<mx:Script>
<![CDATA[
import mx.utils.ObjectProxy;
import mx.controls.*;
[Bindable]
public var objectProxy:ObjectProxy =
new ObjectProxy({prop1: "Prop 1", prop2: "Prop 2", prop3: "Prop 3"});
]]>
</mx:Script>
<mx:ComboBox id="cb" dataProvider="[1, 2, 3]" />
<mx:Label text="{objectProxy['prop' + cb.value]}" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</pre>
<p>
這個時候,就面臨兩難了<br/>
是要放棄好用的 Data Binding Expression? 或是對警告訊息視而無見?
</p>
<p>
解決方式 1. 利用匿名函式將關聯陣列存取運算子包起來
</p>
<pre name="code" class="mxml:showcolumns">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F0F0F0" layout="vertical">
<mx:Script>
<![CDATA[
import mx.utils.ObjectProxy;
import mx.controls.*;
[Bindable]
public var objectProxy:ObjectProxy =
new ObjectProxy({prop1: "Prop 1", prop2: "Prop 2", prop3: "Prop 3"});
]]>
</mx:Script>
<mx:ComboBox id="cb" dataProvider="[1, 2, 3]" />
<mx:Label text="{(function():*{return objectProxy['prop' + cb.value];})(objectProxy, cb.value)}" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</pre>
<p>
解決方式 2. 將關聯陣列存取運算子放到外部函式
</p>
<pre name="code" class="mxml:showcolumns">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F0F0F0" layout="vertical">
<mx:Script>
<![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];
}
]]>
</mx:Script>
<mx:ComboBox id="cb" dataProvider="[1, 2, 3]" />
<mx:Label text="{getProp(objectProxy, cb.value)}" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->
</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">
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
backgroundColor="#F0F0F0" layout="vertical">
<mx:Script>
<![CDATA[
import mx.controls.*;
[Bindable]
public var listData:Array = [];
public function doUpdateData():void{
listData = [];
for (var i:Number = 0 ; i < 16 ; ++i) {
listData.push(Math.round(Math.random() * 10000));
}
}
]]>
</mx:Script>
<mx:List id="list" width="100" height="200" dataProvider="{listData}"
selectedIndex="{list.selectedIndex &lt; 0 ? 0 : list.selectedIndex}" />
<mx:Button label="Update Data" click="doUpdateData()" />
</mx:Application>
<!-- Ticore's BLog - http://ticore.blogspot.com/ -->
</pre>
</span>