2008年2月28日 星期四

Flex 2 - 實作滑鼠可圈選的 TileList V2   [+/-]

Ticore's Blog

上次實作的可用滑鼠選擇的 TileList
由於 TileList itemRenderer 之間沒有空隙
導致滑鼠只能從邊邊開始圈選
有點不太方便

這次再繼續改造 TileList
強迫在 itemRenderer 之間拉出空隙
同時也使得計算滑鼠圈選物件方式需要調整

以下是 Flex 2 MouseSelectable TileList V2 程式碼:

/*
 MouseSelectableTileList for Flex 2
 
 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.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 != "2.0.1.0") {
    throw new Error("Super class version incompatible !");
   }
   init();
  }
  
  protected function init():void{
   mouseSelectRect = new FlexSprite();
   mouseSelectRect.name = "mouseSelectRect";
   this.addChild(mouseSelectRect);
   this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
  }
  
  //=================================================================
  //
  //=================================================================
  
  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 function onMouseDown(evtObj:MouseEvent = null):void{
   
   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);
  }
  
  
  protected function onMouseMoveHandler(evtObj:MouseEvent = null):void{
   var point:Point = new Point(evtObj.localX, evtObj.localY);
   point = (evtObj.target as DisplayObject).localToGlobal(point);
   point = mouseSelectRect.globalToLocal(point);
   updateMouseSelect(point, evtObj.ctrlKey || evtObj.shiftKey);
   mouseDragScroll();
  }
  
  
  protected function onMouseUpHandler(evtObj:MouseEvent = null):void{
   stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);
   this.removeEventListener(ScrollEvent.SCROLL, onScrollHandler);
   resetMouseDragScrolling();
   endMouseSelect();
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  
  protected function onScrollHandler(evtObj:ScrollEvent):void{
   updateMouseSelect();
  }
  
  protected function resetMouseDragScrolling():void{
   if (mouseDragScrollingInterval != 0) {
    clearInterval(mouseDragScrollingInterval);
    mouseDragScrollingInterval = 0;
   }
  }
  
  protected function mouseDragScroll():void{
   
   var slop:Number = 0;
   var scrollInterval:Number;
   var oldPosition:Number;
   var d:Number;
   var scrollEvent:ScrollEvent;
    
   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);
   }
   
  }
  
  //=================================================================
  //
  //=================================================================
  
  
  protected function beginMouseSelect(point:Point):void{
   
   this.selectedIndices = [];
   
   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, addKey:Boolean = false):void{
   
   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();
   
   var selectedIndices: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) {
      selectedIndices.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) {
      selectedIndices.push(i);
     }
    }
    
   }
   
   var xFlag:Boolean = false;
   var yFlag:Boolean = false;
   this.selectedIndices = selectedIndices;
   
  }
  
  
  protected function endMouseSelect():void{
   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 override function updateDisplayList
      (unscaledWidth:Number, unscaledHeight:Number):void{
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   this.addChild(mouseSelectRect);
   mouseSelectRect.x = listContent.x;
   mouseSelectRect.y = listContent.y;
  }
  
  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;
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  private var itemMargin:Number = 10;
  
  override protected function makeRowsAndColumns(left:Number, top:Number,
             right:Number, bottom:Number,
             firstCol:int, firstRow:int,
             byCount:Boolean = false, rowsNeeded:uint = 0):Point {
   
   var numRows:int;
   var numCols:int;
   var colNum:int;
   var rowNum:int;
   var xx:Number;
   var yy:Number;
   var data:Object;
   var rowData:ListData;
   var uid:String
   var oldItem:IListItemRenderer 
   var item:IListItemRenderer;
   var more:Boolean;
   var valid:Boolean;
   var i:int;
   var rh:Number;
 
   var bSelected:Boolean = false;
   var bHighlight:Boolean = false;
   var bCaret:Boolean = false;
   
   if (columnWidth == 0 || rowHeight == 0)
    return null;
    
   if (direction == TileBaseDirection.VERTICAL)
   {
    numRows = maxRows > 0 ? maxRows : Math.max(Math.floor(listContent.height / rowHeight), 1);
    numCols = maxColumns > 0 ? maxColumns : Math.max(Math.ceil(listContent.width / columnWidth), 1);
    setRowCount(numRows);
    setColumnCount(numCols);
    colNum = firstCol;
    xx = left;
    more = (iterator != null && !iterator.afterLast && iteratorValid);
    while (colNum < numCols)
    {
     rowNum = firstRow;
     yy = top;
     while (rowNum < numRows)
     {
      valid = more;
      data = more ? iterator.current : null;
      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;
       }
      }
 
      if (!listItems[rowNum])
       listItems[rowNum] = [];
 
      if (valid && yy < bottom)
      {
       uid = itemToUID(data);
       oldItem = listItems[rowNum][colNum];
       if (oldItem)
       {
        delete rowMap[oldItem.name];
        item = oldItem;
       }
       else
       {
         item = itemRenderer.newInstance();
         item.owner = this;
        item.styleName = listContent;
       }
       rowData = ListData(makeListData(data, uid, rowNum, colNum));
       rowMap[item.name] = rowData;
       if (item is IDropInListItemRenderer)
        IDropInListItemRenderer(item).listData = data ? rowData : null;
       if (item.data != data)
        item.data = data;
       if (oldItem == null)
        listContent.addChild(DisplayObject(item));
       item.visible = true;
       if (uid)
        visibleData[uid] = item;
       listItems[rowNum][colNum] = item;
       UIComponentGlobals.layoutManager.validateClient(item, true);
       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);
       item.move(xx + itemMargin, yy + cachedPaddingTop + itemMargin);
       bSelected = selectedData[uid] != null;
       bHighlight = highlightUID == uid;
       bCaret = caretUID == uid;
       if (uid)
        drawItem(item, bSelected, bHighlight, bCaret);
      }
      else {
       oldItem = listItems[rowNum][colNum];
       if (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++) {
       if (iterator && more) {
        try {
         more = iterator.moveNext();
        }
        catch (e2:ItemPendingError) {
         lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0);
         e2.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, 
               lastSeekPending));
         more = false;
         iteratorValid = false;
        }
       }
      }
     }
     xx += columnWidth;
    }
   } else {
    numCols = maxColumns > 0 ? maxColumns : Math.max(Math.floor(listContent.width / columnWidth), 1);
    numRows = maxRows > 0 ? maxRows : Math.max(Math.ceil(listContent.height / rowHeight), 1);
    setColumnCount(numCols);
    setRowCount(numRows);
    rowNum = firstRow;
    yy = top;
    more = (iterator != null && !iterator.afterLast && iteratorValid);
    while (rowNum < numRows) {
     colNum = firstCol;
     xx = left;
     rowInfo[rowNum] = null;
     while (colNum < numCols) {
      valid = more;
      data = more ? iterator.current : null;
      if (iterator && more) {
       try {
        more = iterator.moveNext();
       }
       catch (e3:ItemPendingError) {
        lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0);
        e3.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, 
               lastSeekPending));
        more = false;
        iteratorValid = false;
       }
      }
 
      if (!listItems[rowNum])
       listItems[rowNum] = [];
 
      if (valid && xx < right) {
       uid = itemToUID(data);
       oldItem = listItems[rowNum][colNum];
       if (oldItem) {
        delete rowMap[oldItem.name];
        item = oldItem;
       }
       else {
         item = itemRenderer.newInstance();
         item.owner = this;
        item.styleName = listContent;
       }
       rowData = ListData(makeListData(data, uid, rowNum, colNum));
       rowMap[item.name] = rowData;
       if (item is IDropInListItemRenderer)
        IDropInListItemRenderer(item).listData = data ? rowData : null;
       if (item.data != data)
        item.data = data;
       if (oldItem == null)
        listContent.addChild(DisplayObject(item));
       item.visible = true;
       if (uid)
        visibleData[uid] = item;
       listItems[rowNum][colNum] = item;
       UIComponentGlobals.layoutManager.validateClient(item, true);
       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);
       item.move(xx + itemMargin, yy + cachedPaddingTop + itemMargin);
       bSelected = selectedData[uid] != null;
       bHighlight = highlightUID == uid;
       bCaret = caretUID == uid;
       if (!rowInfo[rowNum])
        rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
       if (uid)
        drawItem(item, bSelected, bHighlight, bCaret);
      } else {
       if (!rowInfo[rowNum])
        rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
 
       oldItem = listItems[rowNum][colNum];
       if (oldItem) {
        listContent.removeChild(DisplayObject(oldItem));
        delete rowMap[oldItem.name];
        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++) {
       if (iterator && more) {
        try {
         more = iterator.moveNext();
        }
        catch (e4:ItemPendingError) {
         lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0)
         e4.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, 
               lastSeekPending));
         more = false;
         iteratorValid = false;
        }
       }
      }
     }
     yy += rowHeight;
    }
   }
 
   if (!byCount) {
    var a:Array;
    // prune excess rows and columns
    while (listItems.length > numRows) {
     a = listItems.pop();
     rowInfo.pop();
     for (i = 0; i < a.length; i++)   {
      oldItem = a[i];
      if (oldItem) {
       listContent.removeChild(DisplayObject(oldItem));
       delete rowMap[oldItem.name];
      }
     }
    }
    if (listItems.length && listItems[0].length > numCols) {
     for (i = 0; i < numRows; i++) {
      a = listItems[i];
      while (a.length > numCols) {
       oldItem = a.pop();
       if (oldItem) {
        listContent.removeChild(DisplayObject(oldItem));
        delete rowMap[oldItem.name];
       }
      }
     }
    }
   }
 
   return new Point(xx, yy);
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  
     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;
     }
  
  
  //=================================================================
  //
  //=================================================================
  
  
  override protected function drawSelectionIndicator(
    indicator:Sprite, x:Number, y:Number,
    width:Number, height:Number, color:uint,
    itemRenderer:IListItemRenderer):void {
    
         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 {
    
         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 {
    
         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;
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  
 }
}

Flex 2 MXML 範例程式:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12"
  layout="vertical" verticalAlign="middle" backgroundColor="#E0E0E0">

 <mx:Label selectable="true" fontWeight="bold"
   text="MouseSelectableTileList for Flex 2" />
 <comp:MouseSelectableTileList id="list" xmlns:comp="com.ticore.uicomponents.*"
   direction="horizontal" width="100%" height="100%"
   borderStyle="inset" borderColor="#808080" backgroundAlpha="0"
   allowMultipleSelection="true" dragEnabled="true"
   rowHeight="80" columnWidth="80"
   paddingTop="0" paddingBottom="0" paddingLeft="0" paddingRight="0">
  <comp:dataProvider>
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
  </comp:dataProvider>
  
  <comp:itemRenderer>
   <mx:Component className="MyItemRenderer">
    <mx:HBox verticalAlign="middle" horizontalAlign="center" borderStyle="none">
     <mx:Canvas width="80%" height="80%"
       borderStyle="inset" borderColor="#808080" backgroundColor="#FFFFFF">
      <mx:Label text="{data}" />
     </mx:Canvas>
    </mx:HBox>
   </mx:Component>
  </comp:itemRenderer>
 </comp:MouseSelectableTileList>
 <mx:Label selectable="true" fontWeight="bold"
  htmlText="&lt;a href='http://ticore.blogspot.com'&gt;Ticore's Blog
   http://ticore.blogspot.com&lt;/a&gt;" />
</mx:Application>

效果擷圖:

線上範例:

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

Read more...

2008年2月22日 星期五

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

Ticore's Blog

Flex Framework 中的 UI 組件都沒有提供像 Windows 那樣滑鼠圈選功能
只好自行利用 TileList 組件進行修改
必須要了解 TileList 結構

首先是滑鼠圈選矩形框,必須要放在組件內最上層
而且不可以超過 TileList 視野範圍,也不可以壓到 ScrollBar

滑鼠拖曳超出範圍時,還要自動捲動 ScrollBar

最後是最重要的選取功能,一開始本來想用碰撞測試
實際思考過之後,發現不太可行
一方面是效能會很差,另一方面是受限於 TileList ItemRenderer 實作方式
不在視野內的物件,根本不會建立相對應的 ItemRenderer

以下是 Flex 2 MouseSelectable TileList 程式碼:

/*
 MouseSelectableTileList for Flex 2
 
 Ticore Shih
 http://ticore.blogspot.com/
 
 MouseSelectableTileList Structure
  │
  └┬mouseSelectRect
   │
   └listContent
     │
     ├items
     │
     └selectionLayer
*/
package com.ticore.uicomponents {
 
 import flash.display.*;
 import flash.events.*;
 import flash.utils.*;
 import flash.geom.Point;
 
 import mx.controls.TileList;
 import mx.controls.listClasses.*;
 import mx.core.*;
 import mx.events.*;

 public class MouseSelectableTileList extends TileList {
  
  public function MouseSelectableTileList():void{
   super();
   if (mx_internal::VERSION != "2.0.1.0") {
    throw new Error("Super class version incompatible !");
   }
   init();
  }
  
  protected function init():void{
   mouseSelectRect = new FlexSprite();
   mouseSelectRect.name = "mouseSelectRect";
   this.addChild(mouseSelectRect);
   this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
  }
  
  //=================================================================
  //
  //=================================================================
  
  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 function onMouseDown(evtObj:MouseEvent = null):void{
   
   var point:Point = new Point(evtObj.localX, evtObj.localY);
   point = (evtObj.target as DisplayObject).localToGlobal(point);
   
   if (!listContent.hitTestPoint(point.x, point.y)) {
    onMouseUp();
    return;
   }
   
   // check if mouse press on items
   if (mouseEventToItemRenderer(evtObj)) {
    onMouseUp();
    return;
   }
   
   var listPoint:Point = new Point(0, 0);
   listPoint = listContent.localToGlobal(listPoint);
   
   point = mouseSelectRect.globalToLocal(point);
   stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
   stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   this.addEventListener(ScrollEvent.SCROLL, onScrollHandler);
   beginMouseSelect(point);
  }
  
  protected function onMouseMove(evtObj:MouseEvent = null):void{
   var point:Point = new Point(evtObj.localX, evtObj.localY);
   point = (evtObj.target as DisplayObject).localToGlobal(point);
   point = mouseSelectRect.globalToLocal(point);
   updateMouseSelect(point, evtObj.ctrlKey || evtObj.shiftKey);
   mouseDragScroll();
  }
  
  
  protected function onMouseUp(evtObj:MouseEvent = null):void{
   stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
   this.removeEventListener(ScrollEvent.SCROLL, onScrollHandler);
   resetMouseDragScrolling();
   endMouseSelect();
  }
  
  //=================================================================
  //
  //=================================================================
  
  protected function onScrollHandler(evtObj:ScrollEvent):void{
   updateMouseSelect();
  }
  
  protected function resetMouseDragScrolling():void{
   if (mouseDragScrollingInterval != 0) {
    clearInterval(mouseDragScrollingInterval);
    mouseDragScrollingInterval = 0;
   }
  }
  
  protected function mouseDragScroll():void{
   
   var slop:Number = 0;
   var scrollInterval:Number;
   var oldPosition:Number;
   var d:Number;
   var scrollEvent:ScrollEvent;
    
   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);
   }
   
  }
  
  //=================================================================
  //
  //=================================================================
  
  protected function beginMouseSelect(point:Point):void{
   
   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, addKey:Boolean = false):void{
   
   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();
   
   var selectedIndices: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) {
      selectedIndices.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) {
      selectedIndices.push(i);
     }
    }
    
   }
   
   var xFlag:Boolean = false;
   var yFlag:Boolean = false;
   this.selectedIndices = selectedIndices;
   
  }
  
  protected function endMouseSelect():void{
   var g:Graphics = mouseSelectRect.graphics;
   g.clear();
  }
  
  //=================================================================
  //
  //=================================================================
  
  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 override function updateDisplayList
      (unscaledWidth:Number, unscaledHeight:Number):void{
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   this.addChildAt(mouseSelectRect, this.numChildren);
   mouseSelectRect.x = listContent.x;
   mouseSelectRect.y = listContent.y;
  }
  
  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;
   indexPoint.y = Math.floor(p.y / this.rowHeight) + this.verticalScrollPosition;
   return indexPoint;
  }
  
  //=================================================================
  //
  //=================================================================
  
 }
}

Flex 2 MXML 範例程式:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" fontSize="12"
  layout="vertical" verticalAlign="middle" backgroundColor="#E0E0E0">

 <mx:Label selectable="true" fontWeight="bold"
   text="MouseSelectableTileList for Flex 2" />
 <comp:MouseSelectableTileList id="list" xmlns:comp="com.ticore.uicomponents.*"
   direction="horizontal" width="100%" height="100%"
   borderStyle="inset" borderColor="#808080" backgroundAlpha="0"
   allowMultipleSelection="true" dragEnabled="true"
   rowHeight="80" columnWidth="80"
   paddingTop="0" paddingBottom="0" paddingLeft="0" paddingRight="0">
  <comp:dataProvider>
   [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
  </comp:dataProvider>
  
  <comp:itemRenderer>
   <mx:Component className="MyItemRenderer">
    <mx:HBox verticalAlign="middle" horizontalAlign="center" borderStyle="none">
     <mx:Canvas width="80%" height="80%"
       borderStyle="inset" borderColor="#808080" backgroundColor="#FFFFFF">
      <mx:Label text="{data}" />
     </mx:Canvas>
    </mx:HBox>
   </mx:Component>
  </comp:itemRenderer>
 </comp:MouseSelectableTileList>
 <mx:Label selectable="true" fontWeight="bold"
  htmlText="&lt;a href='http://ticore.blogspot.com'&gt;Ticore's Blog
   http://ticore.blogspot.com&lt;/a&gt;" />
</mx:Application>

效果擷圖:

線上範例:

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

Read more...

2008年2月20日 星期三

AS3 - 避免影格程式變數衝突   [+/-]

ActionScript 3.0 影格程式的特性與過去有很大的改變
其中之一便是變數的生存範圍
很容易發生衝突....

先作一個簡單的測試
建立一個 AS3 fla 空白文件,影格 1 輸入程式

var i:Number = 0;

編譯之後,再利用 ActionScript Viewer 6 Alpha 3d 反編譯測試,可以得到

/*
   Ticore's Blog
   http://ticore.blogspot.com/
*/
package Main_fla {
    import flash.display.*;

    public dynamic class MainTimeline extends MovieClip {

        public var i:Number;

        public function MainTimeline(){
            addFrameScript(0, frame1);
        }
        function frame1(){
            i = 0;
        }

    }
}//package Main_fla 

可以發現影格 1 程式被宣告成為一個 Class Function
而區域變數 i 卻變成 MainTimeline 的成員變數

假如在影格 2 重複宣告一次變數 i

var i:Number = 0;

就會發生編譯錯誤了,原因是由於變數重複定義

1151: A conflict exists with definition i in namespace internal.

這在過去 AS 1.0, 2.0 是不會遇到的
AS 1.0, 2.0 可以允許區域變數重複被宣告
但是 AS 3.0 不行,非但不行,變數宣告還被拉到 Class Member Scope

大多數情況下,只是需要一個小範圍的區域變數
像是跑一個迴圈變數,跑完之後,就可以被回收
直到下一個迴圈重新宣告......
根本不會去記得變數 i 是否曾經宣告過
倘若換作 AS3 影格程式,就會變的很難撰寫程式
需要記住某某變數是否曾經宣告過,否則得到編譯錯誤
而且該變數還會污染到 Class Member Scope

變通方式 1. 將變數丟到 MovieClip Property
優點是不會被編譯成為 Class Member
缺點無法在編譯期檢查型別,Scope 還是太大

for ( this.i = 0 ; this.i < 10 ; ++ this.i ) {
 trace(this.i);
}

反編譯結果

/*
   Ticore's Blog
   http://ticore.blogspot.com/
*/
package Main_fla {
    import flash.display.*;

    public dynamic class MainTimeline extends MovieClip {

        public function MainTimeline(){
            addFrameScript(0, frame1);
        }
        function frame1(){
            this.i = 0;
            while (this.i < 10) {
                trace(this.i);
                this.i++;
            };
        }

    }
}//package Main_fla 

變通方式 2. 將變數丟到 with 程式區塊內
優點是不會被編譯成為 Class Member,也不會污染到 MovieClip Property
缺點無法在編譯期檢查型別

with ({}) {
 for ( i = 0 ; i < 10 ; ++ i ) {
  trace(i);
 }
}
trace(this.i); // undefined

反編譯結果

package Main_fla {
    import flash.display.*;

    public dynamic class MainTimeline extends MovieClip {

        public function MainTimeline(){
            addFrameScript(0, frame1);
        }
        function frame1(){
            var _local2 = {};
            with (_local2) {
                i = 0;
                while (i < 10) {
                    trace(i);
                    i++;
                };
            };
            trace(this.i);
        }

    }
}//package Main_fla 

再進一步測試,會發現變數 i 也不在 Object 內

var o:Object = {};
with (o) {
 for ( i = 0 ; i < 10 ; ++ i ) {
  trace(i);
 }
}
trace(this.i); // undefined
trace(o.i); // undefined

那變數 i 到底跑到哪去了呢?

var o:Object = {};
with (o) {
 for ( i = 0 ; i < 10 ; ++ i ) {
  trace(i);
 }
}
trace(this.i); // undefined
trace(o.i); // undefined

(function():void{
 trace(this); // [object global]
 trace(this.i); // 10
})();

答案是,跑到 global 物件去了~

假如不希望變數 i 跑到 global 物件
可以先在 Object 宣告好,再拿到 with 區塊使用

/*
   Ticore's Blog
   http://ticore.blogspot.com/
*/
var o:Object = {i: null};
// 注意,此處的變數 o 還是會編譯成 Class Member
with (o) {
 for ( i = 0 ; i < 10 ; ++ i ) {
  trace(i);
 }
}
trace(this.i); // undefined
trace(o.i); // 10

(function():void{
 trace(this); // [object global]
 trace(this.i); // undefined
})();

拿掉變數 o 實際使用版本

with ({i: null}) {
 for ( i = 0 ; i < 10 ; ++ i ) {
  trace(i);
 }
}

trace(this.i); // undefined

(function():void{
 trace(this); // [object global]
 trace(this.i); // undefined
})();
Read more...

2008年2月14日 星期四

AS3 E4X - XML 未公開的函式   [+/-]

Ticore's Blog

找資料的時候,偶然發現 XML 還有未公開的函式 XML.setNotification
目前只有在 Flex 內 mx.utils.XMLNotifier 使用
主要是用來提供 XMLListCollection 資料變動事件的功能

XML.setNotification 測試程式:

package {
 
 import flash.display.*;
 
 public class Main extends MovieClip {
  
  public function Main() {
   
   var xml:XML =
    <xml>
     <node index="0"></node>
    </xml>;
   var ns:Namespace = new Namespace("ns", "http://ticore.blogspot.com/");
   xml.setNotification(callback);
   
   
   xml.*[0].@index = 1;
   // attributeChanged
   
   xml.*[0].@attr = "attr";
   // attributeAdded
   
   delete xml.*[0].@attr;
   // attributeRemoved
   
   xml.* += <node index="2"></node>;
   // nodeChanged
   
   xml.*[2] = <node index="3"></node>;
   // nodeAdded
   
   delete xml.node[2];
   // nodeRemoved
   
   xml.addNamespace(ns);
   // namespaceAdded
   
   xml.setNamespace(ns);
   // namespaceSet
   
   xml.setNamespace(new Namespace());
   // namespaceSet
   
   xml.removeNamespace(ns);
   // namespaceRemoved
   
   xml.node[0].text = "Text";
   // textSet
   
   xml.node[0].setName("item");
   // nameSet
   
   trace(xml.toXMLString());
   
  }
  
  public function callback(targetCurrent:Object, command:String,
    target:Object, value:Object, detail:Object):void{
   
   /*/
   commands types :
   attributeAdded
   attributeChanged
   attributeRemoved
   nodeAdded
   nodeChanged
   nodeRemoved
   namespaceAdded
   namespaceRemoved
   namespaceSet
   textSet
   nameSet
   //*/
   
   trace("callback [");
   trace("\ttargetCurrent : " + targetCurrent.toXMLString());
   trace("\tcommand : " + command);
   trace("\ttarget : " + target.toXMLString());
   trace("\tvalue : " + (value is XML ? value.toXMLString() : value));
   trace("\tdetail : " + detail);
   trace("]");
   
  }
 }
}

相關連結:
Flex Internals - XML.setNotification

Read more...

2008年2月12日 星期二

2003 Flash 6 AS 1 旋轉時鐘作品   [+/-]

Ticore's Blog

好幾年前,網路上很流行 JavaScript 作的旋轉時鐘
那時候還是 Flash 6 而已,當初為了練習 ActionScript
將旋轉時鐘改成 ActionScript 版
現在分享給大家看看

ActionScript 1.0 旋轉時鐘

/*
 Ticore's Blog
 http://ticore.blogspot.com/
*/
Stage.scaleMode = "noscale";
//:::Color Setting:::
dCol = 0x333333;
fCol = 0x800000;
sCol = 0xFFAA00;
mCol = 0xCCAA00;
hCol = 0x99AA00;
shaCol = 0xA0A0A0;
//:::Clock Setting:::
ClockHeight = 30;
ClockWidth = 30;
ClockFromMouseY = 0;
ClockFromMouseX = 0;
//:::Clock Font Setting:::
textBorder = false;
textSize = 11;
fontFace = "Txt";
HandRatio = 0.2;
DateRatio = 1.5;
speed = 0.6;
//:::Date Text:::
day = ["SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY", "SATURDAY"];
month = ["JANUARY", "FEBRUARY", "MARCH", "APRIL", "MAY", "JUNE", "JULY", "AUGUST", "SEPTEMBER", "OCTOBER", "NOVEMBER", "DECEMBER"];
myDate = new Date();
year = myDate.getYear();
if (year < 2000) {
 year = year + 1900;
}
TodaysDate = " " + day[myDate.getDay()] + " " + month[myDate.getMonth()] + " " + year;
//:::Cha Array:::
D = TodaysDate.split('');
H = "‧‧‧".split('');
M = "‧‧‧‧".split('');
S = "‧‧‧‧‧".split('');
F = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
//:::End Cha Array:::
maxCounter = Math.max(F.length + S.length + M.length + H.length, D.length);
Split = 360 / F.length * Math.PI / 180;
Dsplit = 360 / D.length * Math.PI / 180;
HandHeight = ClockHeight * HandRatio;
HandWidth = ClockWidth * HandRatio;
step = 0.1;
currStep = 0;
Dy = [];
Dx = [];
Sy = [];
Sx = [];
for (i = 0; i < maxCounter; i++) {
 Dy[i] = Dx[i] = 0;
}
/*
:::Generate TextField:::
*/
myTextFormat = new TextFormat();
myTextFormat.align = "center";
myTextFormat.font = fontFace;
myTextFormat.size = textSize;
for (var i = 0; i < D.length; i++) {
 myTextFormat.color = dCol;
 var tmp = createEmptyMovieClip("ieDate" + i, i + 50);
 tmp.createTextField("fro",20,0,0,0,0);
 tmp.createTextField("sha",10,1,1,0,0);
 var tmp1 = tmp["fro"];
 var tmp2 = tmp["sha"];
 tmp1.text = tmp2.text = D[i];
 tmp1.selectable = tmp2.selectable = false;
 tmp1.autoSize = tmp2.autoSize = "center";
 tmp1.border = tmp2.border = textBorder;
 tmp1.setTextFormat(myTextFormat);
 myTextFormat.color = shaCol;
 myTextFormat.bold = true;
 tmp2.setTextFormat(myTextFormat);
}
for (var i = 0; i < F.length; i++) {
 myTextFormat.color = fCol;
 var tmp = createEmptyMovieClip("ieFace" + i, i + 100);
 tmp.createTextField("fro",20,0,0,0,0);
 tmp.createTextField("sha",10,1,1,0,0);
 var tmp1 = tmp["fro"];
 var tmp2 = tmp["sha"];
 tmp1.text = tmp2.text = F[i];
 tmp1.selectable = tmp2.selectable = false;
 tmp1.autoSize = tmp2.autoSize = "center";
 tmp1.border = tmp2.border = textBorder;
 tmp1.setTextFormat(myTextFormat);
 myTextFormat.color = shaCol;
 myTextFormat.bold = true;
 tmp2.setTextFormat(myTextFormat);
}
//myTextFormat.bold = true;
for (var i = 0; i < H.length; i++) {
 myTextFormat.color = hCol;
 var tmp = createEmptyMovieClip("ieHours" + i, i + 150);
 tmp.createTextField("fro",20,0,0,0,0);
 tmp.createTextField("sha",10,1,1,0,0);
 var tmp1 = tmp["fro"];
 var tmp2 = tmp["sha"];
 tmp1.text = tmp2.text = H[i];
 tmp1.selectable = tmp2.selectable = false;
 tmp1.autoSize = tmp2.autoSize = "center";
 tmp1.border = tmp2.border = textBorder;
 tmp1.setTextFormat(myTextFormat);
 myTextFormat.color = shaCol;
 myTextFormat.bold = true;
 tmp2.setTextFormat(myTextFormat);
}
for (var i = 0; i < M.length; i++) {
 myTextFormat.color = mCol;
 var tmp = createEmptyMovieClip("ieMinutes" + i, i + 200);
 tmp.createTextField("fro",20,0,0,0,0);
 tmp.createTextField("sha",10,1,1,0,0);
 var tmp1 = tmp["fro"];
 var tmp2 = tmp["sha"];
 tmp1.text = tmp2.text = M[i];
 tmp1.selectable = tmp2.selectable = false;
 tmp1.autoSize = tmp2.autoSize = "center";
 tmp1.border = tmp2.border = textBorder;
 tmp1.setTextFormat(myTextFormat);
 myTextFormat.color = shaCol;
 myTextFormat.bold = true;
 tmp2.setTextFormat(myTextFormat);
}
for (var i = 0; i < S.length; i++) {
 myTextFormat.color = sCol;
 var tmp = createEmptyMovieClip("ieSeconds" + i, i + 250);
 tmp.createTextField("fro",20,0,0,0,0);
 tmp.createTextField("sha",10,1,1,0,0);
 var tmp1 = tmp["fro"];
 var tmp2 = tmp["sha"];
 tmp1.text = tmp2.text = S[i];
 tmp1.selectable = tmp2.selectable = false;
 tmp1.autoSize = tmp2.autoSize = "center";
 tmp1.border = tmp2.border = textBorder;
 tmp1.setTextFormat(myTextFormat);
 myTextFormat.color = shaCol;
 myTextFormat.bold = true;
 tmp2.setTextFormat(myTextFormat);
}
/*
:::End Generate TextField:::
*/
/*
:::Clock Run Function:::
*/
ClockAndAssign = function () {
 time = new Date();
 secs = time.getSeconds();
 mins = time.getMinutes();
 hr = time.getHours();
 sec = Math.PI * (-1 / 2 + secs / 30);
 min = Math.PI * (-1 / 2 + mins / 30);
 hrs = Math.PI * (-1 / 2 + hr / 6 + parseInt(time.getMinutes()) / 360);
 for (var i = 0; i < F.length; i++) {
  var FL = this["ieFace" + i];
  FL._y = Math.round(Dy[i] + ClockHeight * Math.sin(-Math.PI / 3 + i * Split));
  FL._x = Math.round(Dx[i] + ClockWidth * Math.cos(-Math.PI / 3 + i * Split));
  FL.sha._y = -Sy[i] + 1;
  FL.sha._x = -Sx[i] - FL.sha._width / 2 + 1;
 }
 var HandHeight_sin_hrs = HandHeight * Math.sin(hrs);
 var HandWidth_cos_hrs = HandWidth * Math.cos(hrs);
 for (var j = 0; j < H.length; i++, j++) {
  var HL = this["ieHours" + j];
  HL._y = Math.round(Dy[i] + j * HandHeight_sin_hrs);
  HL._x = Math.round(Dx[i] + j * HandWidth_cos_hrs);
  HL.sha._y = -Sy[i] + 1;
  HL.sha._x = -Sx[i] - HL.sha._width / 2 + 1;
 }
 var HandHeight_sin_min = HandHeight * Math.sin(min);
 var HandWidth_cos_min = HandWidth * Math.cos(min);
 for (var j = 0; j < M.length; i++, j++) {
  var ML = this["ieMinutes" + j];
  ML._y = Math.round(Dy[i] + j * HandHeight_sin_min);
  ML._x = Math.round(Dx[i] + j * HandWidth_cos_min);
  ML.sha._y = -Sy[i] + 1;
  ML.sha._x = -Sx[i] - ML.sha._width / 2 + 1;
 }
 var HandHeight_sin_sec = HandHeight * Math.sin(sec);
 var HandWidth_cos_sec = HandWidth * Math.cos(sec);
 for (var j = 0; j < S.length; i++, j++) {
  var SL = this["ieSeconds" + j];
  SL._y = Math.round(Dy[i] + j * HandHeight_sin_sec);
  SL._x = Math.round(Dx[i] + j * HandWidth_cos_sec);
  SL.sha._y = -Sy[i] + 1;
  SL.sha._x = -Sx[i] - SL.sha._width / 2 + 1;
 }
 var ClockHeight_15 = ClockHeight * DateRatio;
 var ClockWidth_15 = ClockWidth * DateRatio;
 for (var i = 0; i < D.length; i++) {
  var DL = this["ieDate" + i];
  DL._y = Math.round(Dy[i] + ClockHeight_15 * Math.sin(currStep + i * Dsplit));
  DL._x = Math.round(Dx[i] + ClockWidth_15 * Math.cos(currStep + i * Dsplit));
  DL.sha._y = -Sy[i] + 1;
  DL.sha._x = -Sx[i] - DL.sha._width / 2 + 1;
 }
 currStep = (currStep - step) % (2 * Math.PI);
 // updateAfterEvent();
};
Delay = function () {
 Sy[0] = ymouse - Dy[0];
 Sx[0] = xmouse - Dx[0];
 Dy[0] += Sy[0] * speed;
 Dx[0] += Sx[0] * speed;
 for (i = 1; i < maxCounter; i++) {
  Sy[i] = Dy[i - 1] - Dy[i];
  Sx[i] = Dx[i - 1] - Dx[i];
  Dy[i] += Sy[i] * speed;
  Dx[i] += Sx[i] * speed;
 }
 ClockAndAssign();
};
onMouseMove = function () {
 xmouse = _xmouse - ClockFromMouseX;
 ymouse = _ymouse - ClockFromMouseY - (textSize + 4) / 2;
};
onMouseMove();
onEnterFrame = Delay;

線上範例

Read more...

2008年2月10日 星期日

自改版 dp.SyntaxHighlighter 1.5.1.1   [+/-]

Ticore's Blog

dp.SyntaxHighlighter 是一個很好用的程式碼標記工具
可以自行定義筆刷適用於不同的程式碼
不過個人覺得尺規功能與高度部分比較不合用
於是進行改版

dp.SyntaxHighlighter 1.5.1.1 新增特色:

  • 程式碼列尾不會產生多餘的空白字元
  • 可以利用 contentHeight[240px] 參數指定程式碼區塊高度
  • 列號尺規只會隨著程式碼區塊垂直捲動,固定水平位置
  • 行號尺規只會隨著程式碼區塊水平捲動,固定垂直位置

下載位置
注意,此檔案只包含修改過的 shCore.js, SyntaxHighlighter.css
其它的筆刷參考 dp.SyntaxHighlighter 原始網站
關於 AS3, MXML 筆刷參考
AS3 Syntax Highlighting
SyntaxHighlighter for MXML and AS3

程式碼標記範例:

<pre name="code" class="as3:showcolumns:contentHeight[240px]">

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
 * dp.SyntaxHighlighter
 *
 */
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package {
 import flash.display.*;
 
 public class Main extends MovieClip {
  
  public function Main() {
   var xml:XML =
    &lt;xml>
     &lt;ns1:node xmlns:ns1="http://www.ticore.com/ns1"/&gt;
     &lt;ns2:node xmlns:ns2="http://www.ticore.com/ns2"/&gt;
     &lt;ns3:node xmlns:ns3="http://www.ticore.com/ns3"/&gt;
     &lt;ns4:node xmlns:ns4="http://www.ticore.com/ns4"/&gt;
    &lt;/xml&gt;;
    trace(xml[new QName("http://www.ticore.com/ns1", "node")].toXMLString());
    trace(xml.elements(new QName("http://www.ticore.com/ns2", "node")).toXMLString());
    trace(xml.descendants(new QName("http://www.ticore.com/ns3", "node")).toXMLString());
    trace(xml.child(new QName("http://www.ticore.com/ns4", "node")).toXMLString());
  }
 
 }
}
</pre>

實際效果:


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
 * dp.SyntaxHighlighter
 *
 */
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package {
 import flash.display.*;
 
 public class Main extends MovieClip {
  
  public function Main() {
   var xml:XML =
    <xml>
     <ns1:node xmlns:ns1="http://www.ticore.com/ns1"/>
     <ns2:node xmlns:ns2="http://www.ticore.com/ns2"/>
     <ns3:node xmlns:ns3="http://www.ticore.com/ns3"/>
     <ns4:node xmlns:ns4="http://www.ticore.com/ns4"/>
    </xml>;
    trace(xml[new QName("http://www.ticore.com/ns1", "node")].toXMLString());
    trace(xml.elements(new QName("http://www.ticore.com/ns2", "node")).toXMLString());
    trace(xml.descendants(new QName("http://www.ticore.com/ns3", "node")).toXMLString());
    trace(xml.child(new QName("http://www.ticore.com/ns4", "node")).toXMLString());
  }
 
 }
}

相關連結:
含有尺規行號的 DIV 捲動區塊

Read more...

2008年2月9日 星期六

Firefox E4X Namespace Bug   [+/-]

Ticore's Blog

承上一篇 AS3 E4X - Namespace Bug,想到 Firefox 也開始支援 E4X 語法
所以順便測試看看,結果 Bug 情況幾乎一樣

Firefox XML Namespace Bug 示範:
(配合 Firebug 觀察輸出結果)

var xml = <xml/>;
var ns = new Namespace("ns", "http://ticore.blogspot.com/");

console.log(xml.toXMLString());

xml.addNamespace(ns);
console.log(xml.toXMLString());
xml.setNamespace(ns);
console.log(xml.toXMLString());

xml.removeNamespace(ns);
xml.removeNamespace(ns);
xml.removeNamespace(ns);
xml.removeNamespace(ns);
console.log(xml.toXMLString());

xml.setNamespace(new Namespace());
console.log(xml.toXMLString());
xml.removeNamespace(ns);
console.log(xml.toXMLString());
xml.removeNamespace(ns);
console.log(xml.toXMLString());

Firefox 2.0.0.11 輸出結果:

<xml/>
<xml xmlns:ns="http://ticore.blogspot.com/"/>
<ns:xml xmlns:ns="http://ticore.blogspot.com/" xmlns:ns="http://ticore.blogspot.com/"/>
<ns:xml xmlns:ns="http://ticore.blogspot.com/" xmlns:ns="http://ticore.blogspot.com/"/>
<xml xmlns:ns="http://ticore.blogspot.com/" xmlns:ns="http://ticore.blogspot.com/"/>
<xml xmlns:ns="http://ticore.blogspot.com/"/>
<xml/>

相關連結:
AS3 E4X - Namespace Bug

Read more...

AS3 E4X - Namespace Bug   [+/-]

Ticore's Blog

又是一個 ActionScript 3.0 的 Bug
對 XML 物件呼叫多次 addNamespace 與 setNamespace 方法之後
會造成無法藉由 removeNamespace 移除該 Namespace

必須要先對 XML 物件呼叫一次 setNamespace,設為其它的 Namespace 物件
之後再呼叫一到多次 removeNamespace 方可移除 Namespace 物件
而呼叫的次數決定於之前 addNamespace 與 setNamespace 執行的次數

以下是 AS3 XML Namespace Bug 示範程式:

// 建立一個 XML 與 Namespace 物件
var xml:XML = <xml />;
var ns:Namespace = new Namespace("ns", "http://ticore.blogspot.com/");

// 對 XML 物件分別呼叫 addNamespace, setNamespace 各一次
xml.addNamespace(ns);
trace(xml.toXMLString());
// <xml xmlns:ns="http://ticore.blogspot.com/"/>

xml.setNamespace(ns);
trace(xml.toXMLString());
// <ns:xml xmlns:ns="http://ticore.blogspot.com/"/>


// 此時無論呼叫 removeNamespace 幾次都無法移除 ns
xml.removeNamespace(ns);
xml.removeNamespace(ns);
xml.removeNamespace(ns);
xml.removeNamespace(ns);
xml.removeNamespace(ns);
trace(xml.toXMLString());
// <ns:xml xmlns:ns="http://ticore.blogspot.com/"/>


// 必須先用 setNamespace 設到其它的 Namespace
xml.setNamespace(new Namespace());
trace(xml.toXMLString());
// <xml xmlns:ns="http://ticore.blogspot.com/"/>

// 再呼叫兩次 removeNamespace 方可移除
// 呼叫次數會隨著之前呼叫 addNamespace, setNamespace 次數增加
xml.removeNamespace(ns);
trace(xml.toXMLString());
// <xml xmlns:ns="http://ticore.blogspot.com/"/>

xml.removeNamespace(ns);
trace(xml.toXMLString());
// <xml/>

以上的 Bug 至少會發生在以下版本的 Flash Player
Flash Player 9.0.15.0
Flash Player 9.0.16.0
Flash Player 9.0.28.0
Flash Player 9.0.45.0
Flash Player 9.0.47.0
Flash Player 9.0.60.235
Flash Player 9.0.115.0
Flash Player 9.0.124.0
Flash Player 10.0.2.54
Flash Player 10.0.12.29
Flash Player 10.0.12.36
Flash Player 10.0.22.87

相關連結:
Firefox E4X Namespace Bug
AS3 E4X - QName 相關操作

Read more...

2008年2月5日 星期二

Adobe AIR 與 Zinc 3.0 兩則比較   [+/-]

Ticore's Blog

Adobe AIR 很早就說要跨平台支援三種 OS
可是到現在已經 Beta 3 了,Linux 版本完全沒有消息
結果 Zinc 3.0 已經率先推出 Linux 版本

從資料上看,Zinc 3.0 支援功能比 AIR 多太多了
SQLite、MySQL、MSAccess、ADO 四種資料庫
RealMedia、QuickTime、MediaPlayer、PDF、Browser 混搭
Dll 擴充、Process、FileSystem
其它還有一堆....

可是這麼多的功能,主要都是在 Windows 上才有
換到的 Mac OS X 馬上縮水一半
再換到 Linux 上就已經所剩無幾了

Adobe AIR 支援的功能雖然不多
可是目前在 Windows 與 Mac OS 上來看,是非常一致的
而且這些功能與 Flash Player 整合程度很高

Flashmagazine - AIR is not a projector
Multidmedia Zinc 3.0 and Adobe AIR in review

相關連結:
AIR - 詭異的類別物件比較結果
AIR JavaScript Runtime 的物件型別
Adobe AIR 混搭測試
純程式建立 AIR AS3-JS 混搭的簡單範例
AIR - ActionScript, JavaScript 混搭技巧
AIR - JavaScript 與 ActionScript 之間物件的傳遞
AIR - 如何區分 JavaScript 與 ActionScript 物件
Adobe AIR Namespace Bug

Read more...

Flex 技巧 - 替自訂組件加上版本檢查   [+/-]

Ticore's Blog

Flex Framework 內建的組件可能無法滿足全部的需求
很多情況都會需要自行繼承修改 Flex 組件
不過隨著 Flex 2.0, 2.01, 3.0 陸續出現
自訂的組件可能會遇到版本上的衝突
以下介紹一個簡單的 Flex Framework 版本檢查方式

其實在 Flex Framework 內,每一個組件都是帶有 VERSION 的
在覆寫 Flex 組件時,只要對 Super Class 的 VERSION 作檢查即可
假如 VERSION 不吻合,便丟出 Error
如此,當 Flex SDK 改變時,強迫開發者處理版本衝突

Flex Component 版本檢查範例:

/*
 Flex Component Version Check
 
 Ticore's Blog
 http://ticore.blogspot.com/
*/
package com.ticore.uicomponents {
 
 import mx.core.mx_internal;
 import mx.controls.Button;

 public class MyButton extends Button {
  
  public function MyButton() {
   super();
   if (mx_internal::VERSION != "3.0.0.0") {
    throw new Error("Super class version incompatible !");
   }
  }
  
 }
}

或者也可以將檢查動作拉到建構子之前的靜態初始區塊執行

/*
 Flex Component Version Check
 
 Ticore's Blog
 http://ticore.blogspot.com/
*/
package com.ticore.uicomponents {
 
 import mx.core.mx_internal;
 import mx.controls.Button;

 public class MyButton extends Button {
  
  {
   if (mx_internal::VERSION != "3.0.0.0") {
    throw new Error("Super class version incompatible !");
   }
  }
  
  public function MyButton() {
   super();
  }
  
 }
}

PS. Flex 3 已經加入版本功能
可以參考 Backward compatibility
mx.core.FlexVersion class

相關連結:
Flex SDK 馬歇爾計畫

Read more...

2008年2月1日 星期五

Flash Media Server 3 線上 HTML 文件下載   [+/-]

Ticore's Blog

Flash Media Server 3 線上 HTML 文件下載

Read more...