Building tables made easy: TableViewerBuilder
In my last RCP training the participants complained a bit about the JFace TableViewer API. I have to agree, tables are the bread and butter of many RCP business applications and binding tables to your data model is a somewhat cumbersome process that usually yields a lot of boilerplate code.
This was eased a bit with the latest additions to JFace Data Binding, which allow you to use data binding to bind the columns of TableViewers. Have a look at the JFace data binding snippets Snippet017TableViewerWithDerivedColumns and Snippet032TableViewerColumnEditing to find out how that works. JFace Data Binding is perfect if the binding between the table UI and the tabel model data is bi-directional.
But many table bindings are a passive thing, they display and modify data in a model object but the model object doesn’t change by itself and you wouldn’t expect the table to refresh automatically if the model changes. This use case is usually implemented using TableViewer and custom CellLabelProviders.
I just finished a builder class TableViewerBuilder, which makes it easy to setup TableViewers. Features of TableViewerBuilder are:
- Clear separation between data value (like a Date object), formatted value (like a Date object formatted as String) and cell formatting (like dates in the past are colored blue).
- Binding the columns using nested property Strings like
company.country.nameinstead of writingLabelProviders. - Sorting based on data values.
- Convenient and straightforward builder API.
Example
Let’s say we want to build such a table:

If you want to see a full example right away, here you go: Snippet01TableViewerBuilder
TableViewerBuilder
At first you create a TableViewerBuilder. This instantly creates a Table widget and a TableViewer for you. The given parent Composite needs to be empty, because TableColumnLayout is used internally. For example:
TableViewerBuilder t = new TableViewerBuilder(parent);
Columns
You can create columns by calling createColumn on the TableViewerBuilder object. This returns a ColumnBuilder that can be used to configure the table column. When you have finished configuring the column, you have to call build() on the ColumnBuilder to create the actual column:
TableViewerBuilder t = new TableViewerBuilder(parent);
t.createColumn("City").build();
t.createColumn("Population").build();
t.createColumn("Area").build();
t.createColumn("People/km²").build();
t.createColumn("Founding date").build();
t.createColumn("Neighbor city").build();
This is the result:

Setting the input data
To set the data to be shown in the table, you can get the TableViewer from the TableViewerBuilder and set a ContentProvider and Input object:
t.getTableViewer().setContentProvider(someContentProvider);
t.getTableViewer().setInput(someInput);
If you want to use a Collection as model for your table, you can also use setInput on the TableViewerBuilder with a Collection - this automatically sets an ArrayContentProvider for you:
t.setInput(someCityCollection);
Binding columns
In this example someCityCollection is a list of City objects. The columns of the table can be bound to a property of these objects using bindToProperty:
ColumnBuilder city = t.createColumn("City");
city.bindToProperty("name");
city.build();
ColumnBuilder population = t.createColumn("Population");
population.bindToProperty("stats.population");
population.build();
ColumnBuilder area = // ...
ColumnBuilder density = // ...
ColumnBuilder foundingDate = // ...
ColumnBuilder neighborCity = // ...
You can also bind a column to an arbitrary value using bindToValue. In the example this helps with the “People/km²” column which is calculated from two other values:
ColumnBuilder density = t.createColumn("People/km²");
density.bindToValue(new BaseValue<City>() {
@Override
public Object get(City city) {
return city.getStats().getPopulation() / city.getStats().getAreaKm2();
}
});
density.build();
This is the result. Please note that the user can already sort the table by clicking on the table headers:

Formatting values
In this example, the number and date values need to be formatted. You can set a formatter on the column objects for this:
population.format(Formatter.forInt(new DecimalFormat("#,##0")));
area.format(Formatter.forDouble(new DecimalFormat("0.00 km²")));
density.format(Formatter.forDouble(new DecimalFormat("0")));
foundingDate.format(Formatter.forDate(SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM)));
Formatter is a factory class for commonly used formatters (mainly based on java.text.Format). You can always implement the IValueFormatter interface yourself. Please note that formatting has no influence on the sort order, because the comparator responsible for the table sorting uses the raw data values, not the formatted values.
Formatting cells
Sometimes you want to format the cell besides the textual value, for example to customize colors or to set images. You can do that by configuring a cell formatter for the table column:
population.format(new ICellFormatter() {
public void formatCell(ViewerCell cell, Object value) {
int population = (Integer) value;
int color = (population > 5000000) ? SWT.COLOR_RED : SWT.COLOR_BLACK;
cell.setForeground(cell.getControl().getDisplay().getSystemColor(color));
}
});
If your column is not text based (for example a column with images that are owner-drawn), you can use a custom CellLabelProvider instead of a value and a value formatter:
someColumn.setCustomLabelProvider(new CellLabelProvider() { /* ... */ });
The result so far:

Column width and align
Use setPercentWidth, setPixelWidth and align to format the cell width and alignment:
city.setPercentWidth(60);
foundingDate.setPixelWidth(100);
foundingDate.alignCenter();
area.alignRight();
Sorting
You can set the default sort column by calling useAsDefaultSortColumn:
city.useAsDefaultSortColumn();
If you want to sort the column by a value that is different from the original value, you can set a custom value using sortBy:
city.sortBy(new PropertyValue("stats.otherValue"));
Cell editing
You can make columns editable using makeEditable. By default, you get a text cell editor with no formatting applied:
city.makeEditable()
For editing formatted values, you need to specify a formatter which is also responsible to parse the value back from the String:
population.makeEditable(Formatter.forInt());
area.makeEditable(Formatter.forDouble(new DecimalFormat("0.00")));
foundingDate.makeEditable(Formatter.forDate(SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM)));
You can also set your own cell editor, for example:
ComboBoxViewerCellEditor cityComboEditor = new ComboBoxViewerCellEditor(t.getTable(), SWT.READ_ONLY);
cityComboEditor.setContenProvider(new ArrayContentProvider());
cityComboEditor.setLabelProvider(new LabelProvider());
cityComboEditor.setInput(RandomData.CITIES);
neighborCity.makeEditable(cityComboEditor);
Result:

Download
I hope this helps with building JFace TableViewers. Feedback and contributions are very appreciated: eMail.
You can see the full example code here: Snippet01TableViewerBuilder.
TableViewerBuilder is part of my de.ralfebert.rcputils plug-in which can be downloaded as source project from github: de.ralfebert.rcputils


This looks very promising! I am looking for a way to bind lists in my data model to tables with editable cells by using convention over configuration so that my business-specific code can be as minimal as possible. So I will give your utils a test drive and maybe wrap them in my own table data binder utility.
Unfortunately though, the link to your Snippet01TableViewerBuilder does not work at the moment. Could you fix this?