TableCellRenderer, Part 4 - Advanced Renderer Examples(20:19, 06. Oct. 2010)

The previous article covers some basic delegate renderer examples. Now it's time to take a look at advanced renderer examples.

Combining renderers in a renderer chain

Let's create some additional renderers and combine them in a renderer chain - each of the renderers below implements a single rule. The first renderer (PercentageRenderer) formats numbers as percentages.

/** Example #7 **/
public class PercentageRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private NumberFormat formatter;

  public PercentageRenderer(TableCellRenderer defaultRenderer)
  {
    this.delegate = defaultRenderer;
    formatter = NumberFormat.getPercentInstance();
  }

  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);
    String s = formatter.format((Double)value);
    if (c instanceof JLabel)
      ((JLabel)c).setText(s);
    return c;
  }
} 

The IconRenderer marks numbers which are evenly divisable by 5 with a checkmark icon.

/** Example #8 **/ 
public class IconRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private Icon icon;

  public IconRenderer(TableCellRenderer defaultRenderer)
  {
    this.delegate = defaultRenderer;
    icon = new ImageIcon(this.getClass().getResource("/demo/tablecellrenderer/accept.png"));
  }

  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 (c instanceof JLabel && (Double)value*1000 % 50 == 0)
      ((JLabel)c).setIcon(icon);
    return c;
  }
}

The ColorRenderer renders Color objects with a rectangular icon and a hexadecimal value.

/** Example #9 **/ 
public class ColorRenderer implements TableCellRenderer
{
  private TableCellRenderer delegate;
  private ColorIcon icon;

  public ColorRenderer(TableCellRenderer defaultRenderer)
  {
    this.delegate = defaultRenderer;
    icon = new ColorIcon();
  }

  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    //remember - the default renderer is a label instance 
    //for values of data types String, Object and Number
    Component c = delegate.getTableCellRendererComponent(table, value, isSelected, 
                                                                hasFocus, row, column);
    if (c instanceof JLabel)
    {  
      Color color = (Color)value;
      icon.setColor(color);
      ((JLabel)c).setIcon(icon);
      ((JLabel)c).setText(Integer.toHexString(color.getRGB()).toUpperCase().substring(2));
    }  
    return c;
  }
  ...
} 

In the next step we combine the renderers in a renderer chain and register the resulting renderer for the related table columns.

/** Example #10 **/ 
TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
// colorize column background
TableCellRenderer backgroundRenderer = new SimpleColumnBackgroundRenderer(defaultRenderer);
// format numbers as percentages
TableCellRenderer percentageRenderer = new PercentageRenderer(backgroundRenderer);
// mark values with an icon which are evenly divisable by 5
TableCellRenderer iconRenderer = new IconRenderer(percentageRenderer);        
// apply to columns of data type Double
table.setDefaultRenderer(Double.class, iconRenderer);
   
// colorize column background
TableCellRenderer backgroundRenderer2 = new SimpleColumnBackgroundRenderer(defaultRenderer);
// render as icon and hexadecimal value
TableCellRenderer colorRenderer = new ColorRenderer(backgroundRenderer2);
// apply to columns of data type Color
table.setDefaultRenderer(Color.class, colorRenderer);

//because a background renderer is already defined you can simplify the
//lines above by reusing the existing background renderer instance
/*TableCellRenderer colorRenderer = new ColorRenderer(backgroundRenderer);
table.setDefaultRenderer(Color.class, colorRenderer);*/

Let's take a look at the rendering result below.

Component Renderer

Sometimes it's necessary to use another component than a JLabel for rendering. In case that you want to give your users a visual feedback that a value is editable in a specific manner you can use any Swing component for rendering. In the example below we'll use a JSpinner component to display float values.

/** Example #11 **/
public class SpinnerRenderer extends JLabel implements TableCellRenderer
{
  protected TableCellRenderer delegate;
  protected JSpinner spinner;

  public SpinnerRenderer(TableCellRenderer defaultRenderer)
  {
    delegate = defaultRenderer;
    //identifies TableCellRenderer
    setName("Table.cellRenderer");
    setLayout(new BorderLayout());
    
    spinner = new JSpinner();   
    //remove component border
    spinner.setBorder(new EmptyBorder(0,0,0,0));
    //disable component background painting
    spinner.putClientProperty("Synthetica.opaque", false);
    add(spinner);
  }
  
  public Component getTableCellRendererComponent(JTable table, Object value, 
                           boolean isSelected, boolean hasFocus, int row, int column) 
  {
    JComponent c = (JComponent)delegate.getTableCellRendererComponent(table, value, 
                                                  isSelected,hasFocus, row, column);
    //respect insets and focus
    setBorder(c.getBorder());
    //copy opacity
    setOpaque(c.isOpaque());
    //apply background
    setBackground(c.getBackground());    
    //apply foreground
    Component editor = SyntheticaLookAndFeel.findComponent(JTextField.class, spinner);
    editor.setForeground(c.getForeground());
    //set value
    spinner.setValue(value);
    return this;
  }  
}

To maximize compatibility with DefaultTableCellRenderer we use a JLabel as container for our JSpinner component. This technique works fine for most Swing components and enables you to create component renderers easily without taking care about details like setting selected/unselected colors, alternating row color or cell padding.

Note: This kind of renderer makes use Swing's component hierarchy and is therefore not optimized for performance. So be aware that using such component renderers in very large tables can affect table performance.

/** Example #12 **/
TableCellRenderer defaultRenderer = table.getDefaultRenderer(Object.class);
TableCellRenderer backgroundRenderer = new SimpleColumnBackgroundRenderer(defaultRenderer);
...
//use backgroundRenderer instead of defaultRenderer to highlight table column
TableCellRenderer spinnerRenderer = new SpinnerRenderer(backgroundRenderer);
table.setDefaultRenderer(Float.class, spinnerRenderer);

After applying the renderer to columns of data type Float each cell appears with additional arrow buttons.

Note: For your convenience the latest SyntheticaAddons release (V1.3) comes along with a bunch of predefined TableCellRenderers and TableCellEditors which provides support for the data types Boolean, Integer, Long, Float, Double, String, Point, Dimension, Rectangle, Insets, Color, Date, Font, DefaultComboBoxModel, SpinnerNumberModel, DefaultBoundedRangeModel, TableSeparator and URL.

Download

Demos Sourcecode

Related Posts

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

The upcoming article covers some helpful hints... CU