Thursday 18 March 2010

BetterDataGrid Improved

package
{
import flash.display.Sprite;
import flash.geom.Point;

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
{
    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 = null;


    // 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:Sprite, colIndex:int, color:uint, x:Number);
            return;
        }
        if (colIndex == attrColumnIndex-pos)
        {
            var colour:uint = 0xFF0000;
            super.drawVerticalLine(s,colIndex,colour,x);
            return;
        }
        super.drawVerticalLine(s,colIndex,color,x);
    }

 }
}

Posted via email from fasanya's posterous

No comments: