TableCellRenderer, Part 2 - How To Create A Custom Renderer(10:06, 11. Aug. 2010)

Generally there are three basic approaches to create a custom renderer.

  • Implementing a TableCellRenderer from scratch
  • Extending javax.swing.table.DefaultTableCellRenderer
  • Create a renderer-chain by using the already installed renderer as delegate

Let's take a closer look at each solution to find out which one is the best for your needs.

Implementing a TableCellRenderer from scratch

For the first solution you have to implement each rendering detail by yourself - this is error prone and keeping your custom renderer independent from the look and feel can become complex and therefore time consuming. Additionally you have to take care about performance. This solution is only recommended if no other approach fits your needs.

Extending DefaultTableCellRenderer

For creating a custom renderer developers often tend to extend javax.swing.table.DefaultTableCellRenderer. The howto can be found at Oracle's JTable tutorial. Unfortunately this solution is not quite perfect because the active look and feel possibly already installed it's own renderers. Third party table components like JXTable also install specialized renderers. So in case that you install a renderer which is derived from DefaultTableCellRenderer you can loose some settings like focus border, padding, alignment, font or colors.

Create a renderer-chain by using the already installed renderer as delegate

Even if deriving from DefaultTableCellRenderer is good enough for your needs - the most robust and preferred solution is to use the original renderer as delegate for your custom renderer. In the basic example below the rendering component runs through a renderer-chain before the component will be painted by the CellRendererPane.

JTable myTable = new JTable(...);
final TableCellRenderer renderer = myTable.getDefaultRenderer(Object.class);
myTable.setDefaultRenderer(Object.class, new TableCellRenderer(){
  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = renderer.getTableCellRendererComponent(table, value, isSelected, 
                                                         hasFocus, row, column);
    //apply all needed settings to c
    ...
    return c;
  }
}); 

By putting your custom renderer in a separate class it will become reusable.

JTable myTable = new JTable(...);
TableCellRenderer renderer = myTable.getDefaultRenderer(Object.class);
myTable.setDefaultRenderer(Object.class, new CustomTableCellRenderer(renderer);

public class CustomTableCellRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;

  public CustomTableCellRenderer(TableCellRenderer defaultRenderer)
  {
    this.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);
    //apply all needed settings to c
    ...
    return c;
  }
} 

Normally a single component instance is used for rendering - this also means that you are responsible for resetting modified component properties just like in the example below.

public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    Component c = renderer.getTableCellRendererComponent(table, value, isSelected, 
                                                         hasFocus, row, column);
    if (!isSelected)
    { 
      //highlight row #10
      if (row == 9)
        c.setBackground(Color.YELLOW);
      //reset background
      else
        c.setBackground(table.getBackground());
    }
    return c;
  }
}); 

Note: In case that your custom renderer modifies the background or foreground color, please make sure to apply the renderer to the Object.class. This ensures that your renderer will be called for each table cell and your settings will be applied to the renderer component. Swing's DefaultTableCellRenderer implements a caching mechanism which can result in wrong cell colors if you ignore this rule. For compatibility reasons Synthetica's default renderers acts like the Swing renderers. However, in Synthetica V2.11.0 and above an optional UI-property can be set to disable color caching.

Related Posts

TableCellRenderer, Part 1 - The Basics

In the upcoming article we examine the delegate method including more complex examples in detail.