Hide/Show columns of a JTable

The ability to dynamically hide and re-show columns of a JTable is often asked for. Letting the user decide which columns of a table she wants to see at any given moment is not just good style but can improve readablity.
Unfortunately there is no built-in support for invisible columns in the swing table component. But then, luckily there is some functionality to built upon.

Jumpstart: If you're in a hurry grab the code: Let's Do It

Inner workings of JTable et. al.

Each JTable uses an instance of TableColumnModel to keep track of all columns. The interface TableColumnModel specifies the protocol used to add, move, remove columns and query for TableColumn objects. These TableColumn objects hold data such as the column index in the table model it resembles, the column's width and its cell renderer and editor. The DefaultTableColumnModel, ahm, well, is just that: Whenever you don't supply an column model to the JTable constructor, an instance of this class is created and populated with one column for each column in your table model.
It's important to separate your idea of a column in the TableColumnModel from a column in the TableModel.
There can be multiple TableColumn objects for each column in the TableModel (perhaps showing the same value in different currencies). Column indices in both JTable and TableColumnModel refer to the visible columns and indices in TableModel refer to actual index in the data. Off course it is this indirection that allows for columns to be reordered. JTable offers you two methods to convert between these indices:
int convertColumnIndexToModel(int viewColumnIndex)
int convertColumnIndexToView(int modelColumnIndex) Will return -1 if this column is not visible else it will return the viewIndex of the first (leftmost) column with this model index.

The Simple Solution

The simplest way to hide a column and re-show it later is off course to remove it and add it again later.
The following methods from JTable just delegate to the same methods defined in TableColumnModel
void addColumn(TableColumn aColumn)
void removeColumn(TableColumn column)
This is how it was done in any previous examples I have found.

One shortcoming is that columns are always added to the right. That's not what I expect if I hide a column in my sophisticated table and immediatly make it visible again. Columns should keep their order even when invisible. moveColumn() to the rescue: void moveColumn(int columnIndex, int newIndex)
The problem then is to determine the newIndex. It just won't work to use the last visible position, because other columns might have been reordered and/or made invisible, so changing the overall column indices.

Another nuisance is the need to store the invisible TableColumn objects someplace. As soon as you called removeColumn(aColumn) noone else is holding a reference to aColumn anymore. If you want to make the same column visible again you must either throw it into some collection or construct it from scratch. The former alternative is much more practical because it saves you from remembering the columns properties.

Improved Solution

What does it mean after all, that columns retain their positions when made invisible and visible again? As long as you don't reorder columns it simply means their relative order stays the same no matter in which order you hide/show any columns. In order to see what should happen if you reorder columns while other columns are invisible, imagine these columns in your model:
A B (C) D E
The parantheses indicate that C has just been made invisible. If the user drags column A to a new position between D and E it is quite clear that new order is:
B (C) D A E
The only decision to make is what should happen if a column is dragged at a position where an invisible column rests: Now drag E and drop it between B and D. We could either decide to always position to the left or to the right of any invisible columns or to always exclude or include the invisible columns in the drag distance. I choose the last alternative, so the new order is:
B (C) E D A

There is an easy implementation for this behaviour: Put all columns (both visible and invisible) in a vector. If any column is moved then remove it from the old position and insert it into the new position. The vector will adjust positions of the remaining elements accordingly.

In fact this is exactly what the DefaultTableColumnModel does for the visible columns. The vector of all columns also serves the purpose of keeping a reference to invisible columns, so that they can be retrieved later on and they aren't garbage collected.
So we go ahead and subclass the default column model to get an extended version that can handle invisible columns.


public class XTableColumnModel extends DefaultTableColumnModel {
    protected Vector allTableColumns = new Vector();
    public void setColumnVisible(TableColumn column, boolean visible);
    public boolean isColumnVisible(TableColumn aColumn);
Let's throw in a method to query for the (first) column that shows a certain column index from the table model. We'll need this if we want to retrieve an invisible column and pass it to setColumnVisible():
    public TableColumn getColumnByModelIndex(int modelColumnIndex)
And then add a convenience method to show all invisible columns again:
    public void setAllColumnsVisible();
For JTable and other table column model listeners to work as before it is important that the column model behaves as if invisible columns have been removed from the model. So the columns, count and index in each of these functions still refer to the visible columns only: getColumnCount(), getColumns(), getColumnIndex(), getColumn()
You can see in the javadoc that I added methods with the same name that let you specifiy wether you want to have invisible columns take into account.
To keep the vector of all columns up to date, these methods have to be overridden:
    void addColumn(TableColumn aColumn);
Just also add the column to the allTableColumns vector.
    void removeColumn(TableColumn column);
Remove the column from the allTableColumns vector.
    void moveColumn(int columnIndex, int newIndex);
Find the corresponding column objects in the allTableColumns vector.# and move them accordingly.
All these let the superclass do the real work.
Now take a closer look at the most important method:
    public void setColumnVisible(TableColumn column, boolean visible);
If the column is made invisible the call is simply rerouted to super.removeColumn(column);, while leaving allTableColumns as is.
To make the column visible again, we find its new position, then call super.addColumn(column) and super.moveColumn(tableColumns.size() - 1 /* rightmost position */, newPosition);
The newPosition is calculated by counting all visible column in allTableColumns up to column.
For details check out the source code.
There is one gotcha with this design: If you currently have invisible columns and change your table model the JTable will recreate columns, but will fail to remove any invisible columns.

Alternative Implementations

Off course there are alternatives to this implementation. Here are those that come to my mind:

Let's Do It - Put It to Use

Take this code, and do with it whatever you like, but
Here is the source:XTableColumnModel.java
The javadoc generated class documentation:XTableColumnModel.html
And this is a little quick and dirty example I put together:TableDemo.java

Using the extended column model requires two simple steps:
  1. Make XTableColumnModel your table column model
    If you have used your own column model before you know how to replace it with XTableColumnModel. However it is more probable that you relied on JTable creating a default table column model for you. Here is what you do to replace it:
    Create your table as usual:JTable yourTable = new JTable(...);
    Create and remember a XTableColumnModel:yourColumnModel = new XTableColumnModel();
    Make your table use the new column model:yourTable.setColumnModel(yourColumnModel);
    Create columns:yourTable.createDefaultColumnsFromModel();

  2. Get hold of the TableColumn you want to show or hide and call setColumnVisible(), e.g. to hide the very first column:
    TableColumn column  = columnModel.getColumnByModelIndex(0);
    yourColumnModel.setColumnVisible(column, false);
Any comments and suggestions are welcome: