javaswingtablecellrenderertablecelleditorjxtable

JXTable: use a TableCellEditor and TableCellRenderer for a specific cell instead of the whole column


I have a JXTable compound of 6 columns and two of them are JCheckBox. I would like to have the following behavior:

I edited an image with Photoshop to show the desired result: enter image description here

For the CheckOne and CheckTwo columns i use a custom TableCellEditor and TableCellRenderer :

public class CheckBoxCellEditor  extends AbstractCellEditor implements TableCellEditor { 

    private static final long serialVersionUID = 1L;

    private JCheckBox checkBox = new JCheckBox();

    public CheckBoxCellEditor() {       
        checkBox.setHorizontalAlignment(SwingConstants.CENTER);
    }

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

        checkBox.setSelected(value==null ? false : (boolean)value);             
        return checkBox; 
    } 


    @Override
    public Object getCellEditorValue() { 
        return checkBox.isSelected(); 
    }
}
public class CheckBoxCellRenderer extends JCheckBox implements TableCellRenderer{

    private static final long serialVersionUID = 1L;

    public CheckBoxCellRenderer() {
        setHorizontalAlignment(SwingConstants.CENTER);
    }

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

        setSelected(value==null ? false : (boolean)value);              
        return this;
    }

}

And this is how i set them:

//CheckOne
table.getColumn(4).setCellEditor(new CheckBoxCellEditor());
table.getColumn(4).setCellRenderer(new CheckBoxCellRenderer()); 
//CheckTwo
table.getColumn(5).setCellEditor(new CheckBoxCellEditor());
table.getColumn(5).setCellRenderer(new CheckBoxCellRenderer());

I tried to add a PropertyChangeListener to the JXTable and implement the behavior from there, but i couldn´t get it done.

Then i realised that my custom editor and renderer were changing all the column components at the same time instead of only the desired component. So, i tried to make the changes in the TableCellEditor and TableCellRenderer, and in the PropertyChangeListener but, again, i couldn´t figure it out.


Solution

  • First off please note your problem is not related to JXTable but renderers / editors / model.

    As @mKorbel points out JCheckBox is the default Renderer/Editor for booleans and generally speaking you won't need to re-invent the wheel: just override getColumnClass() properly to return Boolean on both 5th and 6th columns.

    However, because of this requirement:

    If the first checkbox is unchecked, the second must be disabled and unchecked.

    This is not the default renderer's behavior so you actually need your own renderer. But only renderer, you don't need an editor: as @mKorbel points out you need a little work with the table model.

    Renderer

    class CheckBoxCellRenderer implements TableCellRenderer {
    
        private final JCheckBox renderer;
    
        public CheckBoxCellRenderer() {
            renderer = new JCheckBox();
            renderer.setHorizontalAlignment(SwingConstants.CENTER);
        }
    
        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    
            Color bg = isSelected 
                    ? table.getSelectionBackground() : table.getBackground();
    
            renderer.setBackground(bg);
            renderer.setEnabled(table.isCellEditable(row, column));
            renderer.setSelected(value != null && (Boolean)value);
            return renderer;
        }
    }
    

    Model

    You need to work on both isCellEditable() and setValueAt() methods to properly update your second booleans column based on values on first one. For instance consider this snippet:

    // A default model with 5 rows and 6 columns
    DefaultTableModel model = new DefaultTableModel(5, 6) {
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            switch(columnIndex) {
                case 4:
                case 5: return Boolean.class; // Both 5th and 6th columns are booleans
            }
            return super.getColumnClass(columnIndex);
        }
    
        @Override
        public boolean isCellEditable(int row, int column) {
            /*
             * In order to know if 6th column is editable, you have to check
             * 5th column's value.
             */
            if(column == 5) {
                Object value = getValueAt(row, 4);
                return value != null && (Boolean)value;
            }
            return super.isCellEditable(row, column);
        }
    
        @Override
        public void setValueAt(Object aValue, int row, int column) {
            /*
             * If 5th column value is updated to FALSE, you need to 
             * set 6th column's value to FALSE as well
             */
            if(column == 4) {
                super.setValueAt(Boolean.FALSE, row, 5);
            } 
            super.setValueAt(aValue, row, column);
        }
    };
    

    Test it

    Here is a complete test case. Hope it helps.

    import java.awt.Color;
    import java.awt.Component;
    import javax.swing.JCheckBox;
    import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTable;
    import javax.swing.SwingConstants;
    import javax.swing.SwingUtilities;
    import javax.swing.table.DefaultTableModel;
    import javax.swing.table.TableCellRenderer;
    
    /**
     * @author dic19
     */
    public class Demo {
    
        private void createAndShowGui() {
    
            DefaultTableModel model = new DefaultTableModel(5, 6) {
                @Override
                public Class<?> getColumnClass(int columnIndex) {
                    switch(columnIndex) {
                        case 4:
                        case 5: return Boolean.class;
                    }
                    return super.getColumnClass(columnIndex);
                }
    
                @Override
                public boolean isCellEditable(int row, int column) {
                    if(column == 5) {
                        Object value = getValueAt(row, 4);
                        return value != null && (Boolean)value;
                    }
                    return super.isCellEditable(row, column);
                }
    
                @Override
                public void setValueAt(Object aValue, int row, int column) {
                    if(column == 4) {
                        super.setValueAt(false, row, 5);
                    } 
                    super.setValueAt(aValue, row, column);
                }
            };
    
            JTable table = new JTable(model);
            table.getDefaultRenderer(null);
            table.getColumnModel().getColumn(5).setCellRenderer(new CheckBoxCellRenderer());
    
            JScrollPane scrollPane = new JScrollPane(table);
    
            JFrame frame = new JFrame("Demo");
            frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            frame.add(scrollPane);
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        class CheckBoxCellRenderer implements TableCellRenderer {
    
            private final JCheckBox renderer;
    
            public CheckBoxCellRenderer() {
                renderer = new JCheckBox();
                renderer.setHorizontalAlignment(SwingConstants.CENTER);
            }
    
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    
                Color bg = isSelected 
                        ? table.getSelectionBackground() : table.getBackground();
    
                renderer.setBackground(bg);
                renderer.setEnabled(table.isCellEditable(row, column));
                renderer.setSelected(value != null && (Boolean)value);
                return renderer;
            }
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new Demo().createAndShowGui();
                }
            });
        }
    }