Striped Clouds

Striped Cirrocumulus Undulatus CloudsI've spent quite a bit of my spare time over the last week or two doing some Java GUI programming, the reasons for which will become clear in a later post. Quite a lot of the GUI is table based and so I've spent quite a bit of time playing with custom rendering code for different data types to make things easier to visualize and edit. There are plenty of tutorials on how to do this spread over the web but one thing I found quite difficult was writing a renderer that worked reliably across different Java Look and Feels (L&F). The one renderer I wrote that highlights most of the problems I had was for displaying a checkbox in a table.

By default Java will display a checkbox for boolean data types, but unfortunately it doesn't disable the checkbox when it can't be edited. This leads to a situation where you can't change the state of the checkbox but there is no visual feedback to tell you this. So I wrote a simple renderer that would disable the checkbox if it wasn't editable. The first problem I found was that under the GTK+ L&F the background of the cell didn't change when the row was selected. It did under the default Metal L&F and after a little bit of debugging I discovered the problem. Every Swing component has an opaque property which determines if it's background is drawn or not. It turns out that the default value is dependent on the L&F. So under Metal checkbox's have an opaque background while under GTK+ they don't. Fortunately this is easy to fix simply by calling setOpaque(true). A similar problem occurs with the focus rectangle around the outside of each table cell, but again it's an easily fixed by calling setBorderPainted(true). These tweaks gave me a cell renderer which seemed to work well until, that is, I tried it under the Nimbus L&F.

The Nimbus L&F was introduced in Java SE 6 Update 10, and was meant to be the new cross platform L&F that would replace the aging Metal which has been the default since Swing was first developed. One of the nice things about Nimbus is that it is resolution independent and uses vector graphics rather than bitmaps. This should, in theory, lead to a crisper interface. Personally I'm not a fan, and as yet it hasn't replaced Metal as the default L&F. It seemed, however, sensible to make sure my rendering code worked correctly under Nimbus as well as Metal, GTK+ and CDE/Motif (these are the four L&Fs available by default when running Java under Ubuntu). Unfortunately it didn't.

Nimbus, in an attempt to be different, colours the background of table rows alternating colours -- by default white and a light gray. This is instead of drawing a border around the cells. The problem is that my renderer (and almost every example I've seen) gets the background colour for the cell from the table by calling either table.getBackground() or table.getSelectionBackground(). The selected background colour works correctly but the unselected cells get drawn with a dark gray background. There are three tricks to work around this while leaving the code working under the other L&Fs. The first is to get the alternative background colour from the UIManager class. The second is to recreate the unselected background colour to make it display correctly. Finally we use the modulus operator to determine which row colour we should be using. Adding these workarounds gives me the following cell renderer which seems to work under the four L&Fs available by default under Ubuntu as well as the Windows L&F.
import java.awt.Color;
import java.awt.Component;

import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.table.TableCellRenderer;

/**
 * A TableCellRenderer for JCheckBox that disables the checkbox when the
 * cell isn't editable to make it clear that you can't click on it
 * 
 * @author Mark A. Greenwood
 */
@SuppressWarnings("serial")
public class CheckBoxTableCellRenderer extends JCheckBox implements
                                                    TableCellRenderer {

  private static final Border NO_FOCUS =
    BorderFactory.createEmptyBorder(1, 1, 1, 1);;

  public CheckBoxTableCellRenderer() {
    super();
    setHorizontalAlignment(JCheckBox.CENTER);
    setBorderPainted(true);
    setOpaque(true);
  }

  public Component getTableCellRendererComponent(JTable table,
    Object value, boolean isSelected, boolean hasFocus,
    int row, int column) {

    // this is needed for Nimbus which has alternative rows in different
    // colors hopefully other L&Fs that also do this use the same key
    Color alternate = UIManager.getColor("Table.alternateRowColor");

    // strangely the background color from nimbus doesn't render properly
    // unless we convert it in this way. I'm guessing the problem is to do
    // with the DerivedColor class that Nimbus uses
    Color normal = new Color(table.getBackground().getRGB());

    if(isSelected) {
      setForeground(table.getSelectionForeground());
      setBackground(table.getSelectionBackground());
    } else {
      setForeground(table.getForeground());
      setBackground(alternate != null && row % 2 == 0 ? alternate : normal);
    }

    setEnabled(table.isCellEditable(row, column));
    setSelected(value != null && (Boolean)value);

    if(hasFocus) {
      setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
    } else {
      setBorder(NO_FOCUS);
    }

    return this;
  }
}
One thing to note is that I've used the alternative colour for the even numbered rows (i.e. when row % 2 == 0). I've seen some web pages suggesting that you need to use the alternative colour on the odd rows. I'm not sure how Nimbus decides which colour to use for which rows so if you see them switched around for some reason you'll need to tweak the code slightly (i.e. use row % 2 == 1).

0 comments:

Post a Comment