3

I'm trying to implement an on click color changing button inside a cell of a JTable. I did already that the color is changed onClick but what i don't get is, how the background color stays in the color the button is switched to. Anytime i loose the focus by clicking in another cell or inside the table the Button is again white. I think it has something to do with the renderers, maybe you can support me.

    signalTable = new JTable();
    signalTable.setModel(new DefaultTableModel(
        new Object[][] {
            {"AAA", "A_SIGNAL", null, null, null, null, null, null, null, null, "Example", Boolean.TRUE},
        },
        new String[] {
            "ID", "Message Identifier", "0", "1", "2", "3", "4", "5", "6", "7", "Description", ""
        }
    ) {
        Class[] columnTypes = new Class[] {
            String.class, String.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, String.class, Boolean.class
        };
        public Class getColumnClass(int columnIndex) {
            return columnTypes[columnIndex];
        }
    });
    TableColorRenderer renderer = new TableColorRenderer();
    signalTable.getColumn("0").setCellRenderer(renderer);
    signalTable.getColumn("1").setCellRenderer(renderer);
    signalTable.getColumn("2").setCellRenderer(renderer);
    signalTable.getColumn("3").setCellRenderer(renderer);
    signalTable.getColumn("4").setCellRenderer(renderer);
    signalTable.getColumn("5").setCellRenderer(renderer);
    signalTable.getColumn("6").setCellRenderer(renderer);
    signalTable.getColumn("7").setCellRenderer(renderer);
    signalTable.getColumn("0").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("1").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("2").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("3").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("4").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("5").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("6").setCellEditor(new ButtonEditor(new JCheckBox()));
    signalTable.getColumn("7").setCellEditor(new ButtonEditor(new JCheckBox()));

ButtonEditor.java:

public class ButtonEditor extends DefaultCellEditor
{
    protected JButton button;

    public ButtonEditor(JCheckBox checkBox) 
    {
        super(checkBox);
        button = new JButton();
        button.setOpaque(true);
        button.addActionListener(new ActionListener() 
        {
            public void actionPerformed(ActionEvent e)
            {
                button.setBackground(Color.GREEN);
                button.repaint();
            }
        });
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
        boolean isSelected, int row, int column) 
    {
        return button;
    }

}

TableColorRenderer.java:

public class TableColorRenderer extends JLabel implements TableCellRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) 
    {
        return this;
    }

When clicked on invisible Button

After clicked in another row

NFoerster
  • 386
  • 2
  • 16
  • why is there description about JButton and the code shows JCheckBox – mKorbel Jan 06 '16 at 17:01
  • 1. read Oracle tutorial How to use Tables, boolean value stored in the model JCheckBox represents AbstractButton.isSelected, 2. there no reason to use custom (and wrong) Renderer and Editor (missing stopCellEditing, and to store boolean value in the XxxTableModel), then Renderer hasn't any value stored in model, signal to changes the paitning in Renderer – mKorbel Jan 06 '16 at 18:16

2 Answers2

2

You're clearly misunderstanding the roles of the editor and the renderer. A editor allows a user to edit the state of a cell, a renderer renders the state of the cell.

They do this through the use of the TableModel. Start by having a look at Concepts: Editors and Renderers and Using Other Editors for more details.

To start with the things you're doing wrong...

signalTable.setModel(new DefaultTableModel(
    new Object[][] {
        {"AAA", "A_SIGNAL", null, null, null, null, null, null, null, null, "Example", Boolean.TRUE},
    },
    new String[] {
        "ID", "Message Identifier", "0", "1", "2", "3", "4", "5", "6", "7", "Description", ""
    }
) {
    Class[] columnTypes = new Class[] {
        String.class, String.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, JButton.class, String.class, Boolean.class
    };
    public Class getColumnClass(int columnIndex) {
        return columnTypes[columnIndex];
    }
});

The cell values for the "color" cells should true or false (they can be any other "on"/"off" value you want, so long as the editor and renderer know how to deal with them, but for this example, I'm using booleans)

The columnTypes for these cells should also be Boolean.class

signalTable.setModel(new DefaultTableModel(
        new Object[][]{
            {"AAA", "A_SIGNAL", false, false, false, false, false, false, false, false, "Example", Boolean.TRUE},},
        new String[]{
            "ID", "Message Identifier", "0", "1", "2", "3", "4", "5", "6", "7", "Description", ""
        }
) {
    Class[] columnTypes = new Class[]{
        String.class, String.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, String.class, Boolean.class
    };

    public Class getColumnClass(int columnIndex) {
        return columnTypes[columnIndex];
    }
});

So, now, if you did nothing else, you would have JCheckBoxs in those cells, but that's not what we want.

public class ButtonEditor extends DefaultCellEditor
{
    protected JButton button;

    public ButtonEditor(JCheckBox checkBox) 
    {
        super(checkBox);
        button = new JButton();
        button.setOpaque(true);
        button.addActionListener(new ActionListener() 
        {
            public void actionPerformed(ActionEvent e)
            {
                button.setBackground(Color.GREEN);
                button.repaint();
            }
        });
    }

    public Component getTableCellEditorComponent(JTable table, Object value,
        boolean isSelected, int row, int column) 
    {
        return button;
    }

}

Here, you are ignoring the JCheckBox which is passed to your editor and creating your own, but there are two problems with this. The DefaultCellEditor is deriving the cells value from the JCheckBox, not some other state (so it's always false) and you never configure the button to represent the current state of the cell when it's requested.

Because I don't want to deal with other components, I'm going to do something a little different.

public class ButtonEditor extends AbstractCellEditor implements TableCellEditor {

    private JLabel editor;

    public ButtonEditor() {
        editor = new JLabel();
        editor.setBackground(Color.GREEN);

        editor.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                editor.setOpaque(!editor.isOpaque());
                stopCellEditing();
            }
        });
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        if (value instanceof Boolean) {
            boolean result = (boolean) value;
            editor.setOpaque(!result);
        }
        return editor;
    }

    @Override
    public Object getCellEditorValue() {
        return editor.isOpaque();
    }

    @Override
    public boolean isCellEditable(EventObject e) {
        return true;
    }

}

This is just a JLabel which when clicked, will switch it's opaque state. We also use this state to determine the cell value when the editor is "stopped"

public class TableColorRenderer extends JLabel implements TableCellRenderer
{
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) 
    {
        return this;
    }
}

Well, frankly, that's a bit useless, it never does anything with the value of cell

public class TableColorRenderer extends JLabel implements TableCellRenderer {

    public TableColorRenderer() {
        setBackground(Color.GREEN);
    }

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

Again, pretty simple. We check the value of the cell (as passed to us) and change the opaque state accordingly

And because no one ever believes me, a runnable example

Editor

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.AbstractCellEditor;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

public class Example {

    public static void main(String[] args) {
        new Example();
    }

    public Example() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private final JTable signalTable;

        public TestPane() {
            signalTable = new JTable();
    signalTable.setModel(new DefaultTableModel(
            new Object[][]{
                {"AAA", "A_SIGNAL", false, false, false, false, false, false, false, false, "Example", Boolean.TRUE},},
            new String[]{
                "ID", "Message Identifier", "0", "1", "2", "3", "4", "5", "6", "7", "Description", ""
            }
    ) {
        Class[] columnTypes = new Class[]{
            String.class, String.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class, String.class, Boolean.class
        };

        public Class getColumnClass(int columnIndex) {
            return columnTypes[columnIndex];
        }
    });
            TableColorRenderer renderer = new TableColorRenderer();
            signalTable.getColumn("0").setCellRenderer(renderer);
            signalTable.getColumn("1").setCellRenderer(renderer);
            signalTable.getColumn("2").setCellRenderer(renderer);
            signalTable.getColumn("3").setCellRenderer(renderer);
            signalTable.getColumn("4").setCellRenderer(renderer);
            signalTable.getColumn("5").setCellRenderer(renderer);
            signalTable.getColumn("6").setCellRenderer(renderer);
            signalTable.getColumn("7").setCellRenderer(renderer);
            signalTable.getColumn("0").setCellEditor(new ButtonEditor());
            signalTable.getColumn("1").setCellEditor(new ButtonEditor());
            signalTable.getColumn("2").setCellEditor(new ButtonEditor());
            signalTable.getColumn("3").setCellEditor(new ButtonEditor());
            signalTable.getColumn("4").setCellEditor(new ButtonEditor());
            signalTable.getColumn("5").setCellEditor(new ButtonEditor());
            signalTable.getColumn("6").setCellEditor(new ButtonEditor());
            signalTable.getColumn("7").setCellEditor(new ButtonEditor());

            setLayout(new BorderLayout());
            add(new JScrollPane(signalTable));
        }

    }

    public class ButtonEditor extends AbstractCellEditor implements TableCellEditor {

        private JLabel editor;

        public ButtonEditor() {
            editor = new JLabel();
            editor.setBackground(Color.GREEN);

            editor.addMouseListener(new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    editor.setOpaque(!editor.isOpaque());
                    stopCellEditing();
                }
            });
        }

        @Override
        public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
            if (value instanceof Boolean) {
                boolean result = (boolean) value;
                editor.setOpaque(!result);
            }
            return editor;
        }

        @Override
        public Object getCellEditorValue() {
            return editor.isOpaque();
        }

        @Override
        public boolean isCellEditable(EventObject e) {
            return true;
        }

    }

    public class TableColorRenderer extends JLabel implements TableCellRenderer {

        public TableColorRenderer() {
            setBackground(Color.GREEN);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
            if (value instanceof Boolean) {
                boolean result = (boolean) value;
                setOpaque(result);
            }
            return this;
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
1

Find below a small demo application which uses a JButton in the cell. The button text is there to demonstrate the logic which MadProgrammer explained. The button text is edit btn. resp. render btn.. So it is visible which button actually is shown. The edit button is shown when you click on the cell.

import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import static javax.swing.GroupLayout.PREFERRED_SIZE;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.table.DefaultTableModel;

public class FrameDemo extends JFrame {

    public FrameDemo() {
        initComponents();
    }

    private void initComponents() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        JTable signalTable = new JTable();
        signalTable.setModel(new DefaultTableModel(
                new Object[][]{
                    {"AAA", "A_SIGNAL", FALSE, FALSE, FALSE, TRUE},
                    {"BBB", "B_SIGNAL", FALSE, FALSE, FALSE, TRUE},
                    {"CCC", "C_SIGNAL", FALSE, FALSE, FALSE, TRUE},
                    {"DDD", "C_SIGNAL", FALSE, FALSE, FALSE, TRUE}
                },
                new String[]{"ID", "Message Identifier", "0", "1", "2", ""}
        ));

        CellButton cellButton = new CellButton();
        signalTable.getColumn("0").setCellRenderer(cellButton);
        signalTable.getColumn("0").setCellEditor(cellButton);
        signalTable.getColumn("1").setCellRenderer(cellButton);
        signalTable.getColumn("1").setCellEditor(cellButton);
        signalTable.getColumn("2").setCellRenderer(cellButton);
        signalTable.getColumn("2").setCellEditor(cellButton);

        JScrollPane scrollPane = new javax.swing.JScrollPane();
        scrollPane.setViewportView(signalTable);

        GroupLayout layout = new GroupLayout(getContentPane());

        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
                layout.createParallelGroup(Alignment.LEADING)
                .addGroup(Alignment.TRAILING, layout.createSequentialGroup()
                        .addContainerGap(10, Short.MAX_VALUE)
                        .addComponent(scrollPane, PREFERRED_SIZE, 700, 
                            PREFERRED_SIZE)
                        .addContainerGap())
        );
        layout.setVerticalGroup(
                layout.createParallelGroup(Alignment.LEADING)
                .addGroup(Alignment.TRAILING, layout.createSequentialGroup()
                        .addContainerGap(10, Short.MAX_VALUE)
                        .addComponent(scrollPane, PREFERRED_SIZE, 275, 
                            PREFERRED_SIZE)
                        .addContainerGap())
        );
        pack();
    }

    public static void main(String args[]) {
        for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                try {
                    UIManager.setLookAndFeel(info.getClassName());
                } catch (Exception ex) {
                    System.err.println("use default L&F");
                }
                break;
            }
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new FrameDemo().setVisible(true);
            }
        });
    }
}

The class for the cell editor and renderer.

import java.awt.Color;
import static java.awt.Color.BLACK;
import static java.awt.Color.GREEN;
import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;

public class CellButton extends AbstractCellEditor
        implements TableCellEditor, TableCellRenderer {

    private final JButton editButton = new JButton("edit btn.");
    private final JButton renderButton = new JButton("render btn.");
    private final Border focusBorder = BorderFactory.createLineBorder(BLACK);
    private final Border defaultBorder;
    private final Color inactiveColor;
    private Boolean state = FALSE;

    public CellButton() {
        defaultBorder = renderButton.getBorder();
        inactiveColor = renderButton.getBackground();
        editButton.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseExited(MouseEvent e) {
                cancelCellEditing();
            }
        });

        editButton.addActionListener((e) -> {stopCellEditing();});
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        state = FALSE.equals(value);
        editButton.setBackground(state ? GREEN : inactiveColor);
        table.setValueAt(state, row, column);
        return editButton;
    }

    @Override
    public Object getCellEditorValue() {
        return state;
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        renderButton.setBorder(hasFocus ? focusBorder : defaultBorder);
        renderButton.setBackground(TRUE.equals(value) ? GREEN : inactiveColor);
        return renderButton;
    }
}

The button can be toggled with the mouse or keyboard.

enter image description here

SubOptimal
  • 22,518
  • 3
  • 53
  • 69