显示具有 MXML 标签的文章。 显示所有文章
显示具有 MXML 标签的文章。 显示所有文章

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月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年5月18日 星期日

Flash Player 10 beta - MouseCursor 功能   [+/-]

Ticore's Blog

以下介绍 Flash Player 10 beta 新的 Class - MouseCursor

先利用 describeType 观察 MouseCursor 与 Mouse

import flash.ui.*;
import flash.utils.*;

trace(describeType(MouseCursor));
trace(describeType(Mouse));

可以得到以下输出结果:

<type name="flash.ui::MouseCursor" base="Class"
   isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <constant name="IBEAM" type="String"/>
  <constant name="HAND" type="String"/>
  <constant name="BUTTON" type="String"/>
  <constant name="AUTO" type="String"/>
  <constant name="ARROW" type="String"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="flash.ui::MouseCursor">
    <extendsClass type="Object"/>
  </factory>
</type>


<type name="flash.ui::Mouse" base="Class"
   isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <accessor name="cursor" access="readwrite" type="String" declaredBy="flash.ui::Mouse"/>
  <method name="hide" declaredBy="flash.ui::Mouse" returnType="void"/>
  <method name="show" declaredBy="flash.ui::Mouse" returnType="void"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="flash.ui::Mouse">
    <extendsClass type="Object"/>
  </factory>
</type>

MouseCursor 内共有五个常量 IBEAM, HAND, BUTTON, AUTO, ARROW
而刚好 Mouse 多出一个 cursor 属性
看起来就像是可以自由决定使用系统鼠标功能

实际测试之后,果然如此
可以自由设置要使用哪一个系统鼠标游标
这样就省事多了,不需要像以前那样
要先隐藏系统鼠标,还要再用一个 MovieClip 跟随鼠标~~

假如要恢复默认状态
只要重新指定为 MouseCursor.AUTO 即可

Flex MouseCursor 测试程序:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   layout="horizontal" backgroundColor="#FFFFFF">
 <mx:Script>
  <![CDATA[
   import flash.ui.Mouse;
  ]]>
 </mx:Script>
 <mx:Button label="Auto" buttonMode="true" click="Mouse.cursor = MouseCursor.AUTO;" />
 <mx:Button label="Arrow" buttonMode="true" click="Mouse.cursor = MouseCursor.ARROW;" />
 <mx:Button label="Hand" buttonMode="true" click="Mouse.cursor = MouseCursor.HAND;" />
 <mx:Button label="Button" buttonMode="true" click="Mouse.cursor = MouseCursor.BUTTON;" />
 <mx:Button label="IBEAM" buttonMode="true" click="Mouse.cursor = MouseCursor.IBEAM;" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

相关连结:
Flash Player 10 beta - 新的影格事件
Flex SDK - Targeting Flash Player 10 Beta with Flex SDK 3.0.x
Flash Player 10 beta - JPEGLoaderContext 介绍

Read more...

Flash Player 10 beta - JPEGLoaderContext 介绍   [+/-]

Ticore's Blog

最近 Flash Player 10 beta 出了
各大网站、Blog 已经有太多相关文章了
在此不再赘述

除了那些官方列出来的新功能之外
还有一些是没有被发现的
像是 flash.system::JPEGLoaderContext 类别

先来利用 describeType 观察一下 JPEGLoaderContext

import flash.system.*;
import flash.utils.*;
trace(describeType(JPEGLoaderContext));

可以得到以下输出结果:

<type name="flash.system::JPEGLoaderContext"
  base="Class" isDynamic="true" isFinal="true" isStatic="true">
  <extendsClass type="Class"/>
  <extendsClass type="Object"/>
  <accessor name="prototype" access="readonly" type="*" declaredBy="Class"/>
  <factory type="flash.system::JPEGLoaderContext">
    <extendsClass type="flash.system::LoaderContext"/>
    <extendsClass type="Object"/>
    <constructor>
      <parameter index="1" type="Number" optional="true"/>
      <parameter index="2" type="Boolean" optional="true"/>
      <parameter index="3" type="flash.system::ApplicationDomain" optional="true"/>
      <parameter index="4" type="flash.system::SecurityDomain" optional="true"/>
    </constructor>
    <variable name="deblockingFilter" type="Number"/>
    <variable name="applicationDomain" type="flash.system::ApplicationDomain"/>
    <variable name="securityDomain" type="flash.system::SecurityDomain"/>
    <variable name="checkPolicyFile" type="Boolean"/>
  </factory>
</type>

从名称上看起来,用途应该是与 LoaderContext 类似的
然后是用来处理外部 JPEG 图档
只是多了一个 deblockingFilter 变量
看起来也很像是去马赛克用的

以下便是实际使用示范:(需要自行准备一个外部图档 img.jpg)

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  initialize="init();" layout="horizontal" backgroundColor="#FFFFFF">
 <mx:Script>
  <![CDATA[
   import flash.system.*;
   import flash.utils.*;
   
   [Bindable]
   public var jpgLdrCxt1:JPEGLoaderContext = new JPEGLoaderContext(0);
   public var jpgLdrCxt2:JPEGLoaderContext = new JPEGLoaderContext(1);
   
   public function init():void{
    
    trace(describeType(JPEGLoaderContext));
    
    img1.loaderContext = jpgLdrCxt1;
    img1.source = "img.jpg";
    img2.loaderContext = jpgLdrCxt2;
    img2.source = "img.jpg";
   }
  ]]>
 </mx:Script>
 <mx:Image id="img1" scaleX="1.5" scaleY="1.5" />
 <mx:Image id="img2" scaleX="1.5" scaleY="1.5" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

以下是实际用 Flash Player 10.0.1.218 运行的效果

可以明显看得出来其中的差异
deblockingFilter 值大约是在 0-1 之间
超过 1 以上就没有太大的差异了
假如再加上 Bitmap.smoothing 会得到更好的效果

相关连结:
Flash Player 10 beta - 新的影格事件
Flex SDK - Targeting Flash Player 10 Beta with Flex SDK 3.0.x
Flash Player 10 beta - MouseCursor 功能

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:

相关连结: