Virtual Tables are a new selling point of the SWT framework. Being new to SWT myself, I tried to hunt down information on the topic with minimal success. The SWT JavaDocs are as weak for this feature as every other feature. The SWT books at the local Chapters don't even mention Virtual Tables. Pages upon pages of Google hits link to casual mention about how virtual tables are good, great, and/or fast. Either that or comments by people like myself, who like most developers, really just want an example to clarify things. Then the clouds parted and the light shone on a random SWT book errata website. Where there's an Errata, there's sample code. So in an attempt to save others some time and effort, I've decided to author a very brief tutorial on the subject.
Virtual Tables offer a huge performance boost over standard Tables. Tables which are not "Virtual" contain n items, each with their own distinct TableItem Object from Table initialization. Virtual Tables offer a savings by restricting creation of TableItem Objects to only those items which have been in the user's view. If the view changes, more TableItems are created as required. But before your eyes glaze over, here is a simple example to get you started.
Example 1. A Simple Virtual Table
Step 1: Create your table with whatever attributes you want, including the SWT.VIRTUAL flag.
Table virtualTable = new Table(shell, SWT.MULTI | SWT.FULL_SELECTION | SWT.VIRTUAL | SWT.BORDER); virtualTable.setHeaderVisible(true); TableColumn column = new TableColumn(virtualTable, SWT.RIGHT); column.setText("My Virtual Table"); column.setWidth(480);
Now you have a virtual table with one column that contains no data.
Step 2: Tell the table how large your data set is.
String[] exampleData = ... virtualTable.setItemCount(exampleData.length);
Now the virtual table knows how many items it should contain but it still doesn't contain any. Virtual tables are populated via a callback method dressed up as a Listener. This could be written as an annonymous class, but I chose clarity over brevity for the purposes of this example.
Step 3: Add a SetData callback listener.
// Set a "set data" event listener as a callback method on the Table virtualTable.addListener(SWT.SetData, new CallbackListener()); class CallbackListener implements Listener { public void handleEvent(Event event) { TableItem item = (TableItem)event.item; int tableIndex = virtualTable.indexOf(item); item.setText(exampleData[tableIndex]); } }
Now this may seem a little magic to some of you so I will explain it fully. Whenever the view of a Virtual Table changes, the Table creates a TableItem and stores it in an event. The registered "SetData" Listener (dubbed CallbackListener in the example) is then alerted via a call to handleEvent with the event containing the TableItem. Since the TableItem has already been created within the Table, it has a valid index. In order to correctly fill it with data, we need to determine what that index is. Then set the text on that TableItem to the corresponding value from our example data. That's just about as hard as it gets for most use cases.
It's pretty easy once you get over the first hurdle of having no idea how to proceed. The only real trick in this is the callback method. SWT.SetData not only suffers from inconsistencies with coding style conventions, but the JavaDoc description is weak to say the least. The JavaDoc for SetData provides the following cyclic and misleading definition: "The set data event type (value is 36)." By that definition, one could reasonably assume that a listener of that type would receive events when data is set, similar to a PropertyChangeListener for JavaBeans. And maybe it works that way in some cases, but in the Virtual Table usage, it is used for initialization. It could even be stretched to apply in this case if you think of creating a TableItem as setting data, but that's a stretch. Fundamentally, the problem getting started with SWT is a lack of sufficient information on the topic.
The next, more complex example deals with a problem that you may run into while using Virtual Tables in complex or custom ways. The Table's SetEvent event system works in such a way that events are fired only the first time that a TableItem is requested for view to initialize it. From this it can be reasoned that Virtual tables were created for the following use cases:
This isn't a problem that affects everyone. It affects use cases for table sorting or table filtering where sorting and filtering is done by anything other than a JFaces TableViewer. In those use cases, you cannot take full advantage of Virtual Tables. This is a result of often having to add in the middle, or explicitly update all values on a change to Table sorting. That's not to say that Virtual Tables aren't worthwhile in those cases, just that the full performance benefit that Virtual Tables offer can't always be realized. Thankfully, you can use a Virtual Table just like a normal table without incident. This allows you to use the Virtual Table in a hybrid way to get performance gains in some cases. The best way to describe this is with another example.
Example 2. A Table with Filtering
Step 1: Recreate Example 1 replacing the exampleData String array with a List of the same name.
Step 2: Apply a text filter of some variety. The one used here is fictional
// Create a text filter and apply it to the example data TextFilter textFilter = new TextFilter(exampleData); textFilter.addFilterListener(this); /** * The filter listener method */ public void handleTextFilterEvent(TextFilterEvent e) { // Deletes are the same for all types of tables if(e.getType() == TextFilter.DELETE) { virtualTable.remove(e.getIndex()); // Inserts are handled different depending where they occur } else if(e.getType() == TextFilter.INSERT) { // Add in the middle of the table so you need to manually add it, if(e.getIndex() < virtualTable.getItemCount()) { TableItem item = new TableItem(virtualTable, 0, e.getIndex()); int sourceIndex = textFilter.getSourceIndex(e.getIndex()); item.setText(exampleData.get(sourceIndex)); // Add at the end of the table, so it is a 'Virtual' add } else { virtualTable.setItemCount(virtualTable.getItemCount + 1); } } }
Step 3: Modify the CallbackListener to use the TextFilter as a map between Table index and List index.
class CallbackListener implements Listener { public void handleEvent(Event event) { TableItem item = (TableItem)event.item; int tableIndex = virtualTable.indexOf(item); int sourceIndex = textFilter.getSourceIndex(tableIndex); item.setText(exampleData[tableIndex]); } }
This example demonstrated the complex task of filtering using Virtual Tables, without needlessly complicating things with the actual details of filtering. The strategy used in this example offers performance benefits most of the time. The worst case exists in the event that the last item isn't filtered out. When the text filter is removed, all of the elements that were filtered out will now be explicitly added to the list. The only way to perform better than this, would require extension of Table to possibly gain direct access over the created TableItems. But extension of Table is a dangerous practice in SWT based on the warning in the JavaDocs. As such, the method described in the example is the best bet.
The GlazedLists project is working towards full support for SWT. That support includes Virtual Tables. At this time, SWT support in GlazedLists is available only in CVS and as a developer preview. However, it is worthwhile to provide an example of how easy it is to create a filtered, sorted table that supports the SWT.VIRTUAL style. For this example, familiarity with GlazedLists is assumed as a full explanation is beyond the scope of this tutorial. If you don't know about GlazedLists, find out more at the GlazedLists website.
Example 3. Virtual Tables with GlazedLists.
Step 1: Create the EventLists which handle the transformations for the table data.
EventList exampleEventList = new BasicEventList(); // Add sorting and filtering SortedList exampleSortedList = new SortedList(exampleEventList); TextFilterList exampleTextFiltered = new TextFilterList(exampleSortedList); // Fill the example list with data populateExampleData();
Step 2: Create the table with whatever attributes you want, including the SWT.VIRTUAL flag.
Table virtualTable = new Table(shell, SWT.MULTI | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.BORDER | SWT.VIRTUAL); virtualTable.setHeaderVisible(true); TableColumn column = new TableColumn(virtualTable, SWT.RIGHT); column.setWidth(480);
Step 3: Connect the Table to the data.
EventTableViewer virtualTableViewer = new EventTableViewer(exampleTextFiltered, virtualTable, new SimpleTableFormat()); /** * A simple table formatter for the EventTableViewer */ class SimpleTableFormat implements TableFormat { public int getColumnCount() { return 1; } public String getColumnName(int column) { if(column == 0) { return "My Virtual Table"; } return null; } public Object getColumnValue(Object baseObject, int column) { if(baseObject == null) return null; if(column == 0) { return baseObject.toString(); } return null; } }
Step 4: Add the sorting and filtering triggers.
// Add sorting triggered by clicking on the table headers new TableComparatorChooser(virtualTableViewer, exampleSortedList, false); // Add text filtering from a Text box Text filterText = = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER); exampleTextFiltered.setFilterEdit(filterText);
Well, that's all there really is to it! This last example showed the ease of adding Virtual Tables to your GlazedLists SWT application. The most interesting part about this example, is that the only difference between using regular Tables and Virtual Tables with GlazedLists is one style constant on a single line of code.
Virtual tables are a powerful and easy to use feature to add to your SWT toolbox. Using Virtual tables will result in a significant performance boost in any application that displays large amounts of data in Tables. This tutorial demonstrated some simple and complex examples to help you if you are just starting out. I hope that you found this tutorial useful. If you have any questions or comments please feel free to email me at kevin@swank.ca