« Using the Flex Builder 3.x Profiler | Main | Tree and Lazy or Paged Data »

Faster DataGrid Horizontal Scrolling

Lately, I've heard many complaints about horizontal scrolling performance in DataGrid. Vertical scrolling has been optimized, but horizontal has not. I found a bit of time to put together how to subclass DataGrid and try to optimize horizontal scrolling. I'm sure there's bugs and I don't know if I'll have time to fix them and the usual caveats apply.

Hope it helps. It should work with any Flex 3 SDK version.

Download Source

In the example, the top DataGrid is not optimized, the bottom one is. Make the screen bigger and you'll start to see and feel the difference.

Run Example

Comments

Hi Alex,

I got the following error while testing the bottom one.

TypeError: Error #1010: A term is undefined and has no properties.
at BetterDataGrid/scrollLeftOrRight()
at BetterDataGrid/set horizontalScrollPosition()
at mx.controls::DataGrid/scrollHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()
at mx.controls.scrollClasses::ScrollBar/http://www.adobe.com/2006/flex/mx/internal::dispatchScrollEvent()
at mx.controls.scrollClasses::ScrollThumb/mouseMoveHandler()

--------------------
Alex responds:

I couldn't reproduce this exact stack trace, but I found and fixed a related bug that might fix this one as well. Examples and source were updated around 9:24PM PST

There's a problem with th ebottom one, I think - errors when I vertical scroll...

Jamie.

------------------
Alex responds:

I found and fixed one problem. The examples and source have been updated around 9:24PM PST

I sometimes get this error when scrolling in the bottom grid:

TypeError: Error #1010: A term is undefined and has no properties.
at BetterDataGrid/sumColumnWidths()
at BetterDataGrid/scrollLeftOrRight()
at BetterDataGrid/set horizontalScrollPosition()
at mx.controls::DataGrid/scrollHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()
at mx.controls.scrollClasses::ScrollBar/http://www.adobe.com/2006/flex/mx/internal::dispatchScrollEvent()
at mx.controls.scrollClasses::ScrollThumb/mouseMoveHandler()

I'm using FlashPlayer 10 (debug version). I'm not really sure how to reproduce this, though.

-------------------
Alex responds:

OK, I'll look into it

Thanks for putting this together. It is much appreciated!

What would it take to get this working with the AdvancedDataGrid? I think some of the methods referenced are a little different, but I've got a fairly massive AdvancedDataGrid that is having some scrolling problems.

---------------------------
Alex responds:

AdvancedDataGrid is a different team, but I took a quick look at their code and this example might just work as is

Did you find the solutions?
TypeError: Error #1010: A term is undefined and has no properties.
at BetterDataGrid/scrollLeftOrRight()
at BetterDataGrid/set horizontalScrollPosition()
at mx.controls::DataGrid/scrollHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()

----------------------
Alex responds:

I posted new code a few days back. If you still have this problem, provide some info as to how you got it.

Hello, Alex!
Excellent example, thank you.

Vertical scrolling may have been optimized but I feel there is room for improvement by providing smooth vertical scrolling instead of jumping from one row to the next. Hopefully this is a feature that is getting some attention.

---------------------
Alex responds:

Vertical scrolling should be pixel based in Gumbo

TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.controls.dataGridClasses::DataGridBase/addToFreeItemRenderers()
at BetterDataGrid/shiftColumns()
at BetterDataGrid/scrollLeftOrRight()
at BetterDataGrid/set horizontalScrollPosition()
at mx.controls::DataGrid/scrollHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()
at mx.controls.scrollClasses::ScrollBar/http://www.adobe.com/2006/flex/mx/internal::dispatchScrollEvent()
at mx.controls.scrollClasses::ScrollThumb/mouseMoveHandler()

How to reproduce:
----
Hold mouse on vertical scroll, and do some scrolling. Then, (Don't up mouse button, before you'll be on horizontal scroll bar), try to scroll horizontally. Try to do it so fast as possible :)

There's 2 exceptions, but I think source is same.

-------------------------
Alex responds:

Ok thanks.

Alex- What approach would you recommend taking in optimizing the AdvancedDataGrid hScrolling?

---------------------
Alex responds:

The code might work if you subclass ADG instead of DG. Give it a try and let us know

Sorry I should have mentioned that I had done that already. There are a few protected methods in the DG that are not available in the ADG

setupColumnItemRenderer layoutColumnItemRenderer

Also sub classing the ADG, there is no direct access to the header object.

I believe the above methods as well as the header object are fully encapsulated before the ADG class. However i am looking into
override mx_internal function getHeaderInfo(col:AdvancedDataGridColumn):AdvancedDataGridHeaderInfo as a possible way to get the header info yet that still leaves the rendering methods unavailable.


----------------------
Alex responds:

I don't have time to look into it right now. Post on Sameer Bhatt's blog and see if he can help you.

Alex,

Thanks for this fantastic component. I found a bug that you can easily replicate if you make the number of rows less than the vertical visible area. The "null" rows that fill the remaining area under the rows with data create a problem during the addToFreeItemRenderers call. There was a post above with a similar error from Sven. The error is:

TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.controls.dataGridClasses::DataGridBase/addToFreeItemRenderers()
at BetterDataGrid/shiftColumns()
at BetterDataGrid/scrollLeftOrRight()
at BetterDataGrid/set horizontalScrollPosition()
at mx.controls::DataGrid/scrollHandler()
at flash.events::EventDispatcher/dispatchEventFunction()
at flash.events::EventDispatcher/dispatchEvent()
at mx.core::UIComponent/dispatchEvent()
at mx.controls.scrollClasses::ScrollBar/http://www.adobe.com/2006/flex/mx/internal::dispatchScrollEvent()
at mx.controls.scrollClasses::ScrollThumb/mouseMoveHandler()

------------------------------
Alex responds:

OK, thanks. Not sure when I'll get to it. Feel free to post your solution.

Alex,
Thanks for your great work!

I got a Error #1009 message with your example. When I try to move the vertical scrollbar to the end, and then move the horizontal scrollbar to left. This error message occurred. I found the problem might be caused by the source code BetterDataGrid.as in line 104. I just replaced it with 'var rowCount:int = rowInfo.length - 1;'. So far, it works fine!

--------------------
Alex responds:

OK, thanks!

Hello Alex,
Thanks for putting this example together, the performance improvements are remarkable. I thought I'd share a number of small fixes I've added:

1. as some previous posters noted, 'null' renderers in empty rows cause an error #1009 to be thrown. I fixed this with these handful of lines in scrollLeftOrRight():

if (scrollUp)
{
 ...
 for (i = 0; i  {
  numCols = listItems[i].length;
  if(numCols == 0) //empty row
   continue;

  ...
 }
 ...
 for (i = 0; i  {
  if(iterator == null || iterator.afterLast || !iteratorValid)
   continue;

  ...
 }
}
else
{
 ...
 for (i = 0; i  {
  numCols = listItems[i].length;
  if(numCols == 0)
   continue;

  ...
 }
 ...
 for (i = 0; i  {
  if(iterator == null || iterator.afterLast || !iteratorValid)
   continue;

  ...
 }
}

The solution posted by another reader (changing line 104 to 'var rowCount:int = rowInfo.length -1;') gets rid of the errors, but also causes the last row to not be updated correctly when scrolling.

2. When the first itemRenderer of a row is freed via freeItemRenderer() (which happens when the rows get moved to the left), the listContent.visibleData entry for that row is deleted, which prevents the row highlight from rendering when the mouse cursor moves over a row. To fix this, I modified the shiftColumns() function as follows:

var uid:String = itemToUID(listItems[rowIndex][0].data);
for (var i:int = 0; i {
 item = listItems[rowIndex].shift();
 addToFreeItemRenderers(item);
}
//rebuild the listContent.visibleData map entry
listContent.visibleData[uid] = listItems[rowIndex][0];

3. The horizontal scrollbar doesn't get updated properly if the table contains variable column widths. This one-liner in set horizontalScrollPosition() fixed this for me:

// if the flag got tripped run our new technique
if (canUseScrollH)
{
 scrollLeftOrRight();
 configureScrollBars();
}

4. In some situations where there are columns of varying width, the (updated) value of visibleColumns.length can become smaller than deltaPos by time we get to scrollLeftOrRight(). For example, if you can currently see 10 narrow columns and the previous 10 columns are significantly wider such that only 2 fit at a time, then if you drag the scrollthumb over such that deltaPos = 3, the initial check passes (since deltaPos scrollLeftOrRight():


if(scrollUp)
{
 ...
}
else
{
 numCols = listItems[0].length;

 if(deltaPos > visibleColumns.length)
  deltaPos = visibleColumns.length;

 moveBlockDistance = sumColumnWidths(deltaPos, false);

 ...
}

5. This one I'm not actually sure if it's necessary, but I slightly modified the lines in scrollLeftOrRight() that toss excess columns so that the number of itemRenderers in a row always matches visibleColumns.length:

if(scrollUp)
{
 ...
 for (i = 0; i  {
  ...
  //toss excess columns
  while (listItems[i].length > visibleColumns.length) //instead of 'if'
  {
   addToFreeItemRenderers(listItems[i].pop());
  }
 }
}
else
{
 ...
 for (i = 0; i  {
  ...
  //toss excess columns
  while (listItems[i].length > visibleColumns.length) //instead of 'if'
  {
   addToFreeItemRenderers(listItems[i].pop());
  }
 }
}

It seems to run pretty robustly now. Hope this information is useful!

--------------------------
Alex responds:

Thanks!

Hi ALex,
I see one strange issue with your components.The issue is data of one column gettting REPLICATED on another column adjacent to it.Please have a look in to it as i am using this components in a critical application and my client was the performace.Now I became dependent on this componets. I will also create a sample to demotrate this defact and post to you.
To Reroduce it you have to put at least 100 object into dataptovider

------------------------
Alex responds:

These examples are unsupported. I cannot guarantee a fix. Others are using this component so maybe they can offer help. Try posting on an Adobe forum or FlexCoders.

Alex,

Thanks for this very interesting and useful component. Do you have donation count? I want to send you some money.

-----------------
Alex responds:

Currently, Adobe does not allow us to make extra money from our blog content. Go spend it on the needy.

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)