Reputation: 12738
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. 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();
}
});
}
}
Upvotes: 3
Views: 10162
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
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
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