TableCellRenderer, Part 3 - Delegate Renderer Examples(13:56, 25. Aug. 2010)

As already mentioned in the last article modifying the cell background color of a JTable in a delegate renderer is a bit tricky. This article describes possible pitfalls and you will learn how to work around color caching.

Column Highlighting

Let's start with a very simple example - the intention is to highlight a single table column.

/** Example #1 **/ 
class SimpleColumnBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public SimpleColumnBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                          hasFocus, row, column);      
    if (!isSelected)
      c.setBackground(new Color(0xFFF0E0));
    return c;
  }
} 

We apply the renderer to the sixth column.

TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
TableCellRenderer r = new SimpleColumnBackgroundRenderer(defaultRenderer);
table.getColumnModel().getColumn(5).setCellRenderer(r);

Take a look at the result below.

Oops, if you wonder why three columns are highlighted, you are not alone. As already mentioned in the last article the problem is caused by DefaultTableCellRenderer which implements a caching mechanism for background/foreground colors and icons. Technically our renderer handles the sixth column only but the delegate is also responsible for all columns without a registered renderer. To fix this behavior we have to register our renderer for all related columns and have to reset the background color within our renderer just like below.

/** Example #2 **/ 
TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
TableCellRenderer r = new ColumnBackgroundRenderer(defaultRenderer);
table.setDefaultRenderer(Object.class, r);
   
...
class ColumnBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private Color alternateColor; 

  public ColumnBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
    alternateColor = UIManager.getColor("Table.alternateRowColor");
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                          hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      //respect alternating row background when resetting
      Color defaultBackground = (row % 2 == 0 && alternateColor != null) ? alternateColor : table.getBackground();
      c.setBackground(modelColumn == 5 ? new Color(0xFFF0E0) : table.getBackground());
    }  
    return c;
  }
} 

One question still remains - what's the reason for the color caching implementation in DefaultTableCellRenderer? Performance - no, there's no difference. One can only assume that the caching was made to enable simple column coloring as below.

/** Example #3 **/ 
TableCellRenderer r = new DefaultTableCellRenderer();
((Component)r).setBackground(Color.YELLOW);
table.getColumnModel().getColumn(5).setCellRenderer(r);
table.getColumnModel().getColumn(3).setCellRenderer(r);

Anyway, generally this implementation detail makes things more complicated than necessary. That's why we've decided to disable color caching by default in the upcoming Synthetica release V2.11. To control the caching behavior the UI-property "Synthetica.table.cellRenderer.colorCache.enabled" will be introduced with V2.11.

Note: Because of the color caching workaround, Example#2 should work well for most available look and feels. For look and feels which install caching free-renderers (like Synthetica V2.11 and above) also Example#1 works fine. Avoid the renderer usage of Example#3 - it's very implementation specific.

Row Highlighting

In the next step we extend our renderers to highlight a the third table row. To achieve this, we have to register our renderer for each table column.

/** Example #4 **/
for (int i = 0; i < table.getColumnModel().getColumnCount(); i++)
{  
  Class columnClass = table.getModel().getColumnClass(i);
  TableCellRenderer defaultRenderer = table.getDefaultRenderer(columnClass);
  TableCellRenderer colRenderer = new ColumnRowBackgroundRenderer(defaultRenderer);
  table.getColumnModel().getColumn(i).setCellRenderer(colRenderer);
}

The related renderer takes care about row and column coloring and also respects alternating row background (if enabled by UI-property).

/** Example #5 **/
class ColumnRowBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private Color alternateColor; 

  public ColumnRowBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
    alternateColor = UIManager.getColor("Table.alternateRowColor");
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      //Because of the color caching we have to reset the background to table background.
      //If color caching is disabled you can also use c#getBackground instead of 
      //table#getBackground. For resetting also respect alternating row background.
      Color defaultBackground = (row % 2 == 0 && alternateColor != null) ? alternateColor : table.getBackground();
      c.setBackground(modelColumn == 5 ? new Color(0xFFF0E0) : defaultBackground);
      c.setBackground(row == 2 ? new Color(0xDDD7FF) : c.getBackground());
    }  
    return c;
  }
}

In Synthetica V2.11 (scheduled for September 2010) the same result can be achieved with the more simple renderer below.

/** Example #6 - requires Synthetica V2.11 or above **/
class SimpleColumnRowBackgroundRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public SimpleColumnRowBackgroundRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);      
    if (!isSelected)
    {  
      int modelColumn = table.convertColumnIndexToModel(column);
      if (modelColumn == 5)
        c.setBackground(new Color(0xFFF0E0));
      if (row == 2)
        c.setBackground(new Color(0xDDD7FF));
    }  
    return c;
  }
} 

Here's the result (with alternating row background enabled):

SwingX related note: Delegate renderers also work fine for JXTables. However, the JXTable component comes along with a sophisticated highlighting feature which is recommended to be used - check it out if you are not bound to JTable.

Download

Demos Sourcecode

Related Posts

TableCellRenderer, Part 1 - The Basics
TableCellRenderer, Part 2 - How To Create A Custom Renderer

The next article will provide some advanced examples... CU