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

相关连结:
Firefox E4X Namespace Bug
AS3 E4X - QName 相关操作

Read more...

2008年2月5日 星期二

Aode 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

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