JXTable: use a TableCellEditor and TableCellRender

2019-05-28 12:25发布

问题:

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

  • If the first checkbox is checked, the second checkbox is enabled and can be checked or not.
  • If the first checkbox is unchecked, the second must be disabled and unchecked.

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

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.

回答1:

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();
            }
        });
    }
}