顯示具有 Flex 標籤的文章。 顯示所有文章
顯示具有 Flex 標籤的文章。 顯示所有文章

2008年9月15日 星期一

Flash CS3 Compiler Bug   [+/-]

Ticore's Blog

這個問題其實去年就遇到了,只是最近又有人遇到類似的問題
再仔細探討發生原因,順便做的紀錄

Bug 重現步驟如下:

  1. 使用 Flash CS3 建立一空白的 fla (as3) 文件
  2. 與 Cairngorm.swc (Cairngorm 2.2.1 or 2.2) 放在同一個資料夾下
  3. 影格一寫一行 trace(123); 程式
  4. 編譯測試影片就會得到以下的錯誤訊息
1046: Type was not found or was not a compile-time constant: WebService.
1046: Type was not found or was not a compile-time constant: RemoteObject.
1046: Type was not found or was not a compile-time constant: HTTPService.

這個實在蠻詭異的,fla 文件內完全沒有用到 Cairngorm 的類別
甚至連 ActionScript Classpath 都沒設
Flash IDE 就自動去找到 Cairngorm.swc
然後自己掛在那邊~~

Read more...

2008年9月4日 星期四

Flex 技巧 - 將 NumericStepper 上下按鈕變灰   [+/-]

Ticore's Blog

Flex 內的 NumericStepper 組件,在遇到 Value 等於 Maximun or Minimum 時
NextButton, PrevButton 仍然是 Enable 狀態
這樣很容易讓使用者混淆,多按了好幾下才知道已經到底了
以下分享一個簡單的做法,可以讓 NumericStepper 到底時
自動將 NextButton, PrevButton Disable

MXML Code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   layout="horizontal" verticalAlign="middle" backgroundColor="#FFFFFF" fontSize="12">
 <mx:Script>
  <![CDATA[
   import mx.core.mx_internal;
   import mx.controls.NumericStepper;
  ]]>
 </mx:Script>
 <mx:Label text="Before Disable Btn:" />
 <mx:NumericStepper />
 
 <mx:VRule height="100%" />
 
 <mx:Label text="After Disable Btn:" />
 <mx:NumericStepper>
  <mx:creationComplete>
   <![CDATA[
    var target:NumericStepper = event.target as NumericStepper;
    target.mx_internal::nextButton.enabled = target.value < target.maximum;
    target.mx_internal::prevButton.enabled = target.value > target.minimum;
   ]]>
  </mx:creationComplete>
  <mx:change>
   <![CDATA[
    var target:NumericStepper = event.target as NumericStepper;
    target.mx_internal::nextButton.enabled = target.value < target.maximum;
    target.mx_internal::prevButton.enabled = target.value > target.minimum;
   ]]>
  </mx:change>
 </mx:NumericStepper>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

線上範例:

Read more...

2008年6月27日 星期五

Flex 技巧 - 將資料綁定封裝起來   [+/-]

Ticore's Blog

之前介紹了 純手工設定 Flex DataBinding 的方式
不過那挺麻煩的
假如想要將 DataBinding 封裝起來,保留部分彈性
又不想要那麼麻煩的設定方式
不妨可以試試看以下的方式

在這個例子中,完全的將 DataBinding 封裝在一個 MXML Component 中
必須要指定好目標物,Component 內的 DataBinding 才會發生作用
想要停止 DataBinding 也很簡單,只要將目標屬性設為 null 就好

甚至可以對一份資料,準備多個 DataBinding Component
只要在執行期動態替換 Component,就能達到切換 DataBinding 行為的目的
其實還挺方便的

Main.mxml:

<?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/ -->

BindingComp.mxml:

<?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/ -->

線上測試範例:

相關連結:
Flex - 純手工設定 DataBinding 的方式
Flex 技巧 - BindingManager 使用方式
Flex 技巧 - 觀察 Data Binding 資料變化
Flex Tip - 在 Data Binding 內使用 [...] 運算子
Flex 2 Bindable Metadata Tag 背後實際作用
Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding

Read more...

2008年6月18日 星期三

Flex SDK 馬歇爾計畫   [+/-]

Ticore's Blog

Marshall Plan 原文有點長,主要只有兩件事情:

  • 不同版本交互支援 (Cross-Versioning):
    不同版本 Flex 編譯的 SWFs 可以被放在相同的 SecurityDomain 執行
    但是卻可以有不同的 ApplicationDomain

  • 不信任的應用程式支援 (Untrusted Application):
    被載入到不同 SecurityDomain 的 SWFs 將不能存取主應用程式或是 Stage 與其它受限的資源

這個計畫將可能會在 Flex 3.1 開始支援
未來不同版本的 Flex 應用程式可以做混搭了
個人猜測,這計畫背後更重要的意義是 Flash 與 Flex 應用程式混搭

不過在那之前,Adobe 可能要先把 ApplicationDomain 的 Bug 處理掉吧~

Read more...

2008年6月14日 星期六

AS 技巧 - Layer BlendMode 的用處   [+/-]

Ticore's Blog

Flash Player 8 新增的混合模式功能中的圖層模式 (Layer)
一般使用起來或許覺得沒有什麼特別的用處
說明文件上寫得更是令人霧茫茫
簡單的說,只是讓 DisplayObject 子物件預先混和顏色而已

基本上,除了 Normal 之外的混合模式,都會強迫預先混和子物件顏色
但是其它混合模式都是有特殊效果的
假如不需要那些效果,但是又要強迫預先混合顏色時
就要用 Layer 混合模式了

對於像是 Flex 這樣由大量的 DisplayObject 組合而成的組件
遇到需要淡入、淡出效果時
即使是在最外層設定 Alpha 透明度
每個子物件仍會先被單獨套用 Alpha 效果再疊合成一張圖
這樣就會有某些位置顏色特別突兀不透明

此時 Layer 混合模式就非常好用了
它可以讓整個表單先疊合成一張圖再進行 Alpha 效果
可以確保組件顏色看起來不會特別突兀

以下是用 Flex 作的簡單測試
可以容易觀察到多層巢狀組件在不同 Alpha, BlendMode 的效果

<?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/ -->

線上測示範例:

相關連結:
Flex Label, TextField 半透明小技巧
Flash 8 半透明輸入、動態文字欄位

Read more...

2008年6月10日 星期二

Flex - 純手工設定 DataBinding 的方式   [+/-]

Ticore's Blog

Flex 的 DataBinding Expression 功能雖然非常方便
不用自行呼叫 addEventListener,省去了不少程式碼
但是換個角度看,Flex DataBinding 其實是有點缺乏彈性
不用自行加入監聽事件,同時也意味著不能移除監聽事件

然而,自行利用 addEventListener 方式實作的 DataBinding
感覺好像又沒有 Flex Compiler 產生的好~
於是想要觀察 Flex Compiler 產生的程式碼
進而自行模仿實作 DataBinding

想要觀察 Flex Compiler 產生的 ActionScript 很簡單
只要加入編譯參數 -keep-generated-actionscript=true
就可以在 /src/generated 下找到了

舉例來說,想要模擬以下的 DataBinding Expression:

<?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/ -->

步驟有點多~~

  1. 實作 mx.binding.IBindingClient; 介面

  2. 匯入必要的 Class:

    import mx.binding.*;
    import mx.binding.utils.*;
    import mx.core.mx_internal;
    

  3. 宣告 Bindings, Watchers 等屬性:

    mx_internal var _bindings : Array = [];
    mx_internal var _watchers : Array = [];
    mx_internal var _bindingsByDestination : Object = {};
    mx_internal var _bindingsBeginWithWord : Object = {};
    

  4. 建立 Bindings 與 Watchers:

    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();
    

最後一個步驟看起來就有點複雜了
Binding 的功能有點類似 Event Handler,負責執行 DataBinding 運算
而 Watcher 則是類似 Event Listener,負責監聽資料來源的變化
為什麼不用標準的 Event Listener 機制
看 Flex Source 上寫的是因為效能考量

先看一下 mx.binding.Binding 類別的使用方式
public function Binding(document:Object, srcFunc:Function, destFunc:Function, destString:String)

  • document: binding 目標的文件
  • srcFunc: 用來取值的函式
  • destFunc: 將值指定到目的地的函式
  • destString: 用來告訴 ValidationManager 驗證該欄位

至於 Watcher 其實只是一個上層類別
實際使用時,需要視 Binding Source 種類決定使用哪一種子 Watcher
XMLWatcher, PropertyWatcher, StaticPropertyWatcher, RepeaterItemWatcher, RepeaterComponentWatcher, FunctionReturnWatcher, ArrayElementWatcher

Watcher 有一個最特別的地方是,它具有父子關係
上層的父 Watcher 會觸發下層的子 Watcher 物件
可以藉由以下 Watcher 函式設定:

public function updateParent(parent:Object):void;
public function addChild(child:Watcher):void;
public function removeChildren(startingIndex:int):void;
public function updateChildren():void;

綜合上述的步驟
完整手工設定的 DataBinding MXML 程式碼如下:

<?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/ -->

相關連結:
Flex 技巧 - 將資料綁定封裝起來
Flex 技巧 - BindingManager 使用方式
Flex 技巧 - 觀察 Data Binding 資料變化
Flex Tip - 在 Data Binding 內使用 [...] 運算子
Flex 2 Bindable Metadata Tag 背後實際作用
Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding
Soph-Ware Associates Blog - Data Binding in Flex, Part I
Soph-Ware Associates Blog - Data Binding in Flex, Part II

Read more...

2008年6月8日 星期日

Flex 技巧 - BindingManager 使用方式   [+/-]

Ticore's Blog

Flex 的 mx.binding.*; 內有許多未公開的類別
主要都是給 MXML Compiler 使用的
其中 mx.binding.BindingManager 便是負責管理所有的 DataBinding 運作
BindingManager 內有一些靜態函式還蠻有用的
列舉如下:

  • BindingManager.setEnabled(document:Object, isEnabled:Boolean):void;
    用來 停止/啟動 Flex Application/Component 內 Data Binding

  • BindingManager.executeBindings(document:Object, destStr:String, destObj:Object):void;
    執行被指定 Data Binding Expression

  • BindingManager.debugBinding(destinationString:String):void;
    對指定的 Data Binding Expression 進行除錯

以下是簡單的 BindingManager 使用示範程式:

<?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/ -->

測試輸出結果:

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

相關連結:
Flex 技巧 - 將資料綁定封裝起來
Flex - 純手工設定 DataBinding 的方式
Flex 技巧 - 觀察 Data Binding 資料變化
Flex Tip - 在 Data Binding 內使用 [...] 運算子
Flex 2 Bindable Metadata Tag 背後實際作用
Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding

Read more...

2008年6月6日 星期五

Flex 技巧 - 觀察 Data Binding 資料變化   [+/-]

Ticore's Blog

Flex Data Binding 功能讓開發者不用寫太多的程式碼
就能做到資料繫結功能
但是要除錯的時候就比較不太方便
Data Binding Expression 區塊內很多語法都不能使用
當 Data Binding 被觸發時,想要觀察資料變化就不太方便

以下分享一個小技巧,可以很簡單的觀察 Data Binding 前後資料的變化

<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/ -->

由於 Data Binding Expression 內不能使用 ";" 結尾的陳述式
所以使用 "," 作區隔,只有最後一個 Expression 會被當作值指定給目標
這樣在前面就可以 trace 到變化之前的值
配合 setTimeout 又可以觀察到變化之後的值

相關連結:
Flex 技巧 - 將資料綁定封裝起來
Flex - 純手工設定 DataBinding 的方式
Flex 技巧 - BindingManager 使用方式
Flex Tip - 在 Data Binding 內使用 [...] 運算子
Flex 2 Bindable Metadata Tag 背後實際作用
Flex 2.0 - 以 ActionScript 3.0 動態設置 Data Binding

Read more...

2008年5月28日 星期三

Flex - RemoteObject 與 ApplicationDomain 問題   [+/-]

Ticore's Blog

問題出處:SWFLoader載入問題,當要載入的swf有使用DataServices...
在 Flex App 內,以新的 ApplicationDomain 載入另一個 Flex App 之後
被讀入的 Flex App 使用 RemoteObject 呼叫會出現問題

重新把問題程式碼簡化如下
因為這問題發生於 HTTP 請求之前,不必配置後端

LoadeeApp.mxml:

<?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>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

LoaderApp.mxml:

<?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>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

編譯之後,執行 LoaderApp,按下按鈕就會得到錯誤訊息

ArgumentError: Error #1063:
 Object/http://adobe.com/AS3/2006/builtin::hasOwnProperty()
 上的引數個數不相符。需要 0 個,目前為 2 個。
 at LoadeeApp/___LoadeeApp_Button1_click()

假如改成 remoteObj.test(); 還會得到堆疊溢位錯誤呢

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()
 ...

從錯誤訊息看起來,其實與 Flex 無關
問題很可能是出在 Flash ActionScript 3.0 內建 Class 上
由於 RemoteObject 是繼承 Proxy
再將問題簡化,剔除 Flex 相關的因素
僅使用 Flash ActionScript 3.0 以 new ApplicationDomain(); 載入另一個 Flash App
於被載入的 Flash App 呼叫自訂的 MyProxy 方法

LoaderFlash Class:

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);
  }
 }
}
// Ticore's Blog - http://ticore.blogspot.com/

LoadeeFlash Class:

package {
 import flash.display.Sprite;

 public class LoadeeFlash extends Sprite {
  public function LoadeeFlash() {
   var proxy:MyProxy = new MyProxy();
   proxy.test(123);
   //proxy.prop++;
  }
 }
}
// Ticore's Blog - http://ticore.blogspot.com/

MyProxy Class:

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;
  }
 }
}
// Ticore's Blog - http://ticore.blogspot.com/

下載測試程式碼

執行 LoaderFlash.swf,結果還是會得到一樣的錯誤訊息
所以 flash.utils.Proxy 無法在 new ApplicationDomain(); 方式載入的 SWF 內使用

變通方式,大概只能避免使用到 Proxy 的功能了
改以 NetConnection 等比較低階的方式使用 Remoting 功能

以上的 Bug 至少會發生在以下版本的 Flash Player
Flash Player 9.0.115.0
Flash Player 9.0.124.0
Flash Player 10.0.0.525
Flash Player 10.0.1.218
Flash Player 10.0.12.10

Read more...

2008年4月21日 星期一

讓 Flex 內不可選擇的文字超連結生效   [+/-]

Ticore's Blog

在 Flash 內,不可選擇的 (unselectable) TextField 仍可保留 HTML 超連結功能
但是 Flex 卻不行
查文件上也有寫到 Label.selectable

其實不光是 Label, Text 組件不行
任何一個在 Flex App 下的 unselectable TextField 超連結都會失效
這樣需要用到不可選擇的超連結文字時就很不方便

Flex 超連結失效測試程式:

<?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>

於是花了不少力氣去追蹤原因
終於發現是 Flex 內的 FocusManager 刻意攔截下 unselectable TextField Focus 事件
這也間接造成超連結失效

既然知道問題是出在 FocusManager 上
問題就比較好處理了
以下是變通方式,讓 FocusManager 短暫失效一下~

<?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>

Online Demo:

Read more...

2008年4月11日 星期五

Flex 3 - 實作滑鼠可圈選的 TileList V3   [+/-]

Ticore's Blog

Mouse Selectable TileList V3
這次改版更新到 Flex 3
改善滑鼠圈選時效能
最主要的是加入 Shift/Ctrl 功能鍵

Flex 3 MouseSelectable TileList V3:

/*
 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;
  }
  
 }
}

Online Demo:

相關連結:
Flex 2 - 實作滑鼠可圈選的 TileList
Flex 2 - 實作滑鼠可圈選的 TileList V2

Read more...

2008年4月6日 星期日

Flex - Daily Task View by Constraint Layout   [+/-]

Ticore's Blog

利用 Flex 3 新功能 Constraint Layout 實作 Daily Task View
之前曾經考慮過使用 DataGrid, TileList 等組件
但是都不是很適合
主要是因為 Daily Task View Column 數量不固定並且需要跨列

後來發現 Flex 3 Constraint Layout 頗適合
因為 Constraint Column, Row 都不是真的建立 Column 或是 Row
只是建立類似參考位置的資料
所以不會消耗太多效能
而且 Constraint Layout 可以隨意的跨行、跨列

以下是簡單實作 Daily Task View 例子:

<?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/ -->

Online Demo:

相關連結:
Flex Button with Constraint Layout

Read more...

2008年4月5日 星期六

Flex Collapsible DataGrid   [+/-]

Ticore's Blog

Flex 3 AdvancedDataGrid 具有 Tree 與 跨欄 (columnSpan) 的功能
以下利用這兩種特性做出可收合的 DataGrid

MyDataGridGroupItemRenderer Class:

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);
  }
  
 }
}

Flex MXML Code:

<?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/ -->

Online Demo:

Read more...