Search code examples
javaswingjtabletablecellrenderer

Extending DefaultTableCellRenderer, no new object creation in getTableCellRendererComponent?


I've some difficulties to understand cell renderer. I want to use StretchIcon in a JTable, so that cells of a column display a resized logo. StretchIcon extends ImageIcon.

Custom renderer:

public class IconCellRenderer extends DefaultTableCellRenderer {

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {

        JLabel label = new JLabel();
        label.setIcon( (Icon) ( new StretchIcon((Image) value) ) );

        return label;
    }
}

This code works as desired, I've no problem with the table.

From this tutorial, I learnt that I should avoid creating new objects in getTableCellRendererComponent for performance reason.

I don't understand:

  • How many times will IconCellRenderer be instantiated. I assume only once, when the JTable is created (assuming I have only one column in the table).

  • How many times will getTableCellRendererComponent be called. I assume once for each cell in the column, so that it can return a new instance of JLabel with an embedded instance ofStretchIcon for each cell. If this is true, then it makes sense to create the new instances of JLabel and StretchIcon in the method.

  • How will the resized icon be repainted when needed (cell being hidden and then shown again, or cell resized)? I assume getTableCellRendererComponent is not involved at all in this operation, and this is done entirely using the paint method of JLabel and the paint method redefined in StretchIcon.

Is my understanding somehow incorrect? Is it legitimate to create a new instance of StretchIcon in the method? What can I do to optimize this cell renderer, e.g. can I make the label object static?


Edit: based on comments and answer provided, I ended up with this cell renderer:

public class StretchIconRenderer extends DefaultTableCellRenderer {

    private static StretchIcon stretched;

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {

        super.getTableCellRendererComponent(table, value, 
                isSelected, hasFocus, row, column);

        Image logo = (Image) value;

        if (stretched == null) { stretched = new StretchIcon(logo); }
        else { stretched.setImage(logo); }

        setIcon(stretched);
        setText(null);

        return this;
    }
}

Solution

  • How many times will IconCellRenderer be instantiated. I assume only once, when the JTable is created (assuming I have only one column in the table).

    Technically, it only needs to be created once, but it may be created as often as you want. Once assigned to a JTable/TableColumn however, it will never be initiated again by these classes. They just maintain a reference to the instance you passed them

    How many times will getTableCellRendererComponent be called. I assume once for each cell in the column, so that it can return a new instance of JLabel with an embedded instance of StretchIcon for each cell. If this is true, then it makes sense to create the new instances of JLabel and StretchIcon in the method.

    Yes, this is true, getTableCellRendererComponent will be called for each row in the table. However, you don't need to create a new instance of components, you can return the same instance configured differently for each row.

    The reason for this is, the table simple "paints" the result onto itself, it does not "add" the components in the manner you are use to. This is often termed "cookie cutter" or "rubber stamping", as the component is used simply as a template.

    How will the resized icon be repainted when needed (cell being hidden and then shown again, or cell resized)? I assume getTableCellRendererComponent is not involved at all in this operation, and this is done entirely using the paint method of JLabel and the paint method redefined in StretchIcon.

    No, this is done entirely via the getTableCellRendererComponent. When a cell/row/column is updated in some way, a notification must be triggered from the TableModel or ColumnModel, which instructs the table that a change has occurred.

    The table will determine the affected cells and repaint them, using the appropriate TableCellRenderer

    In short. There should be no need to create a new instance of JLabel within getTableCellRendererComponent. In fact, you don't even need the JLabel at all, as DefaultTableCellRenderer extends from JLabel and you should simply use it.

    I don't know how StretchIcon works, so you may have no choice but to create a new instance, but if you can, you might consider creating a WeakHashMap keyed to the Object value, which maintains a reference of StretchIcon, so you only ever create a single instance of for each row...

    For example...

    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
    
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
        setIcon(getStretchIconFor(value));
    
        return this;
    }
    
    protected StreatchIcon(Object value) {
        StretchIcon icon = null;
        if (!cache.contains(value)) {
            icon = new StretchIcon((Image) value) );
            cache.put(value, icon);
        }
        return icon;
    }
    

    Where cache is a WeakHashMap...

    In might be worth while having a read through Concepts: Editors and Renderers