2008年4月22日 星期二

Google Notebook 扩充套件造成 CPU 满载 Bug   [+/-]

Ticore's Blog

最近在使用 Google Notebook Extension for Firefox 时
发现它有一个很严重的 Bug
只要用装上 Google Notebook Extension 的 Firefox
浏览到网页内含有数百个这样的 HTML 标签 <li><br/></li>
全选之后,取消选择
然后 Firefox 就会狂吃 CPU 效能
几秒钟之后跳出对话框,问你是否要停止运行 JavaScript

Firefox version 2.0.0.14
Google Notebook Extension version 1.0.0.19

HTML 网页测试码如下:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Google Notebook Extension Scripting Timeout</title>
</head>
<body>
<script type="text/javascript">
<!--
var olTag = "<ol>";
for (var i = 0 ; i < 800 ; ++i) {
 olTag += "<li><br/></li>";
}
olTag += "</ol>";
document.write(olTag);
//-->
</script>
</body>
</html>

测试网页

Read more...

2008年4月21日 星期一

让 Flex 内不可选择的文字超连结生效   [+/-]

Ticore's Blog

在 Flash 内,不可选择的 (unselectable) TextField 仍可保留 HTML 超连结功能
但是 Flex 却不行
查文档上也有写到 Label.selectable

其实不光是 Label, Text 组件不行
任何一个在 Flex App 下的 unselectable TextField 超连结都会失效
这样需要用到不可选择的超连结文字时就很不方便

Flex 超连结失效测试程序:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   fontSize="12" backgroundColor="#F0F0F0">
 <mx:Script>
  <![CDATA[
   import flash.events.*;
   import mx.managers.FocusManager;
   
   public function onTxtLink(evtObj:Event):void{
    textArea.text += evtObj + "\n";
   }
  ]]>
 </mx:Script>
 <mx:Label selectable="false" link="onTxtLink(event)">
  <mx:htmlText>
   <![CDATA[Flex Label : <a href='event:linkEvent'>Link Event Text</a> | ]]>
   <![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
  </mx:htmlText>
 </mx:Label>
 
 <mx:Text selectable="false" link="onTxtLink(event)">
  <mx:htmlText>
   <![CDATA[Flex Text : <a href='event:linkEvent'>Link Event Text</a> | ]]>
   <![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
  </mx:htmlText>
 </mx:Text>
 
 <mx:Button label="Clear Log" click="textArea.text = '';" />
 <mx:TextArea id="textArea" width="100%" height="100%" />
</mx:Application>

于是花了不少力气去追踪原因
终于发现是 Flex 内的 FocusManager 刻意拦截下 unselectable TextField Focus 事件
这也间接造成超连结失效

既然知道问题是出在 FocusManager 上
问题就比较好处理了
以下是变通方式,让 FocusManager 短暂失效一下~

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   fontSize="12" backgroundColor="#F0F0F0">
 <mx:Script>
  <![CDATA[
   import flash.events.*;
   import mx.managers.FocusManager;
   
   public function onTxtLink(evtObj:Event):void{
    textArea.text += evtObj + "\n";
   }
  ]]>
 </mx:Script>
 <mx:Label selectable="false" link="onTxtLink(event)"
   rollOver="focusManager.deactivate()" rollOut="focusManager.activate()">
  <mx:htmlText>
   <![CDATA[Flex Label : <a href='event:linkEvent'>Link Event Text</a> | ]]>
   <![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
  </mx:htmlText>
 </mx:Label>
 
 <mx:Text selectable="false" link="onTxtLink(event)"
   rollOver="focusManager.deactivate()" rollOut="focusManager.activate()">
  <mx:htmlText>
   <![CDATA[Flex Text : <a href='event:linkEvent'>Link Event Text</a> | ]]>
   <![CDATA[<a href='http://ticore.blogspot.com' target='_blank'>Ticore's Blog</a>]]>
  </mx:htmlText>
 </mx:Text>
 
 <mx:Button label="Clear Log" click="textArea.text = '';" />
 <mx:TextArea id="textArea" width="100%" height="100%" />
</mx:Application>

Online Demo:

Read more...

2008年4月20日 星期日

ActionScript width, height pitfall   [+/-]

Ticore's Blog

Flash 的图像对象尺寸指定上,有一个很容易犯错的陷阱
举例来说,以下的 AS3 程序会动态创建黑色矩形 MovieClip
但是实际运行,却没有办法看到任何东西

import flash.display.MovieClip;

var mc:MovieClip = new MovieClip();
this.addChild(mc);

mc.width = mc.width;

with (mc.graphics) {
 beginFill(0, 0.5);
 drawRect(0, 0, 100, 100);
 endFill();
}

mc.scaleX = 1;

只要在最后重新设置一次 scaleY 为 1 就会正常了

import flash.display.MovieClip;

var mc:MovieClip = new MovieClip();
this.addChild(mc);

mc.width = mc.width;

with (mc.graphics) {
 beginFill(0, 0.5);
 drawRect(0, 0, 100, 100);
 endFill();
}

mc.scaleX = 1;
mc.scaleY = 1;

主要原因可能是在 MovieClip 内没有任何图像时
指定其 width 为 0,会同时导致 scaleX, scaleY 变成 0~

Read more...

2008年4月11日 星期五

Flex 3 - 实作鼠标可圈选的 TileList V3   [+/-]

Ticore's Blog

Mouse Selectable TileList V3
这次改版更新到 Flex 3
改善鼠标圈选时效能
最主要的是添加 Shift/Ctrl 功能键

Flex 3 MouseSelectable TileList V3:

/*
 MouseSelectableTileList V3 for Flex 3
 
 Ticore Shih
 http://ticore.blogspot.com/
 
 MouseSelectableTileList Structure
   │
   └─┬mouseSelectRect
     │
     └listContent
       │
       ├items
       │
       └selectionLayer
*/
package com.ticore.uicomponents {

 import flash.display.*;
 import flash.events.*;
 import flash.geom.Point;
 import flash.utils.*;
 
 import mx.collections.CursorBookmark;
 import mx.collections.ItemResponder;
 import mx.collections.ItemWrapper;
 import mx.collections.ModifiedCollectionView;
 import mx.collections.errors.ItemPendingError;
 import mx.controls.TileList;
 import mx.controls.listClasses.*;
 import mx.core.*;
 import mx.events.*;

 use namespace mx_internal;

 public class MouseSelectableTileList extends TileList {
  
  public function MouseSelectableTileList():void{
   super();
   if (VERSION != "3.0.0.0") {
    throw new Error("Super class version incompatible !");
   }
   init();
  }
  
  protected function init():void{
   this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
   
   this.addEventListener(KeyboardEvent.KEY_DOWN, onKeyboardEvent);
   this.addEventListener(KeyboardEvent.KEY_UP, onKeyboardEvent);
  }
  
  
  protected function onKeyboardEvent(evtObj:KeyboardEvent):void{
   this.ctrlKey = evtObj.ctrlKey;
   this.shiftKey = evtObj.shiftKey;
  }
  
  
  override protected function createChildren():void{
   super.createChildren();
   if (!mouseSelectRect) {
    mouseSelectRect = new FlexSprite();
    mouseSelectRect.name = "mouseSelectRect";
    this.addChild(mouseSelectRect);
   }
  }
  
  //=================================================================
  // Protected Properties
  //=================================================================
  
  [Bindable]
  public var itemMargin:Number = 10;
  
  //=================================================================
  // Protected Properties
  //=================================================================
  
  
  private var mouseDragScrollingInterval:int = 0;
  
  protected var mouseSelectRect:FlexSprite;
  
  protected var startPoint:Point;
  protected var endPoint:Point;
  
  protected var startIndexPoint:Point;
  protected var endIndexPoint:Point;
  
  protected var ctrlKey:Boolean = false;
  protected var shiftKey:Boolean = false;
  protected var oldSelectedIndices:Array;
  
  
  //=================================================================
  // Mouse Event Handlers
  //=================================================================
  
  protected function onMouseDown(evtObj:MouseEvent = null):void{
   //trace("onMouseDown();");
   
   var point:Point = new Point(evtObj.localX, evtObj.localY);
   point = (evtObj.target as DisplayObject).localToGlobal(point);
   
   if (!listContent.hitTestPoint(point.x, point.y)) {
    onMouseUpHandler();
    return;
   }
   
   // check if mouse press on items
   if (mouseEventToItemRenderer(evtObj)) {
    onMouseUpHandler();
    return;
   }
   
   var listPoint:Point = new Point(0, 0);
   listPoint = listContent.localToGlobal(listPoint);
   
   point = mouseSelectRect.globalToLocal(point);
   stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
   stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);
   this.addEventListener(ScrollEvent.SCROLL, onScrollHandler);
   beginMouseSelect(point);
   //dragScroll();
  }
  
  
  protected function onMouseMoveHandler(evtObj:MouseEvent = null):void{
   //trace("onMouseMoveHandler();");
   var point:Point = new Point(evtObj.localX, evtObj.localY);
   point = (evtObj.target as DisplayObject).localToGlobal(point);
   point = mouseSelectRect.globalToLocal(point);
   updateMouseSelect(point);
   //dragScroll();
   mouseDragScroll();
  }
  
  
  protected function onMouseUpHandler(evtObj:MouseEvent = null):void{
   //trace("onMouseUpHandler();");
   stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpHandler);
   stage.removeEventListener(MouseEvent.MOUSE_MOVE, onMouseMoveHandler);
   this.removeEventListener(ScrollEvent.SCROLL, onScrollHandler);
   resetMouseDragScrolling();
   endMouseSelect();
  }
  
  
  //=================================================================
  // Mouse Drag Scroll
  //=================================================================
  
  protected function onScrollHandler(evtObj:ScrollEvent):void{
   //trace("onScrollHandler();");
   updateMouseSelect();
  }
  
  protected function resetMouseDragScrolling():void{
   if (mouseDragScrollingInterval != 0) {
    clearInterval(mouseDragScrollingInterval);
    mouseDragScrollingInterval = 0;
   }
  }
  
  protected function mouseDragScroll():void{
   //trace("mouseDragScroll();");
   // x 方向快速卷动,会造成 item 显示不正常
   
   var slop:Number = 0;
   var scrollInterval:Number;
   var oldPosition:Number;
   var d:Number;
   var scrollEvent:ScrollEvent;
   
   // if (mouseDragScrollingInterval == 0) return;
    
   const minScrollInterval:Number = 30;
   
   clearInterval(mouseDragScrollingInterval);
   
   if (mouseY < slop) {
    oldPosition = verticalScrollPosition;
    verticalScrollPosition = Math.max(0, oldPosition - 1);
    d = Math.min(0 - mouseY - 30, 0);
    scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
    
    mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval);
    
    if (oldPosition != verticalScrollPosition) {
     scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
     scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
     scrollEvent.direction = ScrollEventDirection.VERTICAL;
     scrollEvent.position = verticalScrollPosition;
     scrollEvent.delta = verticalScrollPosition - oldPosition;
     dispatchEvent(scrollEvent);
    }
    
   } else if (mouseY > (unscaledHeight - slop)) {
    
    oldPosition = verticalScrollPosition;
    verticalScrollPosition = Math.min(maxVerticalScrollPosition, verticalScrollPosition + 1);
    
    d = Math.min(mouseY - unscaledHeight - 30, 0);
    scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
    
    mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval);
    
    if (oldPosition != verticalScrollPosition) {
     scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
     scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
     scrollEvent.direction = ScrollEventDirection.VERTICAL;
     scrollEvent.position = verticalScrollPosition;
     scrollEvent.delta = verticalScrollPosition - oldPosition;
     dispatchEvent(scrollEvent);
    }
    
   } else if (mouseX < slop) {
    oldPosition = horizontalScrollPosition;
    horizontalScrollPosition = Math.max(0, oldPosition - 1);
    d = Math.min(0 - mouseX - 30, 0);
    scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
    
    mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval);
    
    if (oldPosition != horizontalScrollPosition) {
     scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
     scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
     scrollEvent.direction = ScrollEventDirection.HORIZONTAL;
     scrollEvent.position = horizontalScrollPosition;
     scrollEvent.delta = horizontalScrollPosition - oldPosition;
     dispatchEvent(scrollEvent);
    }
    
   } else if (mouseX > (unscaledWidth - slop)) {
    
    oldPosition = horizontalScrollPosition;
    horizontalScrollPosition = Math.min(maxHorizontalScrollPosition, horizontalScrollPosition + 1);
    
    d = Math.min(mouseX - unscaledWidth - 30, 0);
    scrollInterval = 0.593 * d * d + 1 + minScrollInterval;
    
    mouseDragScrollingInterval = setInterval(mouseDragScroll, scrollInterval);
    
    if (oldPosition != horizontalScrollPosition) {
     scrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
     scrollEvent.detail = ScrollEventDetail.THUMB_POSITION;
     scrollEvent.direction = ScrollEventDirection.HORIZONTAL;
     scrollEvent.position = horizontalScrollPosition;
     scrollEvent.delta = horizontalScrollPosition - oldPosition;
     dispatchEvent(scrollEvent);
    }
    
   } else {
    mouseDragScrollingInterval = setInterval(mouseDragScroll, 15);
   }
   
  }
  
  
  /*//
  // override ListBase.dragScroll()
  protected override function dragScroll():void {
   trace("dragScroll();");
   trace("DragManager.isDragging : " + DragManager.isDragging);
   super.dragScroll();
   
   //trace("collection : " + collection);
   //trace((mouseY > unscaledHeight));
   
   var slop:Number = 0;
   
  }
  //*/
  
  /*/
  protected override function mouseMoveHandler(event:MouseEvent):void{
   trace("mouseMoveHandler();");
  }
  //*/
  
  
  //=================================================================
  //
  //=================================================================
  
  protected function beginMouseSelect(point:Point):void{
   //trace("beginMouseSelect();", point);
   //adjustPoint(point);
   
   
   if (ctrlKey || shiftKey) {
    this.oldSelectedIndices = [];
   } else {
    this.selectedIndices = [];
    this.oldSelectedIndices = [];
   }
   
   endIndexPoint = startIndexPoint = pointToIndex(point);
   
   point.x += this.horizontalScrollPosition * this.columnWidth;
   point.y += this.verticalScrollPosition * this.rowHeight;
   
   endPoint = startPoint = point;
   
   drawMouseSelectRect();
  }
  
  
  protected function updateMouseSelect(point:Point = null):void{
   //trace("updateMouseSelect();");
   //adjustPoint(point);
   
   if (!point) {
    point = new Point(mouseSelectRect.mouseX, mouseSelectRect.mouseY);
   }
   endIndexPoint = pointToIndex(point);
   point.x += this.horizontalScrollPosition * this.columnWidth;
   point.y += this.verticalScrollPosition * this.rowHeight;
   endPoint = point;
   
   drawMouseSelectRect();
   
   //trace(startIndexPoint, endIndexPoint);
   
   var newSelectedIndices:Array = [];
   var i:Number, ix:Number, iy:Number;
   if (this.direction == TileBaseDirection.HORIZONTAL) {
    
    for (i = 0 ; i < dataProvider.length ; ++i) {
     
     ix = i % this.columnCount;
     iy = Math.floor(i / this.columnCount);
     
     xFlag = false;
     if (startIndexPoint.x <= endIndexPoint.x) {
      xFlag = ix >= startIndexPoint.x && ix <= endIndexPoint.x;
     } else {
      xFlag = ix <= startIndexPoint.x && ix >= endIndexPoint.x;
     }
     
     yFlag = false;
     if (startIndexPoint.y <= endIndexPoint.y) {
      yFlag = iy >= startIndexPoint.y && iy <= endIndexPoint.y;
     } else {
      yFlag = iy <= startIndexPoint.y && iy >= endIndexPoint.y;
     }
     
     if (xFlag && yFlag) {
      newSelectedIndices.push(i);
     }
    }
    
   } else {
    
    for (i = 0 ; i < dataProvider.length ; ++i) {
     
     ix = Math.floor(i / this.rowCount);
     iy = i % this.rowCount;
     
     xFlag = false;
     if (startIndexPoint.x <= endIndexPoint.x) {
      xFlag = ix >= startIndexPoint.x && ix <= endIndexPoint.x;
     } else {
      xFlag = ix <= startIndexPoint.x && ix >= endIndexPoint.x;
     }
     
     yFlag = false;
     if (startIndexPoint.y <= endIndexPoint.y) {
      yFlag = iy >= startIndexPoint.y && iy <= endIndexPoint.y;
     } else {
      yFlag = iy <= startIndexPoint.y && iy >= endIndexPoint.y;
     }
     
     if (xFlag && yFlag) {
      newSelectedIndices.push(i);
     }
    }
    
   }
   
   var xFlag:Boolean = false;
   var yFlag:Boolean = false;
   var deltaDict:Dictionary = new Dictionary();
   var o:*;
   var selectedIndices:Array;
   
   //var oldSelectedIndices:Array = this.selectedIndices;
   
   
   // Update selectedIndices
   if (this.ctrlKey) {
    
    var index:Number;
    for (i = 0 ; i < oldSelectedIndices.length ; ++i) {
     index = oldSelectedIndices[i];
     deltaDict[index] = null;
    }
    
    oldSelectedIndices = newSelectedIndices;
    
    for (i = 0 ; i < newSelectedIndices.length ; ++i) {
     index = newSelectedIndices[i];
     if (index in deltaDict) {
      delete deltaDict[index];
     } else {
      deltaDict[index] = null;
     }
    }
    
    selectedIndices = this.selectedIndices;
    for (i = 0 ; i < selectedIndices.length ; ++i) {
     index = selectedIndices[i];
     if (index in deltaDict) {
      delete deltaDict[index];
     } else {
      deltaDict[index] = null;
     }
    }
    
    newSelectedIndices = [];
    for (o in deltaDict) { newSelectedIndices.push(o); }
    
   } else if (this.shiftKey) {
    
    var deltaAddDict:Dictionary = new Dictionary();
    var deltaRemoveDict:Dictionary = new Dictionary();
    
    for (i = 0 ; i < oldSelectedIndices.length ; ++i) {
     deltaRemoveDict[oldSelectedIndices[i]] = null;
    }
    for (i = 0 ; i < newSelectedIndices.length ; ++i) {
     index = newSelectedIndices[i];
     deltaAddDict[index] = null;
     if (index in deltaRemoveDict) {
      delete deltaRemoveDict[index];
     }
    }
    oldSelectedIndices = newSelectedIndices;
    
    selectedIndices = this.selectedIndices;
    for (i = 0 ; i < selectedIndices.length ; ++i) {
     deltaDict[selectedIndices[i]] = null;
    }
    
    for (o in deltaAddDict) { deltaDict[o] = null; }
    for (o in deltaRemoveDict) { delete deltaDict[o]; }
    
    newSelectedIndices = [];
    for (o in deltaDict) { newSelectedIndices.push(o); }
    
   } else {
    selectedIndices = this.selectedIndices;
    
    this.oldSelectedIndices = newSelectedIndices.concat();
   }
   
   
   // Check if selectedIndicesupdated
   outer: if (selectedIndices.length == newSelectedIndices.length) {
    selectedIndices.sort(Array.NUMERIC);
    newSelectedIndices.sort(Array.NUMERIC);
    for (i = 0 ; i < selectedIndices.length ; ++i) {
     if (newSelectedIndices[i] != selectedIndices[i]) {
      break outer;
     }
    }
    return;
   }
   
   newSelectedIndices.reverse();
   this.selectedIndices = newSelectedIndices;
  }
  
  protected function endMouseSelect():void{
   //trace("endMouseSelect();");
   var g:Graphics = mouseSelectRect.graphics;
   g.clear();
   
            var evt:ListEvent = new ListEvent(ListEvent.CHANGE);
            dispatchEvent(evt);
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  protected function drawMouseSelectRect():void{
   
   var drawStartPoint:Point = new Point();
   var drawEndPoint:Point = new Point();
   
   drawStartPoint.x = this.startPoint.x - this.horizontalScrollPosition * this.columnWidth;
   drawStartPoint.y = this.startPoint.y - this.verticalScrollPosition * this.rowHeight;
   
   drawEndPoint.x = this.endPoint.x - this.horizontalScrollPosition * this.columnWidth;
   drawEndPoint.y = this.endPoint.y - this.verticalScrollPosition * this.rowHeight;
   
   var adjustDrawStartPoint:Point = adjustPoint(drawStartPoint);
   var adjustDrawEndPoint:Point = adjustPoint(drawEndPoint);
   
   var g:Graphics = mouseSelectRect.graphics;
   g.clear();
   g.lineStyle(1, 0x0, 0.5, true);
   
   g.moveTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y);
   
   if (drawStartPoint.y >= 0 && drawStartPoint.y <= listContent.height) {
    g.lineTo(adjustDrawEndPoint.x, adjustDrawStartPoint.y);
   }
   
   g.moveTo(adjustDrawEndPoint.x, adjustDrawStartPoint.y);
   g.lineTo(adjustDrawEndPoint.x, adjustDrawEndPoint.y);
   g.moveTo(adjustDrawEndPoint.x, adjustDrawEndPoint.y);
   g.lineTo(adjustDrawStartPoint.x, adjustDrawEndPoint.y);
   g.moveTo(adjustDrawStartPoint.x, adjustDrawEndPoint.y);
   
   if (drawStartPoint.x >= 0 && drawStartPoint.x <= listContent.width) {
    g.lineTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y);
   }
   
   g.moveTo(adjustDrawStartPoint.x, adjustDrawStartPoint.y);
   
   //*/
   g.lineStyle(0, 0x0, 0, true);
   g.beginFill(0x0000FF, 0.1);
   g.drawRect(adjustDrawStartPoint.x, adjustDrawStartPoint.y,
    adjustDrawEndPoint.x - adjustDrawStartPoint.x, adjustDrawEndPoint.y - adjustDrawStartPoint.y);
   g.endFill();
   //*/
   
  }
  
  
  //=================================================================
  //
  //=================================================================
  
  protected function adjustPoint(p:Point):Point{
   var newPoint:Point = new Point();
   newPoint.x = p.x < 0 ? 0 : p.x;
   newPoint.y = p.y < 0 ? 0 : p.y;
   
   newPoint.x = newPoint.x > listContent.width - 1 ? listContent.width - 1 : newPoint.x;
   newPoint.y = newPoint.y > listContent.height - 1 ? listContent.height - 1 : newPoint.y;
   return newPoint;
  }
  
  protected function pointToIndex(p:Point):Point{
   var indexPoint:Point = new Point();
   indexPoint.x = Math.floor(p.x / this.columnWidth) + this.horizontalScrollPosition - 0.5;
   indexPoint.y = Math.floor(p.y / this.rowHeight) + this.verticalScrollPosition - 0.5;
   //*/
   indexPoint.x += (p.x % this.columnWidth) > itemMargin ? 0.5 : 0;
   indexPoint.y += (p.y % this.rowHeight) > itemMargin ? 0.5 : 0;
   
   indexPoint.x += (p.x % this.columnWidth) > (this.columnWidth - itemMargin) ? 0.5 : 0;
   indexPoint.y += (p.y % this.rowHeight) > (this.rowHeight - itemMargin) ? 0.5 : 0;
   //*/
   return indexPoint;
  }
  
  //=================================================================
  // Override TileBase Functions
  //=================================================================
  
  override protected function updateDisplayList
      (unscaledWidth:Number, unscaledHeight:Number):void{
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   //this.addChildAt(mouseSelectRect, this.numChildren);
   //this.addChild(mouseSelectRect);
   mouseSelectRect.x = listContent.x;
   mouseSelectRect.y = listContent.y;
   //this.listContent.visible = false;
  }
  
  
  override protected function makeRowsAndColumns(left:Number, top:Number,
             right:Number, bottom:Number,
             firstCol:int, firstRow:int,
             byCount:Boolean = false, rowsNeeded:uint = 0):Point {
    //trace(this, "makeRowsAndColumns " + left + " " + top + " " + right + " " + bottom + " " + firstCol + " " + firstRow);
 
   var numRows:int;
   var numCols:int;
   var colNum:int;
   var rowNum:int;
   var xx:Number;
   var yy:Number;
   var wrappedData:Object;
   var data:Object;
   var uid:String
   var oldItem:IListItemRenderer 
   var item:IListItemRenderer;
   var more:Boolean;
   var valid:Boolean;
   var i:int;
   var rh:Number;
   var lastRowMade:int;
   var lastColumnMade:int;
   
   var bSelected:Boolean = false;
   var bHighlight:Boolean = false;
   var bCaret:Boolean = false;
 
  //      trace("TileBase.makeRowsAndColumns, horizontalScrollPosition = " + horizontalScrollPosition +
  //           ", iterator index = " + iterator.bookmark.getViewIndex() + ", iterator current = " + 
  //           iterator.current);
     
   if (columnWidth == 0 || rowHeight == 0)
    return null;
    
   invalidateSizeFlag = true;
   allowItemSizeChangeNotification = false;
 
   if (direction == TileBaseDirection.VERTICAL) {
    numRows = maxRows > 0 ? maxRows : Math.max(Math.floor(listContent.heightExcludingOffsets / rowHeight), 1);
    numCols = Math.max(Math.ceil((listContent.widthExcludingOffsets)/ columnWidth), 1);
    setRowCount(numRows);
    setColumnCount(numCols);
    colNum = firstCol;
    xx = left;
 
    lastColumnMade = colNum - 1;
    more = (iterator != null && !iterator.afterLast && iteratorValid);
 
    while ((byCount && rowsNeeded--) || (!byCount && (colNum < numCols + firstCol))) {
     rowNum = firstRow;
     yy = top;
     while (rowNum < numRows) {
      valid = more;
      wrappedData = more ? iterator.current : null;
      data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
      more = moveNextSafely(more);
 
      if (!listItems[rowNum])
       listItems[rowNum] = [];
 
      if (valid && yy < bottom) {
       uid = itemToUID(wrappedData);
       rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
       item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid);
       placeAndDrawItemRenderer(item,xx,yy,uid);
       lastColumnMade = Math.max(colNum,lastColumnMade);
      } else {
       oldItem = listItems[rowNum][colNum];
       if (oldItem) {
        addToFreeItemRenderers(oldItem);
        listContent.removeChild(DisplayObject(oldItem));
        //  delete rowMap[oldItem.name];
        listItems[rowNum][colNum] = null;
       }
       rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
      }
      yy += rowHeight;
      rowNum++;
     }
     colNum ++;
     if (firstRow) {
      // we're doing a row along the bottom so we have to skip the beginning of the next column
      for (i = 0; i < firstRow; i++)
       more = moveNextSafely(more);
     }
     xx += columnWidth;
    }
   } else { // horizontal
    numCols = maxColumns > 0 ? maxColumns : Math.max(Math.floor((listContent.widthExcludingOffsets)/ columnWidth), 1);
    numRows = Math.max(Math.ceil(listContent.heightExcludingOffsets / rowHeight), 1);
    setColumnCount(numCols);
    setRowCount(numRows);
    rowNum = firstRow;
    yy = top;
    more = (iterator != null && !iterator.afterLast && iteratorValid);
 
    lastRowMade = rowNum-1;
 
    while ((byCount && rowsNeeded--) || (!byCount && rowNum < numRows + firstRow)) {
     colNum = firstCol;
     xx = left;
     rowInfo[rowNum] = null;
 
     while (colNum < numCols) {
      valid = more;
      wrappedData = more ? iterator.current : null;
      data = (wrappedData is ItemWrapper) ? wrappedData.data : wrappedData;
      more = moveNextSafely(more);
 
      if (!listItems[rowNum])
       listItems[rowNum] = [];
 
      if (valid && xx < right) {
       uid = itemToUID(wrappedData);
 
       if (!rowInfo[rowNum])
        rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
       item = getPreparedItemRenderer(rowNum, colNum, wrappedData, data, uid);
       placeAndDrawItemRenderer(item,xx,yy,uid);
       lastRowMade = rowNum;
      } else {
       if (!rowInfo[rowNum])
        rowInfo[rowNum] = new ListRowInfo(yy, rowHeight, uid);
       oldItem = listItems[rowNum][colNum];
       if (oldItem) {
        addToFreeItemRenderers(oldItem);
        listContent.removeChild(DisplayObject(oldItem));
        listItems[rowNum][colNum] = null;
       }
      }
 
      xx += columnWidth;
      colNum++;
     }
     rowNum ++;
     if (firstCol) {
      // we're doing a column along the side so we have to skip the beginning of the next column
      for (i = 0; i < firstCol; i++)
       more = moveNextSafely(more);
     }
     yy += rowHeight;
    }
   }
 
   if (!byCount) {
    var a:Array;
    // prune excess rows and columns
    while (listItems.length > numRows + offscreenExtraRowsTop) {
     a = listItems.pop();
     rowInfo.pop();
     for (i = 0; i < a.length; i++) {
      oldItem = a[i];
      if (oldItem) {
       if (oldItem.parent)
        listContent.removeChild(DisplayObject(oldItem));
       addToFreeItemRenderers(oldItem);
      }
     }
    }
    if (listItems.length && listItems[0].length > numCols + offscreenExtraColumnsLeft) {
     for (i = 0; i < numRows + offscreenExtraRowsTop; i++) {
      a = listItems[i];
      while (a.length > numCols + offscreenExtraColumnsLeft) {
       oldItem = a.pop();
       if (oldItem) {
        if (oldItem.parent)
         listContent.removeChild(DisplayObject(oldItem));
        addToFreeItemRenderers(oldItem);
       }
      }
     }
    }
   }
 
   allowItemSizeChangeNotification = true;
   invalidateSizeFlag = false;
 
   return new Point(lastColumnMade - firstCol + 1,lastRowMade - firstRow + 1);
  }


     private function moveNextSafely(more:Boolean):Boolean {
         if (iterator && more)   {
             try {
                 more = iterator.moveNext();
             }
             catch(e1:ItemPendingError) {
                 lastSeekPending = new ListBaseSeekPending(CursorBookmark.CURRENT, 0);
                 e1.addResponder(new ItemResponder(seekPendingResultHandler, seekPendingFailureHandler, 
                                             lastSeekPending));
                 more = false;
                 iteratorValid = false;
             }
         }
         return more;
     }


     private function getPreparedItemRenderer(rowNum:int,colNum:int, wrappedData:Object, 
                                              data:Object, uid:String):IListItemRenderer {
         var oldItem:IListItemRenderer = listItems[rowNum][colNum];
         var item:IListItemRenderer;
         var rowData:ListData;
 
         if (oldItem) {
             // If we're running a data effect, do a more expensive check when
             // determining if we can reuse this item renderer
             if (runningDataEffect ? (dataItemWrappersByRenderer[oldItem] != wrappedData) : (oldItem.data != data))
                 addToFreeItemRenderers(oldItem);
             else
                 item = oldItem;
         }
 
         if (!item) {
             // if we're allowed to re-use existing renderers
             if (allowRendererStealingDuringLayout) {
                 // try to steal item renderer if it already exists,
                 // but don't steal item renderers that have already
                 // been used in the layout. (This will happen if there
                 // are duplicate UIDs in the collection, which shouldn't
                 // really happen, but nevertheless may happen).
                 item = visibleData[uid];
                 
                 // if we can't steal an item based on it's UID,
                 // steal based on the UID of the underlying data
                 if (!item && (wrappedData != data))
                     item = visibleData[itemToUID(data)];
             }
    // if we've stolen a renderer from somewhere else...
             if (item) {
                 // update data structures so we're not pointing to it twice
                 var ld:ListData = ListData(rowMap[item.name]);
                 if (ld) {
                     if (((direction == TileBaseDirection.HORIZONTAL) && 
                          ((ld.rowIndex > rowNum) || ((ld.rowIndex == rowNum) && (ld.columnIndex > colNum)))) ||
                         ((direction == TileBaseDirection.VERTICAL) && 
                          ((ld.columnIndex > colNum) || ((ld.columnIndex == colNum) && (ld.rowIndex > rowNum)))))
                         listItems[ld.rowIndex][ld.columnIndex] = null;
                     else
                         item = null;
                 }
             }
 
             if (!item) {
                 item = getReservedOrFreeItemRenderer(wrappedData);
                 if (item && !isRendererUnconstrained(item)) {
                     item.x = 0;
                     item.y = 0;
                 }
             }
             // if all else fails...
             if (!item)
                 item = createItemRenderer(data);
             item.owner = this;
             item.styleName = listContent;
             item.visible = true;
         }
 
         rowData = ListData(makeListData(data, uid, rowNum, colNum));
         rowMap[item.name] = rowData;
         if (item is IDropInListItemRenderer)
             IDropInListItemRenderer(item).listData = data ? rowData : null;
         item.data = data;
         if (wrappedData != data)
             dataItemWrappersByRenderer[item] = wrappedData;
 
         if (!item.parent)
             listContent.addChild(DisplayObject(item));
         item.visible = true;
         if (uid)
             visibleData[uid] = item;
         listItems[rowNum][colNum] = item;
         UIComponentGlobals.layoutManager.validateClient(item, true);
 
         return item;
     }


     private function placeAndDrawItemRenderer(item:IListItemRenderer, xx:Number, yy:Number, uid:String):void {
         var bSelected:Boolean = false;
         var bHighlight:Boolean = false;
         var bCaret:Boolean = false;
         var rh:Number;
 
         rh = item.getExplicitOrMeasuredHeight();
         if (item.width != columnWidth - itemMargin * 2 || rh != (rowHeight - cachedPaddingTop - cachedPaddingBottom - itemMargin * 2))
             item.setActualSize(columnWidth - itemMargin * 2, rowHeight - cachedPaddingTop - cachedPaddingBottom - itemMargin * 2);
             // this is not really doing anything yet
         if (!isRendererUnconstrained(item))
             item.move(xx + itemMargin, yy + cachedPaddingTop + itemMargin);
         bSelected = selectedData[uid] != null;
         if (runningDataEffect) {
             bSelected = bSelected || (selectedData[itemToUID(item.data)] != null);
             bSelected = bSelected && (!getRendererSemanticValue(item,ModifiedCollectionView.REPLACEMENT))
                 && (!getRendererSemanticValue(item,ModifiedCollectionView.ADDED));
         }
         bHighlight = highlightUID == uid;
         bCaret = caretUID == uid;
         if (uid)
             drawItem(item, bSelected, bHighlight, bCaret);
     }

  
  //=================================================================
  // Override ListBase Functions
  //=================================================================
  
  //*/
     override mx_internal function mouseEventToItemRendererOrEditor(
                                 event:MouseEvent):IListItemRenderer {
   var target:DisplayObject = DisplayObject(event.target);
   if (target == listContent) {
    var pt:Point = new Point(event.stageX, event.stageY);
    pt = listContent.globalToLocal(pt);
    
    if (pt.y % rowHeight < itemMargin || pt.y % rowHeight > columnWidth - itemMargin) {
     return null;
    }
    if (pt.x % columnWidth < itemMargin || pt.x % columnWidth > columnWidth - itemMargin) {
     return null;
    }
    
    var yy:Number = 0;
    
    var n:int = listItems.length;
    
    for (var i:int = 0; i < n; i++) {
     if (listItems[i].length) {
      if (pt.y < yy + rowInfo[i].height) {
       var m:int = listItems[i].length;
       // if (m == 1) return listItems[i][0];
       var j:int = Math.floor(pt.x / columnWidth);
       return listItems[i][j];
      }
     }
     yy += rowInfo[i].height;
    }
   } else if (target == highlightIndicator) {
    return lastHighlightItemRenderer;
   }
 
   while (target && target != this) {
    if (target is IListItemRenderer && target.parent == listContent) {
     if (target.visible)
      return IListItemRenderer(target);
     break;
    }
    if (target is IUIComponent)
     target = IUIComponent(target).owner;
    else 
     target = target.parent;
         }
   return null;
     }
  //*/
  
  //=================================================================
  // Overide ListBase draw Functions
  //=================================================================
  
  override protected function drawSelectionIndicator(
    indicator:Sprite, x:Number, y:Number,
    width:Number, height:Number, color:uint,
    itemRenderer:IListItemRenderer):void {
   /*/
   super.drawSelectionIndicator(
    indicator, x, y + itemMargin,
    width, height - itemMargin * 2,
    color, itemRenderer);
   //*/
         var g:Graphics = Sprite(indicator).graphics;
         g.clear();
         g.beginFill(color, 0.5);
         g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20);
         g.endFill();
         
         indicator.x = x;
         indicator.y = y + itemMargin;
  }
  
  override protected function drawHighlightIndicator(
    indicator:Sprite, x:Number, y:Number,
    width:Number, height:Number, color:uint,
    itemRenderer:IListItemRenderer):void {
    
   /*/
   super.drawSelectionIndicator(
    indicator, x, y + itemMargin,
    width, height - itemMargin * 2,
    color, itemRenderer);
   //*/
         var g:Graphics = Sprite(indicator).graphics;
         g.clear();
         g.beginFill(color, 0.5);
         g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20);
         g.endFill();
         
         indicator.x = x;
         indicator.y = y + itemMargin;
  }
  
  override protected function drawCaretIndicator(
    indicator:Sprite, x:Number, y:Number,
    width:Number, height:Number, color:uint,
    itemRenderer:IListItemRenderer):void {
    
   /*/
   super.drawSelectionIndicator(
    indicator, x, y + itemMargin,
    width, height - itemMargin * 2,
    color, itemRenderer);
   //*/
         var g:Graphics = Sprite(indicator).graphics;
         g.clear();
         g.lineStyle(2, color, 1, true);
         g.drawRoundRect(0, 0, width, height - itemMargin * 2, 20, 20);
         
         indicator.x = x;
         indicator.y = y + itemMargin;
  }
  
 }
}

Online Demo:

相关连结:
Flex 2 - 实作鼠标可圈选的 TileList
Flex 2 - 实作鼠标可圈选的 TileList V2

Read more...

2008年4月6日 星期日

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

Ticore's Blog

利用 Flex 3 新功能 Constraint Layout 实作 Daily Task View
之前曾经考虑过使用 DataGrid, TileList 等组件
但是都不是很适合
主要是因为 Daily Task View Column 数量不固定并且需要跨列

后来发现 Flex 3 Constraint Layout 颇适合
因为 Constraint Column, Row 都不是真的创建 Column 或是 Row
只是创建类似参考位置的资料
所以不会消耗太多效能
而且 Constraint Layout 可以随意的跨行、跨列

以下是简单实作 Daily Task View 例子:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   initialize="onInit();" backgroundColor="#E0E0E0">
 <mx:Script>
  <![CDATA[
   import mx.controls.HRule;
   import mx.containers.utilityClasses.ConstraintColumn;
   import mx.containers.utilityClasses.ConstraintRow;
   import mx.controls.*;
   
   public var taskData:Array = [];
   public var taskColumnData:Array = [];
   public var taskItems:Array = [];
   
   public function onInit():void{
    createConsRows();
    
    refresh();
   }
   
   
   public function refresh():void{
    createTaskData();
    arrangeTaskColumn();
    createConsCols();
    createTaskItems();
   }
   
   
   public function createTaskData():void{
    taskData = [];
    var num:Number = Math.random() * 30 + 3;
    for (var i:Number = 0 ; i < num ; ++i) {
     var task:Object = {};
     task.title = "Task " + i;
     task.startDate = new Date();
     task.endDate = new Date();
     task.startDate.setHours(Math.random() * 24);
     task.endDate.setHours(task.startDate.hours + Math.random() * 24);
     task.startDate.setMinutes(Math.random() * 60);
     task.endDate.setMinutes(Math.random() * 60);
     taskData.push(task);
    }
    taskData.sortOn("startDate");
   }
   
   
   public function arrangeTaskColumn():void{
    taskColumnData = [];
    taskColumnData.push([]);
    loop1:for (var i:Number = 0 ; i < taskData.length ; ++i) {
     var task:Object = taskData[i];
     
     // 排除跨天 Task
     if (task.startDate.hours > task.endDate.hours) {
      continue;
     }
     
     loop2:for (var j:Number = 0 ; j < taskColumnData.length ; ++j) {
      var taskColumn:Array = taskColumnData[j];
      loop3:for (var k:Number = 0 ; k < taskColumn.length ; ++k) {
       var taskAdded:Object = taskColumn[k];
       if (task.startDate.time > taskAdded.endDate.time || task.endDate.time < taskAdded.startDate.time) {
        
       } else {
        continue loop2;
       }
      }
      
      taskColumn.push(task);
      continue loop1;
     }
     // 既有 column 时间都重叠 -> 创建新 column
     taskColumnData.push([task]);
    }
   }
   
   public function createConsCols():void{
    canvas.constraintColumns = [];
    for (var i:Number = 0 ; i < taskColumnData.length ; ++i) {
     var consCol:ConstraintColumn = new ConstraintColumn();
     consCol.initialized(canvas, "col" + i);
     // consCol.width = 100;
     consCol.percentWidth = 100 / taskColumnData.length;
     canvas.constraintColumns.push(consCol);
    }
   }
   
   public function createTaskItems():void{
    
    for each(var s:* in taskItems) {
     canvas.removeChild(s);
    }
    taskItems= [];
    
    for (var i:Number = 0 ; i < taskColumnData.length ; ++i) {
     var taskColumn:Array = taskColumnData[i];
     for (var j:Number = 0 ; j < taskColumn.length ; ++j) {
      var task:Object = taskColumn[j];
      
      var btn:Button = new Button();
      taskItems.push(btn);
      btn.minWidth = 10;
      btn.minHeight = 20;
      btn.label = task.title + " (" + task.startDate.hours + ":" + task.startDate.minutes +
        "-" + task.endDate.hours + ":" + task.endDate.minutes + ")";
      
      btn.setStyle("top", "row" + task.startDate.hours + ":" + int(task.startDate.minutes / 60 * 50));
      btn.setStyle("bottom", "row" + (task.endDate.hours) + ":" + -int(task.endDate.minutes / 60 * 50 - 50));
      
      /*/
      btn.setStyle("top", "row" + task.startDate.hours + ":0");
      if (task.startDate.hours == task.endDate.hours) {
       btn.setStyle("bottom", "row" + task.endDate.hours + ":0");
      } else {
       btn.setStyle("bottom", "row" + (task.endDate.hours - 1) + ":0");
      }
      //*/
      
      btn.setStyle("left", "col" + i + ":0");
      btn.setStyle("right", "col" + i + ":0");
      canvas.addChild(btn);
     }
    }
   }
   
   public function createConsRows():void{
    for (var i:Number = 0 ; i < 24 ; ++i) {
     var consRow:ConstraintRow = new ConstraintRow();
     consRow.setActualHeight(50);
     consRow.initialized(canvas, "row" + i);
     // consRow.percentHeight = 100 / 24;
     canvas.constraintRows.push(consRow);
     
     // create rule
     var label:Label = new Label();
     label.text = "" + (i + 1);
     label.setStyle("bottom", "row" + i + ":0");
     canvas.addChild(label);
     
     var rule:HRule = new HRule();
     rule.setStyle("strokeColor", "#808080");
     rule.height = 1;
     rule.percentWidth = 100;
     rule.setStyle("bottom", "row" + i + ":0");
     canvas.addChild(rule);
    }
   }
  ]]>
 </mx:Script>
 <mx:Canvas id="canvas" width="100%" height="100%"
   borderThickness="1" borderColor="#808080" borderStyle="solid"
   horizontalScrollPolicy="off" verticalScrollPolicy="auto">
 </mx:Canvas>
 <mx:Button label="Refresh" click="refresh();" />
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

Online Demo:

相关连结:
Flex Button with Constraint Layout

Read more...

2008年4月5日 星期六

Flex Collapsible DataGrid   [+/-]

Ticore's Blog

Flex 3 AdvancedDataGrid 具有 Tree 与 跨栏 (columnSpan) 的功能
以下利用这两种特性做出可收合的 DataGrid

MyDataGridGroupItemRenderer Class:

package com.ticore.uicomponents {
 import mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer;

 public class MyDataGridGroupItemRenderer extends AdvancedDataGridGroupItemRenderer {
   
  public function MyDataGridGroupItemRenderer() {
   super();
  }
  override protected function updateDisplayList(
     unscaledWidth:Number, unscaledHeight:Number):void {
   
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   
   this.graphics.clear();
   this.graphics.lineStyle(1, 0x808080, 1, true);
   this.graphics.drawRect(-1, -1, unscaledWidth + 1, unscaledHeight);
  }
  
 }
}

Flex MXML Code:

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#E0E0E0">
 <mx:Script>
  <![CDATA[
   import mx.controls.advancedDataGridClasses.*;
   import mx.collections.ArrayCollection;
            
   [Bindable]
   private var dpHierarchy:ArrayCollection = new ArrayCollection([
    {Region:"Arizona", children: [
     {Territory_Rep:"Barbara Jennings", Actual:38865, Estimate:40000},
     {Territory_Rep:"Dana Binn", Actual:29885, Estimate:30000}]},
    {Region:"Central California", children: [
     {Territory_Rep:"Joe Smith", Actual:29134, Estimate:30000}]},
    {Region:"Nevada", children: [
     {Territory_Rep:"Bethany Pittman", Actual:52888, Estimate:45000}]},
    {Region:"Northern California", children: [
     {Territory_Rep:"Lauren Ipsum", Actual:38805, Estimate:40000},
     {Territory_Rep:"T.R. Smith", Actual:55498, Estimate:40000}]},
    {Region:"Southern California", children: [
     {Territory_Rep:"Alice Treu", Actual:44985, Estimate:45000},
     {Territory_Rep:"Jane Grove", Actual:44913, Estimate:45000}]}
    ]);
   
   public function groupLabelFunction(item:Object, column:AdvancedDataGridColumn):String{
    if (item && item.children) {
     return item[column.dataField] + " (" + item.children.length + ")";
    } else {
     return item[column.dataField];
    }
   }
  ]]>
 </mx:Script>

 <mx:AdvancedDataGrid id="myADG" width="100%" height="100%"
    rowHeight="24" headerHeight="24" fontSize="12" displayItemsExpanded="true"
    paddingTop="0" paddingBottom="0" paddingLeft="0" paddingRight="0"
    folderClosedIcon="{null}" folderOpenIcon="{null}" defaultLeafIcon="{null}" >
  <mx:dataProvider>
      <mx:HierarchicalData source="{dpHierarchy}" />
  </mx:dataProvider>
  <mx:rendererProviders>
      <mx:AdvancedDataGridRendererProvider columnIndex="0" columnSpan="4" dataField="Region"
     renderer="com.ticore.uicomponents.MyDataGridGroupItemRenderer" />
      <mx:AdvancedDataGridRendererProvider dataField="Territory_Rep"
     renderer="mx.controls.advancedDataGridClasses.AdvancedDataGridGroupItemRenderer" />
     </mx:rendererProviders>
  <mx:groupedColumns>
   <mx:AdvancedDataGridColumn id="col1" dataField="Region" width="20"
      labelFunction="groupLabelFunction" />
   <mx:AdvancedDataGridColumn id="col2" dataField="Territory_Rep"/>
   <mx:AdvancedDataGridColumn dataField="Actual"/>
   <mx:AdvancedDataGridColumn dataField="Estimate"/>
  </mx:groupedColumns>
 </mx:AdvancedDataGrid>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

Online Demo:

Read more...

2008年4月2日 星期三

Flex - Tab Tree   [+/-]

Ticore's Blog

几年前很流行 Flash Tab Menu 组件
乍看之下,很像是 Accordion 组件
实际上是比较接近 Tree 组件

以下更改 Tree 组件行为,让它变得比较类似 Tab Menu
不同深度节点有不同的背景色
相同深度下的节点同时只能打开一个

TreeItemRenderer Class:

package com.ticore.uicomponents {
 import mx.controls.treeClasses.TreeItemRenderer;

 public class TreeItemRenderer extends mx.controls.treeClasses.TreeItemRenderer {
  
  override protected function commitProperties():void {
   super.commitProperties();
  }

  override protected function updateDisplayList(unscaledWidth:Number,
               unscaledHeight:Number):void {
   
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   
   if (listData) {
    var fillAlpha:Number;
    var fillColor:Number;
    switch (listData['depth']) {
     case 1:
      fillAlpha = 0.8;
      fillColor = 0xFFD0D0;
      break;
     case 2:
      fillAlpha = 0.6;
      fillColor = 0xD0FFD0;
      break;
     case 3:
      fillAlpha = 0.4;
      fillColor = 0xD0D0FF;
      break;
    }
    
    this.graphics.clear();
    this.graphics.lineStyle(1, 0x808080, 0.5, true);
    this.graphics.beginFill(fillColor, fillAlpha);
    this.graphics.drawRect(0, 0, unscaledWidth - 1, unscaledHeight - 1);
    this.graphics.endFill();
   }
   
  }
 }
}

Flex MXML:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
  backgroundColor="#F0F0F0" layout="horizontal">
 <mx:Script>
  <![CDATA[
   import mx.collections.ICollectionView;
   import mx.events.TreeEvent;
   
   
   private var openingItem:Object;
   
   public function onItemOpen(evtObj:TreeEvent):void{
    openingItem = evtObj.item;
    // callLater(handleCoseItems);
    setTimeout(handleCoseItems, 100);
   }
   
   public function handleCoseItems():void{
    var item:Object = openingItem;
    var parentItems:Object = tree.getParentItem(item) == null ? objData : tree.getParentItem(item);
    
    for (var i:Number = 0 ; i < parentItems.children.length ; ++i) {
     var siblingItem:Object = parentItems.children[i];
     if (siblingItem != item) {
      if (tree.isItemOpen(siblingItem)) {
       tree.expandItem(siblingItem, false, true);
      }
     }
    }
   }
   
   [Bindable]
   public var objData:Object ={children: [
    {label: "Node", children: [
     {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
    ]},
    {label: "Node", children: [
     {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
    ]},
    {label: "Node", children: [
     {label: "Node 1", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 2", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
     {label: "Node 3", children: [{label: "Node 1"}, {label: "Node 2"}, {label: "Node 3"}]},
    ]},
   ]};
  ]]>
 </mx:Script>
 <mx:Tree id="tree" width="100%" height="100%" indentation="0" showRoot="false"
   folderClosedIcon="{null}" folderOpenIcon="{null}" defaultLeafIcon="{null}"
   itemOpen="onItemOpen(event)" dataProvider="{objData}"
   verticalScrollPolicy="auto" fontSize="14" backgroundAlpha="0"
   paddingBottom="1" paddingTop="1" paddingLeft="0" paddingRight="0"
   labelField="label" itemRenderer="com.ticore.uicomponents.TreeItemRenderer" >
 </mx:Tree>
</mx:Application>
<!-- Ticore's Blog - http://ticore.blogspot.com/ -->

相关连结:
Flex - 自动伸缩的 Tree

Read more...