package { import flash.display.Sprite; import flash.events.MouseEvent; import flash.geom.Point; import mx.collections.ArrayCollection; import mx.collections.CursorBookmark; import mx.controls.DataGrid; import mx.controls.dataGridClasses.DataGridColumn; import mx.controls.listClasses.IListItemRenderer; import mx.core.mx_internal; use namespace mx_internal; /** * A DataGrid subclass that has faster horizontal scrolling */ public class BetterDataGrid extends DataGrid { /** * draw ground color for every row */ override protected function drawRowBackground(s:Sprite, rowIndex:int, y:Number, height:Number, color:uint, dataIndex:int):void { super.drawRowBackground(s, rowIndex, y, height, color, dataIndex); } public function BetterDataGrid() { super(); } /** * remember the number of columns in case it changes */ private var lastNumberOfColumns:int; /** * a flag as to whether we can use the optimized scrolling */ private var canUseScrollH:Boolean; /** * when the horizontal scrollbar is changed it will eventually set horizontalScrollPosition * This value can be set programmatically as well. */ override public function set horizontalScrollPosition(value:Number):void { // remember the setting of this flag. We will tweak it in order to keep DataGrid from // doing its default horizontal scroll which essentially refreshes every renderer var lastItemsSizeChanged:Boolean=itemsSizeChanged; // remember the current number of visible columns. This can get changed by DataGrid // as it recomputes the visible columns when horizontally scrolled. lastNumberOfColumns=visibleColumns.length; // reset the flag for whether we use our new technique canUseScrollH=false; // call the base class. If we can use our technique we'll trip that flag super.horizontalScrollPosition=value; // if the flag got tripped run our new technique if (canUseScrollH) { scrollLeftOrRight(); configureScrollBars(); } // reset the flag itemsSizeChanged=lastItemsSizeChanged; } // remember the parameters to scrollHorizontally to be used in our new technique private var pos:int; private var deltaPos:int; private var scrollUp:Boolean; public function get attrColumnIndex():int { return _attrColumnIndex; } public function set attrColumnIndex(o:int):void { _attrColumnIndex=o; } protected var _attrColumnIndex:int=0; // override this method. If it gets called that means we can use the new technique override protected function scrollHorizontally(pos:int, deltaPos:int, scrollUp:Boolean):void { // just remember the args for later; this.pos=pos; this.deltaPos=deltaPos; this.scrollUp=scrollUp; if (deltaPos < visibleColumns.length) { canUseScrollH=true; // need this to prevent DG from asking for a full refresh itemsSizeChanged=true; } } /** * The new technique does roughly what we do vertically. We shift the renderers on screen and in the * listItems array and only make the new renderers. * Because we can't get internal access to the header, we fully refresh it, but that's only one row * of renderers. There's significant gains to be made by not fully refreshing the every row of columns * * Key thing to note here is that visibleColumns has been updated, but the renderer array has not * That's why we don't do this in scrollHorizontally as the visibleColumns hasn't been updated yet * But because of that, sometimes we have to measure old renderers, and sometimes we measure the columns */ private function scrollLeftOrRight():void { // trace("scrollHorizontally " + pos); var i:int; var j:int; var numCols:int; var uid:String; var curX:Number; var rowCount:int=rowInfo.length; var columnCount:int=listItems[0].length; var cursorPos:CursorBookmark; var moveBlockDistance:Number=0; var c:DataGridColumn; var item:IListItemRenderer; var itemSize:Point; var data:Object; var xx:Number; var yy:Number; if (scrollUp) // actually, rows move left { // determine how many columns we're discarding var discardCols:int=deltaPos; // measure how far we have to move by measuring the width of the columns we // are discarding moveBlockDistance=sumColumnWidths(discardCols, true); // trace("moveBlockDistance = " + moveBlockDistance); // shift rows leftward and toss the ones going away for (i=0; i < rowCount; i++) { numCols=listItems[i].length; if (numCols == 0) //empty row { continue; } // move the positions of the row, the item renderers for the row, // and the indicators for the row moveRowHorizontally(i, discardCols, -moveBlockDistance, numCols); // move the renderers within the array of rows shiftColumns(i, discardCols, numCols); truncateRowArray(i); } // generate replacement columns cursorPos=iterator.bookmark; var firstNewColumn:int=lastNumberOfColumns - deltaPos; curX=listItems[0][firstNewColumn - 1].x + listItems[0][firstNewColumn - 1].width; for (i=0; i < rowCount; i++) { if (iterator == null || iterator.afterLast || !iteratorValid) continue; data=iterator.current; iterator.moveNext(); uid=itemToUID(data); xx=curX; yy=rowInfo[i].y; for (j=firstNewColumn; j < visibleColumns.length; j++) { c=visibleColumns[j]; item=setupColumnItemRenderer(c, listContent, i, j, data, uid); //if(!item) return; itemSize=layoutColumnItemRenderer(c, item, xx, yy); xx+=itemSize.x; } // toss excess columns while (listItems[i].length > visibleColumns.length) { addToFreeItemRenderers(listItems[i].pop()); } } iterator.seek(cursorPos, 0); } else { numCols=listItems[0].length; if (deltaPos > visibleColumns.length) deltaPos=visibleColumns.length; moveBlockDistance=sumColumnWidths(deltaPos, false); // shift the renderers and slots in array for (i=0; i < rowCount; i++) { numCols=listItems[i].length; if (numCols == 0) continue; moveRowHorizontally(i, 0, moveBlockDistance, numCols); // we add placeholders at the front for new renderers addColumnPlaceHolders(i, deltaPos); } cursorPos=iterator.bookmark; for (i=0; i < rowCount; i++) { data=iterator.current; iterator.moveNext(); uid=itemToUID(data); xx=0; yy=rowInfo[i].y; for (j=0; j < deltaPos; j++) { c=visibleColumns[j]; item=setupColumnItemRenderer(c, listContent, i, j, data, uid); itemSize=layoutColumnItemRenderer(c, item, xx, yy); xx+=itemSize.x; } // toss excess columns while (listItems[i].length > visibleColumns.length) { addToFreeItemRenderers(listItems[i].pop()); } } iterator.seek(cursorPos, 0); } // force update the header header.headerItemsChanged=true; header.visibleColumns=visibleColumns; header.invalidateDisplayList(); header.validateNow(); // draw column lines and backgrounds drawLinesAndColumnBackgrounds(); } /* override protected function addToFreeItemRenderers(item:IListItemRenderer):void { if (item) super.addToFreeItemRenderers(item); } */ // if moving left, add up old renderers // if moving right, add up new columns private function sumColumnWidths(num:int, left:Boolean):Number { var i:int; var value:Number=0; if (left) { for (i=0; i < num; i++) { value+=listItems[0][i].width; } } else for (i=0; i < num; i++) { value+=visibleColumns[i].width; } return value; } // shift position of renderers on screen private function moveRowHorizontally(rowIndex:int, start:int, distance:Number, end:int):void { for (; start < end; start++) if (listItems[rowIndex][start]) { listItems[rowIndex][start].x+=distance; } } // shift renderer assignments in listItems array private function shiftColumns(rowIndex:int, shift:int, numCols:int):void { var item:IListItemRenderer; var uid:String=itemToUID(listItems[rowIndex][0].data); for (var i:int=0; i < shift; i++) { item=listItems[rowIndex].shift(); if (item) { addToFreeItemRenderers(item); } } //rebuild the listContent.visibleData map entry listContent.visibleData[uid]=listItems[rowIndex][0]; } // add places in front of row for new columns private function addColumnPlaceHolders(rowIndex:int, count:int):void { for (var i:int=0; i < count; i++) { listItems[rowIndex].unshift(null); } } // remove excess columns private function truncateRowArray(rowIndex:int):void { while (listItems[rowIndex].length > visibleColumns.length) { var item:IListItemRenderer; { item=listItems[rowIndex].pop(); addToFreeItemRenderers(item); } } } override protected function drawVerticalLine(s:Sprite, colIndex:int, color:uint, x:Number):void { if (!attrColumnIndex) { super.drawVerticalLine(s, colIndex, color, x); return; } if (colIndex == attrColumnIndex - pos) { var colour:uint=0xFF0000; super.drawVerticalLine(s, colIndex, colour, x); return; } super.drawVerticalLine(s, colIndex, color, x); } } }