Customize your JTable for an enriched Swing experience

Note: Click here to download the source code for this article.

When developing Java code using a GUI component I have never used, I like to experiment by writing small sample programs, exploring the best ways to enhance the user experience.

This article is designed for programmers who want to understand the Swing component JTable and find out how to customize it. It is not designed for developers who have used these concepts in depth. However, if you use IDEs to generate code, I believe it is important to understand the code that is generated.

JTable is a user interface component that presents data in a two-dimensional table format. It is a very customizable class that allows users to create a basic table quickly and easily and, with a little time and effort, a complex table with custom controls, fonts, and colors.

There are several ways to create a quick JTable. I find the simplest method is to use String arrays. For example:


      String rowData[][] = new String[][] {
         { "row 1, col 1", "row 1, col 2", "row 1, col 3" },
         { "row 2, col 1", "row 2, col 2", "row 2, col 3" } };
      String colHeadings[] = new String[] {
         "Column 1", "Column 2", "Column 3" };
      tableView = new JTable(rowData,colHeadings);
tableView is then added to your layout like any other control. If you are not yet familiar with building a window in Java, download my listings and review Listing 1 in depth. The most important thing to understand about JTable is that, along with a lot of other Swing components, it doesn't include scrollbars, so it needs to be placed within a JScrollPane. This type of table works great for static data and window mockups, but it is extremely inflexible and non-scalable.

A Useful Table
Most Swing components have implemented the Model-View-Controller (MVC) architecture. This concept splits components into three logical parts. In the JTable, a GUI class represents the View component, while a Model class represents both the Model and the Controller. The GUI class does the painting of the table, and the Model class represents the data and controls the events. This is a typical breakdown of Swing components. All of the JTable classes, except the JTable itself, are stored in the javax.swing.table package.

The first class you should become familiar with is TableModel, which is the model in the MVC architecture. It provides a "getter" and a "setter" for the table data, as well as useful helper functions. Because the Swing developers have provided default implements of most of these methods in the AbstractTableModel class, we only have to override the following three methods based on our representation of the data:


  public int getRowCount();
  public int getColumnCount();
  public Object getValueAt(int row, int column);
For these examples, I use the Vector class to store the test data. A JTable also offers editable cells where the user can manipulate the data within the table. If you want editable cells within your table, you also need to override setValueAt. Although not discussed in this article, you are encouraged to play with editable cells after familiarizing yourself with the other concepts. You will also want to add some of your own functions, such as:


   public void addRow(Object o[]);
   public void clear();
   public void removeRow( int i );
These functions are easily written using Vector functions and the JTable event helper functions from the base class.


   public void clear() {
      data.removeAllElements();
      fireTableDataChanged();
   }
   public void removeRow( int i ) {
      data.removeElementAt(i);
      fireTableRowsDeleted(i,i);
   }
   public void addRow(Object o[]) {
      data.addElement(o);
       // entry added to the end, so fire an event to notify the table
       // to update itself
      fireTableRowsInserted(data.size()-1,data.size()-1);
   }
The AbstractTableModel fireTableXXX functions make it extremely simple to notify the control that data has changed. You do not have to force any repainting or resizing yourself.

Looking at Listing 2, you can now add and remove data from a fully functional table. This is perfect for displaying a list to a user, based on some choices, and having them select one row.

Notice in Listing 2 that I also added a third column that is of type Integer. This is important later because the table knows to deal with Integer class objects differently than String class objects. In this example, you can see that the JTable will right-justify the Integer values when they are displayed. JTable also understands how to render objects, numbers, doubles, dates, booleans, and icons. Date objects are displayed as Month, Day, Year. All of these can be overridden, but they can save a lot of time if the default presentation is acceptable.

The First Fancy Trick
Each column within a table is represented by a TableColumn, which is managed by the TableColumnModel. Most of what follows can be done on a per-column basis, as well as the whole table. For simplicity, we will only modify the TableModel, which represents the entire table. The renderer will be used for all cells in the table because we do not specify one for any individual column.

I have always found a plain white background to be hard on the eyes, so the first trick I taught myself with JTable was how to paint every other line a different color. This allows me to display very wide tables to my users without them having a problem matching the first column with the last column of data (see Figure 1).

Figure 1
Figure 1. JTable with alternating background color.

The AbstractTableCellRenderer class extends JLabel to display the data. It is easy to modify properties of the JLabel and still let the default renderer do the work.

As mentioned previously, the default renderer displays Strings and Integers. Strings fall under the general class Object, and Integers are specially handled. To do painting yourself, you have to create your own renderer and tell the table to call you if it is trying to paint an object you know about. In this case, you care only about Strings and Integers. Remember that you now lose the ability to right-justify Integer objects and all objects are displayed by their toString() method.


      MyTableRenderer mtr = new MyTableRenderer();
      tableView.setDefaultRenderer(Object.class, mtr);
      tableView.setDefaultRenderer(Integer.class, mtr);
You then have to override one method in the renderer:


   public Component getTableCellRendererComponent(
               JTable table,
               java.lang.Object value,
               boolean isSelected,
               boolean hasFocus,
               int row, int column) {
      if( !isSelected ) {
         Color c = table.getBackground();
         if( (row%2)==0 &&
                 c.getRed()>10 && c.getGreen()>10 && c.getBlue()>10 )
            setBackground(new Color( c.getRed()-10,
                                     c.getGreen()-10,
                                     c.getBlue()-10));
         else
            setBackground(c);
      }
      return super.getTableCellRendererComponent( table,
                                value,isSelected,hasFocus,row,column);
   }
You only want to paint the background if the row is not selected. Use the background color of the table, which is white by default. For every other row, change the background color to make it slightly darker. Finally, call super.getTableCellRendererComponent and paint the text or other objects (see Listing 3).

Designing Your Own Column
In one project, I needed to display dates in a column. While the default renderer handles Date objects by displaying them as Month, Day, Year, I needed to display them in my format (which includes the time) and red, yellow, or green text depending on the time criteria (see Figure 2).

Figure 2
Figure 2. JTable with a custom column painted.

Notice that the background of the Default Date column is not painted. The reason it does not paint is because we never specifically told the renderer to paint the background for Date objects. The background paints correctly for the DateCol class because we programmed the renderer to paint type Object and the DateCol extends Object.


      MyTableRenderer mtr = new MyTableRenderer();
      tableView.setDefaultRenderer(Object.class, mtr);
      tableView.setDefaultRenderer(Integer.class, mtr);
       // add this new line to listing 4 to paint Date correctly
      tableView.setDefaultRenderer(Date.class,mtr);
To paint the date/time in color, you need to override another method of the DefaultTableCellRenderer called setValue. Because the DefaultTableCellRenderer is a JLabel, the setValue method can call setText. This also gives you a chance to set the foreground and any alignment.


  public void setValue(Object value) {
     if( value instanceof DateCol ) {
        DateCol dc = (DateCol) value;
        setForeground(dc.getColor());
        setText(dc.toString());
     }
     else {
        if( value instanceof Integer ) {
           setHorizontalAlignment(JLabel.RIGHT);
        }
        setForeground(Color.black);
        super.setValue(value);
     }
  }
The DateCol class needs a getColor() method, but this is simple programming (see Listing 4).

Sorting
Depending on where your table will be used, column sorting might be a nice feature to add. Swing makes it easy to do this by allowing you to add yourself as a mouse listener on the headers.


      JTableHeader th = tableView.getTableHeader();
      th.addMouseListener(listMouseListener);
By writing a mouse listener, you can be notified when the user clicks on the header bar. This allows you to call a sorter function, resort the rows, and fire an event to the table to notify it that the rows have changed.

I built the sorter functions into a separate class called TableSorter. I have TableSorter extend TestTableModel, which allows some classes to use TableSorter for a table that allows sorting, and other classes to use TestTableModel directly so the user cannot sort the table.

In Listing 5, I use a simple bubble sort to compare and reorder objects. The sorting can be done many ways. It is important to note that I had to write a compareTo function into my DateCol class to sort the dates properly. I also had to be careful to sort strings by alpha order and integers by number order. Column 1's data is actually String objects that are all numbers so they will sort differently than the Integers column.

Other Objects in a Table
Any GUI object can be placed within a table. The DefaultTableRenderer automatically displays the Boolean class as a checkbox. Because you paint the background yourself, you have to build the checkbox yourself. This is a good example of how to display any custom object within a table (see Figure 3). You must first define a specific renderer, such as:

Figure 3
Figure 3. JTable with a checkbox column.


   public class MyCheckboxRenderer extends JCheckBox
      implements TableCellRenderer;
and then tell the table to use your renderer:


   MyCheckboxRenderer mcr = new MyCheckboxRenderer();
   tableView.setDefaultRenderer(Boolean.class, mcr);
To allow the user to check the box, you need to make that cell in the table editable.


    public boolean isCellEditable( int row, int col ) {
      if( col < 1 ) return true;
      else return false;
   }

    // we know to expect only boolean/checkbox values here
    // otherwise we would do more error checking
   public void setValueAt(Object value, int row, int col) {
       // get the array for the proper row
      Object o[] = (Object[]) data.elementAt(row);
       // replace the boolean object that changed
      o[col] = value;
   }
The isCellEditable method is called by the JTable to see which cells the user is allowed to modify. The setValueAt method is called after the user modifies a cell, but before the table is painted. With this method, you need to update your data model so the table will be painted correctly.

In Listing 6, I provide an example of implementing a checkbox within a JTable. This is a common technique if you have a large number of items for a user to check, and do not want to waste the screen real estate.

More Thoughts
In case you missed it, with JTable you can resize and reorder columns by clicking and dragging them. These are optional features that may be turned off. As you are coding, you should be aware that the internal data model might not match the visual model because the user is free to reorder the columns. This may be confusing when coding the getValueAt() method, but there are helper functions to help you convert between the two.

JTable is a powerful component within the Swing architecture that is very extensible. Take some time away from the nuts and bolts of your application to concentrate on the user interface. End users will, undoubtedly, be grateful.