Friday, January 05, 2007

What items are visible in Table/Tree?

Have you often asked yourself which items are visibile in Table/Tree. Maybe you need these informations to dispose system-resources (e.g. if you have different images in every row you can easily run out of system resources).

What to do? There's no SWT-API available to calculate this information. So we need a bunch of custom code to make this work. The following codefragements are only a first rough test case and they need JFace from 3.3M4 and a patch from bug 151295 .

Step 1: Setup the infrastructure
- Event object to inform consumers about the change of visible rows
public class ViewerRowStateChangedEvent extends EventObject {
private static final long serialVersionUID = 1L;

public ArrayList itemsHidden;

public ArrayList itemsVisible;

public ViewerRowStateChangedEvent(Object source) {
super(source);
}
}
- ViewerRowStateChangeListener for consumers to implement
public interface ViewerRowStateChangeListener {
public void itemStateChangedListener(ViewerRowStateChangedEvent event);
}
Step 2: Create an AbstractClass implementing the visible row logic on base of ViewerRow and the new API whichn will hopefully added by bug 151295.
public abstract class AbstractViewerRowVisibilityStateSupport {
private ArrayList currentItems = new ArrayList();

private ColumnViewer columnViewer;

private ListenerList listenerList = new ListenerList();

public AbstractViewerRowVisibilityStateSupport(ColumnViewer columnViewer) {
this.columnViewer = columnViewer;

Listener l = new Listener() {
public void handleEvent(Event event) {
ArrayList list = recalculateVisibleItems();
ArrayList itemsVisible = new ArrayList();

Iterator it = list.iterator();
Object obj;

while( it.hasNext() ) {
obj = it.next();

if( ! currentItems.remove(obj) ) {
itemsVisible.add(obj);
}
}

ArrayList hiddenItems = currentItems;
currentItems = list;

if( itemsVisible.size() > 0 || hiddenItems.size() > 0 ) {
if( ! listenerList.isEmpty() ) {
ColumnViewer v;
v = AbstractViewerRowVisibilityStateSupport.this.columnViewer;

ViewerRowStateChangedEvent ev = new ViewerRowStateChangedEvent(v);
ev.itemsHidden = hiddenItems;
ev.itemsVisible = itemsVisible;

Object[] listeners = listenerList.getListeners();
ViewerRowStateChangeListener l;

for( int i = 0; i < listeners.length; i++ ) {
l = (ViewerRowStateChangeListener)listeners[i];
l.itemStateChangedListener(ev);
}
}
}
}
};
addListeners(getControl(),l);
}

protected abstract void addListeners(Scrollable control, Listener l);

protected abstract ViewerRow getTopRow();

protected Scrollable getControl() {
return (Scrollable)columnViewer.getControl();
}

public void addItemStateListener(ViewerRowStateChangeListener listener) {
listenerList.add(listener);
}

private ArrayList recalculateVisibleItems() {
ArrayList list = new ArrayList(100);
ViewerRow topRow = getTopRow();

if( topRow != null ) {
int totalHeight = getControl().getClientArea().height;
int itemHeight = topRow.getBounds().height;

list.add(topRow);

int tmp = topRow.getBounds().x+itemHeight;
// tmp += itemHeight;
// this would be more precise but half rows
// would be marked as non-visible

// run until we reached the end of the client-area
while( tmp < totalHeight ) {
tmp += itemHeight;
topRow = topRow.getNeighbor(ViewerRow.BELOW, false);

if( topRow == null ) {
break;
}

list.add(topRow);
}
}

return list;
}
}
We simply listen to events who can change the items shown in the Scrollable make a diff to the last state and inform all consumers about the change.

But what are the events how modify the items shown? This is delegated to specialized classes for Table and Tree because those may differ from control to control.

Step 3: Provide specialized implementation for Tree and Table
- An implementation for SWT-Table
public class TableViewerRowVisibilityStateSupport extends
AbstractViewerRowVisibilityStateSupport {
public TableViewerRowVisibilityStateSupport(TableViewer columnViewer) {
super(columnViewer);
}

protected void addListeners(Scrollable control, Listener l) {
control.getVerticalBar().addListener(SWT.Selection, l);
control.addListener(SWT.Resize, l);
control.addListener(SWT.KeyUp, l);
}

protected ViewerRow getTopRow() {
Table t = (Table)getControl();
int index = t.getTopIndex();
TableItem topItem = t.getItem(index);

if( topItem != null ) {
return (ViewerRow) topItem.getData(ViewerRow.ROWPART_KEY);
}

return null;
}
}
- An implementation for SWT-Tree
public class TreeViewerRowVisibilityStateSupport extends
AbstractViewerRowVisibilityStateSupport {
public TreeViewerRowVisibilityStateSupport(TreeViewer columnViewer) {
super(columnViewer);
}

protected void addListeners(Scrollable control, Listener l) {
control.getVerticalBar().addListener(SWT.Selection, l);
control.addListener(SWT.Resize, l);
control.addListener(SWT.MouseUp, l);
control.addListener(SWT.KeyUp, l);
}

protected ViewerRow getTopRow() {
TreeItem topItem = ((Tree)getControl()).getTopItem();
if( topItem != null ) {
return (ViewerRow) topItem.getData(ViewerRow.ROWPART_KEY);
}
return null;
}
}
As said this is very rough first draft how this could work. I'll post this got some coments about possible issues this could provoke.

19 comments:

Anonymous said...

Hi Tom,

when figuring out how virtual Trees and Tables work in Eclipse I came across the same problem: how can I find out which Tree/Table items are not visible anymore so that I can despose them?

I solved the problem with an SWT.SetData listener in the inputChanged method in my class that implements ILazyTreeContentProvider.

So when I scroll down, I get a notification in the handleEvent method for every TreeItem that gets displayed and via tree.getTopItem() and TreeItem.indexOf() I can track with own code which TreeItems get 'scrolled-out'. I would like to post some more code here to make this more clear, but it's very inconvenient with this little html-formular here to post source code so that it looks good.

It's a little tricky but this way you can keep track of the TreeItems that are visible/non-visible. It works similar for Tables of course.

Tom said...

Is the SetData-Callback also called when you scroll up once more? Nevertheless this only works for tables/trees with the virtual bit set whereas this works for in theory for any table/tree.

Steve said...

Can you give me a concrete example (better yet, a snippet) that shows why you might need this?

Tom said...

Well the reason is fairly simple look at
Bug 36977

Steve said...

I went to the bug report and it contained too much text for me to understand. Can you summarize it?

NOTE: I'm not trying to start an argument or claim you don't need the feature etc. etc.

Anonymous said...

@Tom: I am clearing the items that get out of sight and I get notified via the SetData-Callback when I scroll up again.
I would prefer your solution from now on as it also works for non-virtual trees/tables.

@Steve: I see the use case where you have really large tables/trees with thousands or even ten-thousands of items, maybe in an RCP application. Someone could need to dispose resources for items that are no longer visible so that the memory-consumption of the RCP application doesn't get too huge.

Tom said...

Ludwig is right in my case I have a table who has potentially different images per row. So it makes sense to me to dispose e.g. the top most image when the user scrolls the table-rows out of view.

The bug report I linked simply request e.g. to use Button(SWT.CHECK) to use in viewers instead of the current work-around of diplaying an image if you want a table who displays a check-box in the e.g. second row.

Steve said...

I understand the virtual case (See https://bugs.eclipse.org/bugs/show_bug.cgi?id=163432).

The non-virtual case seems error prone to me (but it is possible that you have every case).

Tom said...

Is there something SWT could provide like an event similar to the one it provides for virtual tables with SetData? Something like ItemShown/ItemHidden?

Nicolas Richeton said...

Hi,

I had the same problem, and handled it at the SWT level. Details are in the bug 163432 that Steve linked. Basically, I used a cache that limits the number of images in memory. When the cache is full, older images are disposed. But I'm still not happy with this, especially with the fixed size of the cache that should depend of the number of visible items.

Another problem is that I use a custom widget (see : http://sharemedia.free.fr/swtgallery_home.php) and I want to keep the API as close as possible to Tree and Table. This widget let the user define the way items are displayed so I can't suppose they are drawn from top to bottom.

In your code, you are looping on visible items. Does this slow down scrolling ?

I like Tom's idea of an swt event fired when an item toggles between visible and invisible. Another way of doing this could be to have a method that returns only visible items (may be easier to implement).

--
Nicolas

Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
Gold Guide for World of Warcraft said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.
WOW GOLD said...
This comment has been removed by a blog administrator.
WOW GOLD said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.