【问题标题】:Create a combobox with multiple checkbox创建一个带有多个复选框的组合框
【发布时间】:2016-08-25 11:21:39
【问题描述】:

我已阅读文档和教程,并在此处搜索,但无济于事。

Oracle tutorial: how to use custom render for ComboBox

Another question similar with a somehow vague answer

我认为这很重要,因为很多人都问过这个问题,但没有人能提供一个简单、可行的例子。所以我必须自己问:

我们如何制作一个带有下拉菜单的组合框,让我们可以选择多个选项?

什么不工作:

  • JList 在这里被证明是没用的,因为我不能让它出现在下拉菜单中。
  • Swing 中没有 CheckBoxList

我已经在组合的下拉菜单中使用复选框进行了 SCCEE,但复选框拒绝被选中,复选框中的复选框丢失。

我们怎样才能做到这一点?

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

        }   
    });
}
}

【问题讨论】:

  • @FaithReaper - How can we make a combobox with a drop-down menu, allowing us to choose more than one options? == 1. 在某些操作、选择(鼠标/键事件)之后,弹出窗口不会消失或保持可见,2. 那么有一个问题,你如何可以/你想隐藏弹出窗口(以避免混淆用户)
  • @FaithReaper - here is half way to your goal,您可以使用 JWindow 并检查 mouseEvents(来自 SwingUtilities)是否有特殊弹出窗口(这不会在第一次隐藏。鼠标/keyEvent)
  • @mKorbel 我正在和 Andrew 交谈......他坚持认为这是不可能的。至于你的顾虑,我已经找到了解决方法,这些行:@Override public void setPopupVisible(boolean visible){ if (visible) { super.setPopupVisible(visible); } }。诀窍是:只听visible==true。当弹出菜单被通知visible==false 时,忽略它。当我点击其他地方时,弹出菜单会隐藏。运行我的 SCCEE,你会看到它。
  • 下面的方法怎么样:制作一个支持多选的swing组件 b.尝试将其用作渲染器?
  • ... no one can provide a simple, workable example. - 因为尝试在圆孔中安装方形钉子不是一个好主意(由于太多原因在评论中列出)。组合框用于选择单个项目。仅仅因为组合框显示弹出窗口并不意味着它应该用于选择弹出窗口中的多个项目。有更好的 Swing 组件可以使用,例如 JPopupMenu。它允许您在弹出窗口中显示JCheckBoxMenuItems。有关此方法的示例,请参阅 Table Column Adjuster

标签: java swing popup jcombobox jcheckbox


【解决方案1】:

这里有一个部分答案。它没有解决弹出窗口上的 ComboBox 屏蔽事件的问题,但它确实可以解决这个问题。问题仍然是 ComboBox 将一个项目上的每个选择视为另一个项目上的取消选择。但是,您面临的一个问题是,由于每次重绘时都会调用渲染器,因此您的复选框不是持久的 - 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();

        }

    });
}

}

【讨论】:

  • (1-) 那么知道吗?这样做的原因大概是您希望能够查询每个复选框的状态以进行其他类型的处理。那么你打算怎么做呢?您是否要向渲染器添加一个方法来检索每个复选框的状态?这越来越疯狂,甚至不应该尝试。这就是 JTable 和 JList 等组件具有“SelectionModel”的原因。 JComboBox 不是工作的组件,故事结束。
  • 我也希望我们在 Swing 中有CheckBoxList,但实际上没有。也许@Piotr 的方法是一个好的开始。可以通过从内部类中提取 Map 来避免每次构建渲染器时创建复选框,但这不是很好......
  • 我仔细查看了 JComboBox 的源代码,发现通过设计选择 1+ 项是不可能的,因为在 JComboBox.class 中有一个 protected void selectedItemChanged() 方法会触发 ItemEvent 到通知取消选择的状态更改,并且在它的 JavaDoc 中,声明 此受保护的方法是特定于实现的。不要直接访问或覆盖。。原来消费这个Event的想法现在已经不可能了。但是在这里很好尝试。我可能会采用添加带有按钮的JPopupMenu 的想法或单击时显示...
  • @camickr:说真的,我不明白你为什么如此热衷于劝阻人们寻求解决方案。原作者要求一个具有多选功能的 ComboBox。我们知道 Swing 本身不支持它。我们知道有多个组件可以做到这一点。他们都不适合这份工作。可能的解决方案是:做一个由 JList 和其他东西组成的复合组件来充当带有箭头的标签框或扩展 JComboBox。从某种意义上说,它们都是hackish。
  • doing a compound component consisting of a JList and something else to act as the label box with the arrow or extending JComboBox - 完全正确!!!这就是组合框的作用。它将 JTextField、JButton、JPopup、JScrollPane、JList 组合成一个可行的组件。如果您需要不同的功能,那么您没有理由不能创建自己的自定义复合组件来执行特定功能。它根本不是 hackis,它是 Swing 开发人员已经做过的。与 JScrollPane 或 JSplitPane 相同。当您需要更复杂的功能时,您需要更复杂的组件。
【解决方案2】:

我也找到了解决方法,但使用 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 &gt;= 0,因为当第一次创建渲染器时,它会抛出错误,因为selected 数组为空。 :)

【讨论】:

  • 这很好。查看鼠标位置确实是一个丑陋的 hack,但由于事件屏蔽可能需要。
  • @PiotrWilkin 我同意它非常 hackish,从这个意义上说我认为你的解决方案更干净.. 但我个人喜欢找到问题的解决方案,越是 hackish,我就越兴奋跨度>
  • 令人惊讶的是有多少种不同的方式可以解决编程中的问题:D
  • 在空闲时间,我可能会采用我的解决方案,您的解决方案并将其融合到某个 JMultipleCheckboxComboBox 类中。
  • 我的解决方案更干净,但我的解决方案不是解决方案 - 只是解决其中一个问题。另一方面,您的解决方案是屏蔽事件问题的解决方案:)
【解决方案3】:

您忘记了与您的组合框关联的动作侦听器。另一方面,每次选择其他项目时都会调用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();
            }
        });
    }
}

【讨论】:

    猜你喜欢
    • 2016-12-02
    • 1970-01-01
    • 1970-01-01
    • 2015-05-15
    • 2013-06-15
    • 2014-12-25
    • 2020-04-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多