WesternGun
WesternGun

Reputation: 12738

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:

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

        }   
    });
}
}

Upvotes: 3

Views: 10162

Answers (3)

Younes Meridji
Younes Meridji

Reputation: 309

You forget the action listener associated to your comboBox. In other side, CheckBoxRenderer is called every time an other item is selected, so if you put a JCheckBox object as JComboBox item you have to change its status(checked or not) from outside, it means from the method called in action listener of your comboBox. But you can use the automatic calling of CheckBoxRenderer, here I made a simple code to show you how to do it:

public class ComboOfChechBox extends JFrame {

    public ComboOfChechBox() {
        begin();
    }

    //a custom item for comboBox
    public class CustomerItem {

        public String label;
        public boolean status;

        public CustomerItem(String label, boolean status) {
            this.label = label;
            this.status = status;
        }
    }

    //the class that implements ListCellRenderer
    public class RenderCheckComboBox implements ListCellRenderer {

        //a JCheckBox is associated for one item
        JCheckBox checkBox;

        Color selectedBG = new Color(112, 146, 190);

        public RenderCheckComboBox() {
            this.checkBox = new JCheckBox();
        }

        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {

            //recuperate the item value
            CustomerItem value_ = (CustomerItem) value;

            if (value_ != null) {
                //put the label of item as a label for the associated JCheckBox object
                checkBox.setText(value_.label);

                //put the status of item as a status for the associated JCheckBox object
                checkBox.setSelected(value_.status);
            }

            if (isSelected) {
                checkBox.setBackground(Color.GRAY);
            } else {
                checkBox.setBackground(Color.WHITE);
            }
            return checkBox;
        }

    }

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

        JComboBox<CustomerItem> combo = new JComboBox<CustomerItem>() {
            @Override
            public void setPopupVisible(boolean visible) {
                if (visible) {
                    super.setPopupVisible(visible);
                }
            }
        };

        CustomerItem[] items = new CustomerItem[3];
        items[0] = new CustomerItem("oh", false);
        items[1] = new CustomerItem("My", false);
        items[2] = new CustomerItem("God", false);
        combo.setModel(new DefaultComboBoxModel<CustomerItem>(items));
        combo.setRenderer(new RenderCheckComboBox());

        //the action listener that you forget
        combo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                CustomerItem item = (CustomerItem) ((JComboBox) ae.getSource()).getSelectedItem();
                item.status = !item.status;

                // update the ui of combo
                combo.updateUI();

                //keep the popMenu of the combo as visible
                combo.setPopupVisible(true);
            }
        });
        panel.add(combo);
        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() {
                new ComboOfChechBox();
            }
        });
    }
}

Upvotes: 0

nyxaria
nyxaria

Reputation: 480

I too found a work around, but using ActionListener. The fact is that you don't can't make a direct listener on JCheckBox since the render makes a new one every cycle, and the work-around by Piotr Wilkin adresses that issue. You could also use this solution which checks the position of the mouse when the JComboBox is clicked:

    comboBox.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            JComboBox combo = (JComboBox) e.getSource();
            int y = MouseInfo.getPointerInfo().getLocation().y - combo.getLocationOnScreen().y;
            int item =  y / combo.getHeight();
            ((CheckBoxRenderer) combo.getRenderer()).selected[item] = !((CheckBoxRenderer) combo.getRenderer()).selected[item];
        }
    });

also, in the getListCellRendererComponent method, you need to check that index >= 0 because when the renderer first gets created it throws an error as the selected array is null. :)

Upvotes: 0

Piotr Wilkin
Piotr Wilkin

Reputation: 3491

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

        }

    });
}

}

Upvotes: 2

Related Questions