1

I am trying to create a JTable that allows for favoriting of items, but unfortunately the correct images do not show up on initial rendering, and then do not update properly until they lose focus in the cell.

To do this I've made column 1 a String and column 2 a boolean. I then overwrote the boolean renderer/editor as based on this question:

Java Swing, Trying to replace boolean check-box in a JTable with an image-icon checkbox

what I currently have:

public class FavoritableCellEditor extends AbstractCellEditor implements TableCellEditor {

private final FavoriteCheckBox cellEditor;

public FavoritableCellEditor() {
    cellEditor = new FavoriteCheckBox();
}

@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
    if (value instanceof Boolean) {
        boolean selected = (boolean) value;
        cellEditor.setSelected(selected);
    }
    return cellEditor;
}

@Override
public Object getCellEditorValue() {
    return cellEditor.isSelected();
}

}


public class FavoritableCheckboxRenderer extends FavoriteCheckBox implements TableCellRenderer {


@Override
public void setSelected(boolean selected) {
    super.setSelected(selected);
    if (selected) {
        setIcon(selIcon);
    } else {
        setIcon(unselIcon);
    }
}

@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    if (value instanceof Boolean) {
        boolean selected = (boolean) value;
        setSelected(selected);
    }
    return this;
}
}

public class FavoriteCheckBox extends JCheckBox {

    Icon selIcon;
    Icon unselIcon;

    public FavoriteCheckBox() {
        try {
            selIcon = new ImageIcon(ImageIO.read(getClass().getResource("/com/lmco/jsf/dqim/applications/TESTING/resources/baseline_star_black_18dp.png")));
            unselIcon = new ImageIcon(ImageIO.read(getClass().getResource("/com/lmco/jsf/dqim/applications/TESTING/resources/baseline_star_border_black_18dp.png")));
        } catch (IOException ex) {
            Logger.getLogger(FavoriteCheckBox.class.getName()).log(Level.SEVERE, null, ex);
        }
        setHorizontalAlignment(CENTER);
    }

    @Override
    public void setSelected(boolean selected) {
        super.setSelected(selected);
        if (selected) {
            setIcon(selIcon);
        } else {
            setIcon(unselIcon);
        }
        revalidate();
        repaint();
    }

}

Demonstration: I initially click where you see the check mark. Currently the correct images are not showing, but the default checkbox images. enter image description here

I now click the bottom right corner to make that cell lose focus, it then paints itself filled with the correct image. enter image description here

Finally I click where you see the check mark enter image description here

An additional note: I've added in the revalidate() and repaint() myself, without it the behavior is largely the same except that it will never show the check marks again after initial rendering. It will still not update the image until focus is lost

TheYargonaut
  • 195
  • 1
  • 13

1 Answers1

3

If you only have a single column of Boolean values then you can just customize the icons used by the check box editor and renderer:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.border.*;
import java.text.*;

public class TableBasic extends JPanel
{
    public TableBasic()
    {
        setLayout( new BorderLayout() );

        String[] columnNames = {"Date", "String", "Integer", "Boolean"};

        Object[][] data =
        {
            {new Date(), "A", new Integer(1), Boolean.TRUE },
            {new Date(), "B", new Integer(2), Boolean.FALSE},
            {new Date(), "C", new Integer(12), Boolean.TRUE },
            {new Date(), "D", new Integer(5124), Boolean.FALSE}
        };

        DefaultTableModel model = new DefaultTableModel(data, columnNames)
        {
            //  Returning the Class of each column will allow different
            //  renderers and editors to be used based on Class

            @Override
            public Class getColumnClass(int column)
            {
                for (int row = 0; row < getRowCount(); row++)
                {
                    Object o = getValueAt(row, column);

                    if (o != null)
                        return o.getClass();
                }

                return Object.class;
            }
        };

        JTable table = new JTable(model);

        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane( table );
        add( scrollPane );

        // Customize the icons of the renderer/editor used for Boolean data

        Icon selectedIcon = new ImageIcon( "copy16.gif" );
        Icon icon = new ImageIcon( "about16.gif" );

        DefaultCellEditor dce = (DefaultCellEditor)table.getDefaultEditor(Boolean.class);
        JCheckBox cbe = (JCheckBox)dce.getComponent();
        cbe.setSelectedIcon( selectedIcon );
        cbe.setIcon( icon );

        TableCellRenderer tcr = table.getDefaultRenderer(Boolean.class);
        JCheckBox cbr = (JCheckBox)tcr;
        cbr.setSelectedIcon( selectedIcon );
        cbr.setIcon( icon );
    }

    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("TableBasic");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new TableBasic() );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible( true );
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                createAndShowUI();
            }
        });
    }
}

If you need multiple Boolean renderers/editors then you will need to create unique instances of each renderer/editor.

Creating the editor is easy. You just create a DefaultCellEditor using your custom JCheckBox:

JCheckBox checkBox = new JCheckBox();
checkBox.setHorizontalAlignment(JCheckBox.CENTER);
checkBox.setSelectedIcon( selectedIcon );
checkBox.setIcon( icon );
DefaultCellEditor dce = new DefaultCellEditor( checkBox );
table.getColumnModel().getColumn(???).setCellEditor(dce);

Creating the renderer is more difficult. You need to create an custom renderer. Here is the default renderer code for the BooleanRenderer as found in the JTable source:

static class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource
{
    private static final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

    public BooleanRenderer() {
        super();
        setHorizontalAlignment(JLabel.CENTER);
        setBorderPainted(true);
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
                                                   boolean isSelected, boolean hasFocus, int row, int column) {
        if (isSelected) {
            setForeground(table.getSelectionForeground());
            super.setBackground(table.getSelectionBackground());
        }
        else {
            setForeground(table.getForeground());
            setBackground(table.getBackground());
        }
        setSelected((value != null && ((Boolean)value).booleanValue()));

        if (hasFocus) {
            setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
        } else {
            setBorder(noFocusBorder);
        }

        return this;
    }
}

In the constructor you would set the icons.

camickr
  • 321,443
  • 19
  • 166
  • 288
  • This approach is working. Further still, its more concise and easy than extending the Renderer/Editor classes seen in responses to other similar questions like this one: [Java Swing, Trying to replace boolean check-box in a JTable with an image-icon checkbox](https://stackoverflow.com/questions/15963338/java-swing-trying-to-replace-boolean-check-box-in-a-jtable-with-an-image-icon-c) – Denis Abakumov Oct 23 '21 at 16:54