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.
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.
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
Posted by: James | November 22, 2008 1:04 AM
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
Posted by: Jamie Badman | November 22, 2008 8:15 AM
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
Posted by: Asbjørn | November 22, 2008 8:27 AM
Thanks for putting this together. It is much appreciated!
Posted by: Marcin Glowacki | November 22, 2008 8:27 PM
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
Posted by: Danny Gold | November 24, 2008 7:39 AM
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.
Posted by: islami sohbet | November 28, 2008 2:12 PM
Hello, Alex!
Excellent example, thank you.
Posted by: injun #576871 | December 1, 2008 7:33 AM
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
Posted by: Sven | December 2, 2008 5:24 AM
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.
Posted by: Kilew | December 3, 2008 1:14 AM
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
Posted by: nathan rogan | December 3, 2008 6:36 AM
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.
Posted by: nathan | December 8, 2008 6:17 AM
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.
Posted by: Denis | February 9, 2009 10:30 AM
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!
Posted by: Xinyu | February 15, 2009 10:27 AM
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!
Posted by: Oliver | February 18, 2009 7:34 AM
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.
Posted by: Dharmendra | April 23, 2009 10:51 AM
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.
Posted by: Vasiliy Ignatov | April 27, 2009 2:49 AM