创建一个带有多个复选框的组合框
Create a combobox with multiple checkbox
我已经阅读了文档和教程,并在此处进行了搜索,但无济于事。
Oracle tutorial: how to use custom render for ComboBox
Another question similar with a somehow vague answer
我认为它很重要,因为很多人都问过它,但没有人能提供一个简单、可行的例子。所以我必须自己问:
我们如何制作一个带有下拉菜单的组合框,让我们可以选择多个选项?
什么不起作用:
JList
在这里被证明是没有用的,因为我不能让它出现在下拉菜单中。
- Swing 中没有
CheckBoxList
。
我做了一个SCCEE,在combo的下拉菜单中有checkbox,但是checkboxes拒绝被选中,box里面的check不见了。
我们怎样才能做到这一点?
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();
}
});
}
}
这里有部分答案可供参考。它没有解决弹出窗口中 ComboBox 屏蔽事件的问题,但它确实解决了这个问题。问题仍然是 ComboBox 将一个项目上的每个 select 视为另一个项目上的 deselect。但是,您面临的一个问题是,由于每次重绘时都会调用渲染器,因此您的 CheckBoxes 不是持久的 - Map
解决了这个问题。
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();
}
});
}
}
我也找到了解决方法,但使用 ActionListener
。事实上,您不能在 JCheckBox
上直接监听,因为渲染器每个周期都会创建一个新监听器,而 Piotr Wilkin 的解决方法解决了这个问题。您还可以使用此解决方案在单击 JComboBox
时检查鼠标的位置:
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];
}
});
此外,在 getListCellRendererComponent
方法中,您需要检查 index >= 0
,因为渲染器首次创建时会抛出错误,因为 selected
数组为空。 :)
您忘记了与组合框关联的动作侦听器。另一方面,每次选择其他项目时都会调用 CheckBoxRenderer
,因此如果将 JCheckBox
对象作为 JComboBox
项目,则必须从外部更改其状态(选中或未选中) ,这意味着从您的组合框的动作侦听器中调用的方法。但是你可以使用CheckBoxRenderer
的自动调用,这里我做了一个简单的代码来告诉你如何做:
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();
}
});
}
}
我已经阅读了文档和教程,并在此处进行了搜索,但无济于事。
Oracle tutorial: how to use custom render for ComboBox
Another question similar with a somehow vague answer
我认为它很重要,因为很多人都问过它,但没有人能提供一个简单、可行的例子。所以我必须自己问:
我们如何制作一个带有下拉菜单的组合框,让我们可以选择多个选项?
什么不起作用:
JList
在这里被证明是没有用的,因为我不能让它出现在下拉菜单中。- Swing 中没有
CheckBoxList
。
我做了一个SCCEE,在combo的下拉菜单中有checkbox,但是checkboxes拒绝被选中,box里面的check不见了。
我们怎样才能做到这一点?
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();
}
});
}
}
这里有部分答案可供参考。它没有解决弹出窗口中 ComboBox 屏蔽事件的问题,但它确实解决了这个问题。问题仍然是 ComboBox 将一个项目上的每个 select 视为另一个项目上的 deselect。但是,您面临的一个问题是,由于每次重绘时都会调用渲染器,因此您的 CheckBoxes 不是持久的 - Map
解决了这个问题。
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();
}
});
}
}
我也找到了解决方法,但使用 ActionListener
。事实上,您不能在 JCheckBox
上直接监听,因为渲染器每个周期都会创建一个新监听器,而 Piotr Wilkin 的解决方法解决了这个问题。您还可以使用此解决方案在单击 JComboBox
时检查鼠标的位置:
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];
}
});
此外,在 getListCellRendererComponent
方法中,您需要检查 index >= 0
,因为渲染器首次创建时会抛出错误,因为 selected
数组为空。 :)
您忘记了与组合框关联的动作侦听器。另一方面,每次选择其他项目时都会调用 CheckBoxRenderer
,因此如果将 JCheckBox
对象作为 JComboBox
项目,则必须从外部更改其状态(选中或未选中) ,这意味着从您的组合框的动作侦听器中调用的方法。但是你可以使用CheckBoxRenderer
的自动调用,这里我做了一个简单的代码来告诉你如何做:
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();
}
});
}
}