Search code examples
javaswingpopupjcomboboxjcheckbox

Create a combobox with multiple checkbox


I have read the doc and tutorial, and searchd here, to no avail.

Oracle tutorial: how to use custom render for ComboBox

Another question similar with a somehow vague answer

And I see it important because many people asked about it but no one can provide a simple, workable example. So I must ask it myself:

How can we make a combobox with a drop-down menu, allowing us to choose more than one options?

What is not working:

  • JList proved to be useless here, because I cannot make it appear in the drop-down menu.
  • There's no CheckBoxList in Swing.

I have done a SCCEE with checkbox in drop-down menu of a combo, but the checkboxes refuse to be selected, the check in the box is missing.

How can we achieve that?

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.util.List;

import javax.swing.DefaultCellEditor;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;

public class ComboOfCheckBox extends JFrame {

public ComboOfCheckBox() {
    begin();
}

private void begin() {
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel panel = new JPanel();

    JTable table = new JTable(new Object[2][2], new String[]{"COL1", "COL2"});
    final JCheckBox chx1 = new JCheckBox("Oh");
    final JCheckBox chx2 = new JCheckBox("My");
    final JCheckBox chx3 = new JCheckBox("God");
    String[] values = new String[] {"Oh", "My", "God"};
    JCheckBox[] array = new JCheckBox[] {chx1, chx2, chx3};
    final JComboBox<JCheckBox> comboBox = new JComboBox<JCheckBox>(array) {
        @Override
        public void setPopupVisible(boolean visible){
            if (visible) {
                super.setPopupVisible(visible);
            }
        }
    };

    class CheckBoxRenderer  implements ListCellRenderer {

        private boolean[] selected;
        private String[] items;

        public CheckBoxRenderer(String[] items) {
            this.items = items;
            this.selected = new boolean[items.length];
        }

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {
            JLabel label = null;
            JCheckBox box = null;
            if (value instanceof JCheckBox) {
                label = new JLabel(((JCheckBox)value).getText());
                box = new JCheckBox(label.getText());
            }
            return box;
        }
        public void setSelected(int i, boolean selected) {
            this.selected[i] = selected;
        }

    }

    comboBox.setRenderer(new CheckBoxRenderer(values));

    panel.add(comboBox);    
    panel.add(new JCheckBox("Another"));
    getContentPane().add(panel);
    pack();
    setVisible(true);
}

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {

        @Override
        public void run() {
            ComboOfCheckBox frame = new ComboOfCheckBox();

        }   
    });
}
}

Solution

  • Here's a partial answer to go with. It doesn't address the issue of the ComboBox masking events on the popup, but it does work around it. The problem is still that the ComboBox treats each select on one item as a deselect on another. However, one problem you were facing is that, since the renderer is called every time upon repaint, your CheckBoxes weren't persistent - the Map addresses that.

    public class ComboOfCheckBox extends JFrame {
    
    public ComboOfCheckBox() {
        begin();
    }
    
    private void begin() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel panel = new JPanel();
    
        JTable table = new JTable(new Object[2][2], new String[]{"COL1", "COL2"});
        String[] values = new String[] {"Oh", "My", "God"};
        final JComboBox<String> comboBox = new JComboBox<String>(values) {
            @Override
            public void setPopupVisible(boolean visible){
                if (visible) {
                    super.setPopupVisible(visible);
                }
            }
        };
    
        class CheckBoxRenderer  implements ListCellRenderer<Object> {
            private Map<String, JCheckBox> items = new HashMap<>();
            public CheckBoxRenderer(String[] items) {
                for (String item : items) {
                    JCheckBox box = new JCheckBox(item);
                    this.items.put(item, box);
                }
    
            }
            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected,
                                                          boolean cellHasFocus) {
                if (items.containsKey(value)) {
                    return items.get(value);
                } else {
                    return null;
                }
            }
    
            public void setSelected(String item, boolean selected) {
                if (item.contains(item)) {
                    JCheckBox cb = items.get(item);
                    cb.setSelected(selected);
                }
            }
        }
    
        final CheckBoxRenderer renderer = new CheckBoxRenderer(values);
    
        comboBox.setRenderer(renderer);
        comboBox.addItemListener(e -> {
            String item = (String) e.getItem();
            if (e.getStateChange() == ItemEvent.DESELECTED) {
                renderer.setSelected(item, false);
            } else {
                renderer.setSelected(item, true);
            }
        });
    
        panel.add(comboBox);
    
        panel.add(new JCheckBox("Another"));
        getContentPane().add(panel);
        pack();
        setVisible(true);
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
    
            @Override
            public void run() {
                ComboOfCheckBox frame = new ComboOfCheckBox();
    
            }
    
        });
    }
    

    }