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.name
instead of writingLabelProviders
. - Sorting based on data values.
- Convenient and straightforward builder API.
Example
Let’s say we want to build such a table:
Image may be NSFW.
Clik here to view.
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:
TableViewerBuildert=newTableViewerBuilder(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:
TableViewerBuildert=newTableViewerBuilder(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:
Image may be NSFW.
Clik here to view.
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
:
ColumnBuildercity=t.createColumn("City");city.bindToProperty("name");city.build();ColumnBuilderpopulation=t.createColumn("Population");population.bindToProperty("stats.population");population.build();ColumnBuilderarea=// ...ColumnBuilderdensity=// ...ColumnBuilderfoundingDate=// ...ColumnBuilderneighborCity=// ...
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:
ColumnBuilderdensity=t.createColumn("People/km²");density.bindToValue(newBaseValue<City>(){@OverridepublicObjectget(Citycity){returncity.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:
Image may be NSFW.
Clik here to view.
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(newDecimalFormat("#,##0")));area.format(Formatter.forDouble(newDecimalFormat("0.00 km²")));density.format(Formatter.forDouble(newDecimalFormat("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(newICellFormatter(){publicvoidformatCell(ViewerCellcell,Objectvalue){intpopulation=(Integer)value;intcolor=(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(newCellLabelProvider(){/* ... */});
The result so far:
Image may be NSFW.
Clik here to view.
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(newPropertyValue("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(newDecimalFormat("0.00")));foundingDate.makeEditable(Formatter.forDate(SimpleDateFormat.getDateInstance(SimpleDateFormat.MEDIUM)));
You can also set your own cell editor, for example:
ComboBoxViewerCellEditorcityComboEditor=newComboBoxViewerCellEditor(t.getTable(),SWT.READ_ONLY);cityComboEditor.setContenProvider(newArrayContentProvider());cityComboEditor.setLabelProvider(newLabelProvider());cityComboEditor.setInput(RandomData.CITIES);neighborCity.makeEditable(cityComboEditor);
Result:
Image may be NSFW.
Clik here to view.
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