Substance custom table cell renderers

I use the Substance Look & Feel in most of my Java Swing GUIs these days. It’s a very well engineered, mature, configurable implementation of a Swing look & feel which can transform the appearance of any Swing GUI. The author Kirill Grouchnikov has done a great job with it over the years and I’d like to thank him for his work on this project (and his other projects such as the Trident animation library and Flamingo component suite).

Although Substance is extremely configurable, one area that I’ve had problems with in the past is that of custom table cell renderers. Substance provides default renderers for common cell value classes such as strings, numbers, booleans etc. which covers most use cases. However, if you want to customise the cell renderer behaviour and still benefit from all of the Substance effects such as row striping, selection highlighting animation etc. you’re quite limited because Substance enforces that your custom renderer is sub-classed from the SubstanceDefaultTableCellRenderer, which itself sub-classes DefaultTableCellRenderer. This is fine as long as your custom renderer is happy to be, effectively, a sub-class of JLabel, but for anything involving custom painting it’s no good.

This is one area that I think Substance could possibly be improved to make it a bit more extensible and flexible.

One specific situation I encountered this problem with recently was where I wanted to display a mini bar-chart in a table cell. The actual custom bar component was a trivial amount of custom painting but I couldn’t see an easy way of getting this into a Substance table cell renderer. I tried creating a simple cell renderer which extended the bar component and implemented the obligatory TableCellRenderer interface, and although this worked to a degree, I lost all of the nice Substance effects mentioned above by virtue of the fact that the renderer wasn’t derived from SubstanceDefaultTableCellRenderer. I managed to get row background striping back quite easily by calling the SubstanceStripingUtils.applyStripedBackground() method but that wasn’t good enough.

Then I had a mini flash of inspiration triggered when I noticed that Substance provided a default renderer for icons…

Because the Substance default renderer is based on a label, I could set the icon property to an implementation of the javax.swing.Icon interface to get my custom painting “injected” into the renderer. The steps involved were:

  1. Make my custom bar component implement the Icon interface. This was simply a case of moving my custom painting code from the paintComponent method to paintIcon and also providing implementations of the getIconWidth and getIconHeight methods.
  2. Create a simple SubstanceDefaultTableCellRenderer sub-class which wraps an instance of my bar component. In the renderer constuctor I call setIcon to attach the bar component to the renderer. The renderer also needs to override the two setBounds methods so that it can set the size of the wrapped bar component, otherwise it wouldn’t resize with the table cell! Oh, and of course an overridden setValue method needs to update the wrapped bar component value as appropriate.

As a result, my two classes looked something like this:

BarIconComponent

package com.purplegem.substanceplay;

import javax.swing.*;
import java.awt.*;

/**
 * Simple custom component which renders a mini bar chart.
 * Implements the Icon interface so it can be used where an Icon can.
 */
public class BarIconComponent extends JComponent implements Icon {

  private double value = 0.5d;

  public BarIconComponent() {
    setSize(40, 20);
  }

  public double getValue() {
    return value;
  }

  public void setValue(double value) {
    this.value = value;
  }

  @Override
  public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g;
    g2.setColor(Color.GREEN);
    g2.fillRect(0, 0, (int) (getIconWidth() * value), getIconHeight());
  }

  @Override
  public int getIconWidth() {
    return getWidth();
  }

  @Override
  public int getIconHeight() {
    return getHeight();
  }

  @Override
  protected void paintComponent(Graphics g) {
    paintIcon(this, g, 0, 0);
  }

}

BarCellRenderer

package com.purplegem.substanceplay;

import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;

import java.awt.*;

/**
 * Custom Substance table cell renderer which renders a mini bar
 * chart using the wrapped BarIconComponent instance
 */
public class BarCellRenderer extends SubstanceDefaultTableCellRenderer {

  private BarIconComponent barComponent = new BarIconComponent();

  public BarCellRenderer() {
    // attach our bar component as an icon
    setIcon(barComponent);
  }

  @Override
  public void setBounds(int x, int y, int width, int height) {
    super.setBounds(x, y, width, height);
    // ensure we update the size of the wrapped bar component
    barComponent.setSize(width, height);
  }

  @Override
  public void setBounds(Rectangle r) {
    super.setBounds(r);
    // ensure we update the size of the wrapped bar component
    barComponent.setSize((int) r.getWidth(), (int) r.getHeight());
  }

  @Override
  protected void setValue(Object value) {
    if (value != null) {
      // update the value of the bar component for this particular table cell
      barComponent.setValue((Double) value);
    }
  }

}

Once I’d implemented all of this I then had exactly the result I was expecting – a mini bar chart in a table cell with all the expected Substance table cell effects 🙂

3 Comments

  1. I’ve been trying to do something much simpler – change the data format on my Date object cells in a table. I’ve actually extended SubstanceDefaultTableCellRenderer to use my DateFormat in setValue() … but that’s pretty ugly as it ties code to substance that should really have no linkage to it. I’m thinking of subclassing my JTable to intercept the setValue on the way in and convert it before it hits the renderer … again not nice though because at some stage I may want to use the swingx date picker as the editor component.

    Ho hum – substance makes like difficult sometimes, but it’s still the best looking L&F so we put up with it.

    Hope the windmill tilting is being successful!

  2. Hey Ian! Good to hear from you, and thanks for the comment.

    If I understand correctly what you’re trying to do, could you not just return a suitably formatted date string in your table model’s getValueAt method? That’s may be no good if you plan to allow editing of these date cells (as the cell type would be a String instead of a Date), but if all you’re interested in is displaying them this method should work.

    Did you know that Substance was now no longer actively developed/supported by Kirill? I guess he’s devoting all of his time to Android stuff these days.

  3. Hi Darren – yes that’s how I had it originally (the model returning a string) but I don’t like having the formatting / view code in the model – it’s the wrong place. Also I wanted something generic that would do the ‘right thing’ for dates including using the swingx control for editing them. I think my current solution (using substance code in my table subclass) is probably a lesser evil than putting formatting code in the model … debatable though, neither are pretty.

    I noticed that Krill had stopped work on Substance … it’s pretty finished though and the source is available so not too great a threat. I guess because he is working on Android and given the legal issues between google and Oracle he’s been told to distance himself.

Comments are closed.