Ultra-powerful tables with SwingX and FilterHeader in a few moments
August 11, 2011
java swing jtable tables swingx filterheaderCode can be found on Github - https://github.com/ivanporty/swingtools
Previously we’ve created a really powerful addition to the tables – the model which automatically figures out which columns are available from the given data set, their types which will determine the rendering and editing data. The performance of that model is extremely high as well especially on very large data sets comparing to more heavyweight beans binding solutions which is always a point of interest when working with tabular data that tend to grow huge.
Let’s continue on user experience side. Standard JTable does not provide almost anything to make user’s life easier, comparing to its quite famous colleague, pivot table from Microsoft Excel, which allows you to do various filtering and even calculate trends and functions based on data in the table. We’ll leave functions and calculations aside, it’s too complicated and will take a lot of time, and rarely needed, most of such functions are done by the application business layer instead. If you still need something like that where logic is interleaved with presentation, you can either use JIDE’s grids implementation (though they are commercial, behave differently and cost quite a bit) or attach scripting with JDK’s built-in Javascript and use it as a macro language.
We’ll concentrate on simpler usability and appearance things which are still missed from standard JTable, but frequently required by users. First of all, sorting is available now in the JTable, but disabled by default. But more advanced sister of JTable, JXTable from open-source SwingX set, does enable sorting by default, so it will be a good idea to always start with JXTable. The other powerful thing from JXTable is that it allows you to hide columns and restore them back, and more than that, provides a menu to do that with a custom button located in upper right corner of the scroll pane where a JXTable is put. One of the most powerful features of SwingX table is that it allows you to do highlighting over the rows based on some criteria, including simple striping, i.e. interleaving colors between even and odd rows of the table which makes them easier to read. Let’s start:
public class BaseTable extends JXTable {
public BaseTable() {
// always enable column control and striping
setColumnControlVisible(true);
setHighlighters(HighlighterFactory.createSimpleStriping());
}
}
Moving on, there is filtering built in into the standard JTable, but it’s headless (has no UI) and requires some effort to enable and write filters programmatically. Meanwhile, there are no default UI components to do the simplest filtering on the table’s data, neither in core JTable nor in JXTable. Fortunately, there’s a nice open-source solution available from Coderazzi and it’s available from the central Maven repository. It’s extremely easy to use – we just need to create a new instance of table filter header UI component and pass our table into it. Table filter will track model and data changes and update itself automatically. It makes sense to include the table filter header directly into our advanced table:
public class BaseTable extends JXTable {
/** Filter header used in the table */
private TableFilterHeader tableFilterHeader;
/** Flag to enable or disable filter header */
private boolean filterHeaderEnabled;
public BaseTable() {
// always enable column control and striping
setColumnControlVisible(true);
setHighlighters(HighlighterFactory.createSimpleStriping());
}
public boolean isFilterHeaderEnabled() {
return filterHeaderEnabled;
}
/**
* Enables or disables and hides filtering header
* @param filterHeaderEnabled True to enable and attach filter header component
*/
public void setFilterHeaderEnabled(boolean filterHeaderEnabled) {
// if filter was off and enabled, create and attach it
if (filterHeaderEnabled && !this.filterHeaderEnabled && tableFilterHeader == null) {
// enable the filter header
tableFilterHeader = new TableFilterHeader(this);
}
if (tableFilterHeader != null) {
tableFilterHeader.setVisible(filterHeaderEnabled);
tableFilterHeader.setEnabled(filterHeaderEnabled);
}
this.filterHeaderEnabled = filterHeaderEnabled;
}
/**
*
* @return Filter header component used in the table or null if never used
*/
public TableFilterHeader getTableFilterHeader() {
return tableFilterHeader;
}
}
As you can see from the code, we don’t attach the filter header directly and forever – you can disable or enable it back, or get it and manipulate it directly. There’s a reason behind that – although quick and simple filtering using the header is nice, it does involve some overhead, especially when new large bulk of data (rows) arrive. By default the filter header is disabled, but it’s easy to enable it only by setting the appropriate flag to true.
Another thing that is missing from the both JTable and JXTable is tooltips for the cells. As we all know, most of the time the cells are rendered by the JLabel, which hosts some form of text representation of the value in that cell. To enable tooltip, you need that label to have it installed with setToolTipText(). Override all renderers and call it explicitly? Luckily, no. There is a helper method in JTable class which gets called every time before renderer got prepared to be painted in the cell. Since it’s a single point of access to all of the renderers installed on the table, we can override it and do a simple job of enabling tooltips there:
/**
* Overriden to set the tooltip text for the label-based renderers.
* @param renderer
* @param rowIndex
* @param vColIndex
* @return
*/
@Override
public Component prepareRenderer(TableCellRenderer renderer,
int rowIndex, int vColIndex) {
Component c = super.prepareRenderer(renderer, rowIndex, vColIndex);
if (c instanceof JLabel) {
JLabel label = (JLabel) c;
label.setToolTipText(label.getText());
}
return c;
}
}
Taking the demo from previous article, where we dynamically populated model with beans, let’s do the same, but use new BaseTable with advanced functionality and filter header enabled:
BaseTable table = new BaseTable();
table.setFilterHeaderEnabled(true);
add(new JScrollPane(table));
BeanPropertyTableModel<TableBean> model = new BeanPropertyTableModel<TableBean>(TableBean.class);
model.setOrderedProperties(Arrays.asList("name","surname","date"));
model.setData(TableBean.generateList(100));
table.setModel(model);
We’ll have the following – notice that you can hide and show all columns or adjust them to their preferred width (“pack”):
Tooltip are available for all cells by default, without dealing with renderers individually. And filtering works like a charm (in this screen, first column shows everything starting with Z, seconds everything starting with P – both of them should be true so we have only one such a row):
Much better and more usable than the standard JTable, and still as fast as it is.