views:

903

answers:

3

OK, I know how to make a simple custom JComponent. I know how to override a TableCellRenderer. I can't seem to combine the two.

Here's a sample JComponent I created:

public static class BarRenderer extends JComponent
{
 final private double xmin;
 final private double xmax;
 private double xval;
 public BarRenderer(double xmin, double xmax)
 {
  this.xmin=xmin;
  this.xmax=xmax;
 }

 @Override protected void paintComponent(Graphics g)
 {
  super.paintComponent(g);
  Rectangle r = g.getClipBounds();
  g.drawRect(r.x, r.y,
    (int)(r.width * ((xval-xmin)/(xmax-xmin))), r.height);
 }

 public void setXval(double x) { 
  this.xval = x;
  repaint();
 }
 public double getXval() { return xval; }
}

It works fine as a standalone JComponent. I call setXval(something) and it updates just fine. (edit: I have a Swing Timer that updates the data periodically)

But if this component is something I return in TableCellRenderer.getTableCellRendererComponent(), then it only repaints when I click on the cell in question. What gives? I must be leaving out something really simple.

+1  A: 

If you make a table with say 3 rows, each having a different Xval, then does it initially renderer correctly, meaning each cell has a different looking bar?

When you say it does not repaint unless you click it, has something happened to your underlying data that should have caused the visual display of the data (the rendered bar) to change?

If the data changed, but the table does not immediatley re-render, then I would say that your TableModel is not working properly.

underlying data changes -> TableModel changes -> fires TableModelEvent -> JTable re-renders

Look at the TableModel tuturial: http://java.sun.com/docs/books/tutorial/uiswing/components/table.html#data

to make sure you are doing everything correct.

Andrew
+2  A: 

For performance reasons a JTable reuses renderer components to paint multiple cells - so when you see the component in the JTable it isn't actually there in the traditional sense of a Component in a Container which is present at a location. This means that calling repaint() on the renderer component does nothing.

The most effective option would be to store the Integer value of the bar in your TableModel. Your TableCellRenderer would then look something like this:

public class BarTableCellRenderer implements TableCellRenderer {
    private final BarRenderer rendererComponent = new BarRenderer(0, 10);

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        rendererComponent.setXval((Integer)value);
        return rendererComponent;
    }
}

Then you could change the Integer in your TableModel and it would trigger a repaint of the bar (you may need a TableModel.fireTableCellUpdated dependent on the TableModel implementation you are using).

Russ Hayward
sounds like a possible solution... I think the TableModel.fireTableCellUpdated sounds like the key here.
Jason S
+1  A: 

Both of you (Russ Hayward and Andrew) helped, the key was essentially to do the following:

  • store the state to be made visible in the TableModel itself, not in the renderer
  • make sure that when the TableModel's state changes, fireTableCellUpdated() is called
  • have only one TableCellRenderer object and one JComponent for my custom column (not one per cell)
    • within TableCellRenderer.getTableCellRendererComponent() store the cell's state for purposes of being rendering soon after (long-term storage is in the TableModel)
    • provide that state to the JComponent
    • return the JComponent
    • override JComponent.PaintComponent()
  • one convenient possibility is for a custom renderer to extend JComponent and implement TableCellRenderer, then in TableCellRenderer.getTableCellRendererComponent() you store the cell's state and return this;

Here's the relevant excerpt of my code that now works:

class TraceControlTableModel extends AbstractTableModel {
    /* handle table state here */

    // convenience method for setting bar value (table model's column 2)
    public void setBarValue(int row, double x)
    {
        setValueAt(x, row, 2);
    }
}

// one instance of BarRenderer will be set as the
// TableCellRenderer for table column 2
public static class BarRenderer extends JComponent 
 implements TableCellRenderer 
{
 final private double xmin;
 final private double xmax;
 private double xval;
 public BarRenderer(double xmin, double xmax)
 {
  super();
  this.xmin=xmin;
  this.xmax=xmax;
 }

 @Override protected void paintComponent(Graphics g)
 {
  super.paintComponent(g);
  Rectangle r = g.getClipBounds();
  g.drawRect(r.x, r.y,
    (int)(r.width * ((xval-xmin)/(xmax-xmin))), r.height);
 }

 @Override
 public Component getTableCellRendererComponent(JTable arg0,
   Object value, 
   boolean isSelected, boolean hasFocus,
   int row, int col)
 {
  // save state here prior to returning this object as a component
  // to be painted
  this.xval = (Double)value;
  return this;
 }
}
Jason S