jcombobox 中的复选框:从未选中

checkboxes in jcombobox : never checked

我对 java swing 中包含一些 JCheckBox 的 JCombobox 有疑问。 问题是复选框从未被选中,因为渲染器似乎只收到未select编辑的值;但是模型确实包含检查值,我用调试器验证了它。不知道问题出在哪里

代码如下:

主要class构造函数的一部分:

cbb_keywords = new JComboBox();
cbb_keywords.setName("cbb_keywords");
cbb_keywords.addActionListener(this);
cbb_keywords.setMaximumRowCount(5);
cbb_keywords.setRenderer(new CkbKeywordsRenderer(""));
cbbmodel = new DefaultComboBoxModel<CkbKeywordsRenderer>();
cbb_keywords.setModel(cbbmodel);
cbb_keywords.setEditable(true);

应该触发 JCombobox 中某些 JCheckboxes 显示的代码:

public void setKeywords(Keywords keywords) {
    txf_keywords.setText(keywords.toString());

    DefaultComboBoxModel model = extractComboboxModel();
    for (int i = 0; i < model.getSize(); i++) {
      CkbKeywordsRenderer ckbrenderer =
              new CkbKeywordsRenderer(
                      ((CkbKeywordsRenderer) model.getElementAt(i))
                              .getText());
      if (keywords.contains(ckbrenderer.getText()))
        ckbrenderer.setSelected(true);
      else
        ckbrenderer.setSelected(false);

    }
    cbb_keywords.setModel(model);
}

此方法可能需要一些解释: * 首先,"model" 填充存储在 JTable 中的关键字的总数;每行对应一本书,每本书包含一个关键字列表。 "model" 的填充从收集所有关键字开始,然后继续删除双打。 使用我的调试器,我看到该字段包含 4 个关键字:"aventure"、"jeunesse"、"maths"、"philo"。 * 然后,我测试模型的每个关键字,看它是否在本书的关键字列表中(变量 "keywords")(它们是:"aventure",和 "jeunesse")。 因此,在组合框的列表中,我应该有 4 个项目,其中 2 个被选中:"aventure" 和 "jeunesse")。如果我在另一本书的 JTable 中 select,则应在 JCombobox 中检查其他 2 个关键字。 * 此方法已经过测试 returns 一个有效模型:4 个项目,其中 2 个是 "selected"

现在这里是渲染器 class,它从不接收检查值:

 public class CkbKeywordsRenderer extends JCheckBox implements ListCellRenderer,
        ActionListener {

  static CkbKeywordsRenderer[] elements;

  public CkbKeywordsRenderer(String text) {
    super(text);

  }

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

    CkbKeywordsRenderer selectedItem = (CkbKeywordsRenderer) value;

    if (isSelected) {
      setBackground(list.getSelectionBackground());
      setForeground(list.getSelectionForeground());
    } else {
      setBackground(list.getBackground());
      setForeground(list.getForeground());
    }
    if (selectedItem != null) {

      setText(selectedItem.getText());
      setSelected(selectedItem.isSelected());
    } else if (index == -1 && value == null) setText("abc");
    return this;
  }


  @Override
  public void actionPerformed(ActionEvent e) {
    JComboBox cb = (JComboBox) e.getSource();
    CkbKeywordsRenderer jcheckBox = (CkbKeywordsRenderer) cb.getSelectedItem();
    jcheckBox.setSelected(!jcheckBox.isSelected());
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        cb.showPopup();
      }
    });

  }

你看出问题出在哪里了吗? 谢谢

编辑:没有答案......我试图调整我的程序(模型中没有 JCOmponent)但渲染器仍然得到未经检查的值:

请看一下这两张很好地总结了我的问题的图片:

你可以看到填充模型的方法正确,检查了 4 个项目中的 2 个。

但是 您可以看到渲染器得到了错误的值。

对了,这里是值的class(很简单):

public class ItemCombobox {

  public String itemName;
  public boolean checked;

  public ItemCombobox(String itemName, boolean checked) {
    this.itemName = itemName;
    this.checked = checked;
  }


  @Override
  public String toString() {
    return itemName;
  }
}

渲染器的新代码在第一张图片中给出。

谢谢。

Swing 基于 "model-view-controller" 范式。这决定了数据和视图之间的分离。

这意味着信息需要 render/display 并且数据片段与数据本身分开。这意味着数据可以以多种独立的方式表示,具体取决于您尝试使用的内容。

这意味着,模型应该只携带数据。它不应该携带任何类型的 UI 元素。

Swing 还使用 "delegation" 范例来允许您自定义呈现不同元素的 UI 组件的数量。首先看看 Concepts: Editors and Renderers - 这是一个需要理解的非常重要的概念,因为它几乎无处不在。

您还应该查看 How to use lists, writing a custom cell renderer 了解更具体的细节。

这是一个非常基本的示例,它使用您的 ItemCombobox 作为基本构建块来生成 JList,它使用 JCheckBox 作为基本渲染器显示项目

import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import javax.swing.DefaultListModel;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                DefaultListModel<Item> itemListModel = new DefaultListModel<>();
                itemListModel.addElement(new Item("A", false));
                itemListModel.addElement(new Item("B", true));
                itemListModel.addElement(new Item("C", false));
                itemListModel.addElement(new Item("D", false));
                itemListModel.addElement(new Item("E", true));
                itemListModel.addElement(new Item("F", true));
                itemListModel.addElement(new Item("G", false));
                itemListModel.addElement(new Item("H", true));

                JList list = new JList(itemListModel);
                list.setCellRenderer(new CheckBoxListCellRenderer());

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JScrollPane(list));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Item {

        public String itemName;
        public boolean checked;

        public Item(String itemName, boolean checked) {
            this.itemName = itemName;
            this.checked = checked;
        }

        public boolean isChecked() {
            return checked;
        }

        public String getItemName() {
            return itemName;
        }

        @Override
        public String toString() {
            return itemName;
        }
    }

    public static class CheckBoxListCellRenderer extends JCheckBox implements ListCellRenderer<Item> {

        private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);

        public CheckBoxListCellRenderer() {
            setOpaque(false);
            setBorder(DEFAULT_NO_FOCUS_BORDER);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends Item> list, Item value, int index, boolean isSelected, boolean cellHasFocus) {
            setSelected(value.isChecked());
            setText(value.getItemName());
            Color fg = list.getForeground();
            if (isSelected) {
                setBackground(list.getSelectionBackground());
                fg = list.getSelectionForeground();
            } else {
                setBackground(list.getBackground());
            }
            setForeground(fg);
            setOpaque(isSelected);
            Border border = null;
            if (cellHasFocus) {
                if (isSelected) {
                    border = UIManager.getBorder("List.focusSelectedCellHighlightBorder");
                }
                if (border == null) {
                    border = UIManager.getBorder("List.focusCellHighlightBorder");
                }
            } else {
                border = DEFAULT_NO_FOCUS_BORDER;
            }
            setBorder(border);
            return this;
        }

    }

}

But how do I update the item in the list if it changes?

通常,我会提供自定义 ListModel 来处理这个问题,但是,您可以使用 DefaultListModelsetElementAt 来触发列表以重新呈现指定的项目。

这个例子只是添加了一个按钮,当它被触发时,将改变 selected 项目的 selected 状态(或者第一个项目,如果没有 selected )

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                DefaultListModel<Item> itemListModel = new DefaultListModel<>();
                itemListModel.addElement(new Item("A", false));
                itemListModel.addElement(new Item("B", true));
                itemListModel.addElement(new Item("C", false));
                itemListModel.addElement(new Item("D", false));
                itemListModel.addElement(new Item("E", true));
                itemListModel.addElement(new Item("F", true));
                itemListModel.addElement(new Item("G", false));
                itemListModel.addElement(new Item("H", true));

                JList list = new JList(itemListModel);
                list.setCellRenderer(new CheckBoxListCellRenderer());

                JButton change = new JButton("Change");
                change.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int index = list.getSelectedIndex();
                        if (index == -1) {
                            index = 0;
                        }
                        Item item = itemListModel.get(index);
                        item.setChecked(!item.isChecked());

                        // Force an update of the specified element
                        itemListModel.setElementAt(item, index);
                    }
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JScrollPane(list));
                frame.add(change, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Item {

        public String itemName;
        public boolean checked;

        public Item(String itemName, boolean checked) {
            this.itemName = itemName;
            this.checked = checked;
        }

        public void setChecked(boolean checked) {
            this.checked = checked;
        }

        public boolean isChecked() {
            return checked;
        }

        public String getItemName() {
            return itemName;
        }

        @Override
        public String toString() {
            return itemName;
        }
    }

    public static class CheckBoxListCellRenderer extends JCheckBox implements ListCellRenderer<Item> {

        private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);

        public CheckBoxListCellRenderer() {
            setOpaque(false);
            setBorder(DEFAULT_NO_FOCUS_BORDER);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends Item> list, Item value, int index, boolean isSelected, boolean cellHasFocus) {
            setSelected(value.isChecked());
            setText(value.getItemName());
            Color fg = list.getForeground();
            if (isSelected) {
                setBackground(list.getSelectionBackground());
                fg = list.getSelectionForeground();
            } else {
                setBackground(list.getBackground());
            }
            setForeground(fg);
            setOpaque(isSelected);
            Border border = null;
            if (cellHasFocus) {
                if (isSelected) {
                    border = UIManager.getBorder("List.focusSelectedCellHighlightBorder");
                }
                if (border == null) {
                    border = UIManager.getBorder("List.focusCellHighlightBorder");
                }
            } else {
                border = DEFAULT_NO_FOCUS_BORDER;
            }
            setBorder(border);
            return this;
        }

    }

}

But how to I allow the user to select/deselect an item from the JList?

对我来说,这就是使用 JList 来完成此类任务的概念开始瓦解的地方,因为 JList 通常不能编辑。对我来说,I'd prefer a JTable based solution instead,然而...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                DefaultListModel<Item> itemListModel = new DefaultListModel<>();
                itemListModel.addElement(new Item("A", false));
                itemListModel.addElement(new Item("B", true));
                itemListModel.addElement(new Item("C", false));
                itemListModel.addElement(new Item("D", false));
                itemListModel.addElement(new Item("E", true));
                itemListModel.addElement(new Item("F", true));
                itemListModel.addElement(new Item("G", false));
                itemListModel.addElement(new Item("H", true));

                JList list = new JList(itemListModel);
                list.setCellRenderer(new CheckBoxListCellRenderer());

                JButton change = new JButton("Change");
                change.addActionListener(new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        int index = list.getSelectedIndex();
                        if (index == -1) {
                            index = 0;
                        }
                        Item item = itemListModel.get(index);
                        item.setChecked(!item.isChecked());

                        // Force an update of the specified element
                        itemListModel.setElementAt(item, index);
                    }
                });

                list.addMouseListener(new MouseAdapter() {
                    @Override
                    public void mouseClicked(MouseEvent e) {
                        int index = list.locationToIndex(e.getPoint());
                        if (index < 0) {
                            return;
                        }
                        Item item = itemListModel.get(index);
                        item.setChecked(!item.isChecked());

                        // Force an update of the specified element
                        itemListModel.setElementAt(item, index);
                    }                   
                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new JScrollPane(list));
                frame.add(change, BorderLayout.SOUTH);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class Item {

        public String itemName;
        public boolean checked;

        public Item(String itemName, boolean checked) {
            this.itemName = itemName;
            this.checked = checked;
        }

        public void setChecked(boolean checked) {
            this.checked = checked;
        }

        public boolean isChecked() {
            return checked;
        }

        public String getItemName() {
            return itemName;
        }

        @Override
        public String toString() {
            return itemName;
        }
    }

    public static class CheckBoxListCellRenderer extends JCheckBox implements ListCellRenderer<Item> {

        private static final Border DEFAULT_NO_FOCUS_BORDER = new EmptyBorder(1, 1, 1, 1);

        public CheckBoxListCellRenderer() {
            setOpaque(false);
            setBorder(DEFAULT_NO_FOCUS_BORDER);
        }

        @Override
        public Component getListCellRendererComponent(JList<? extends Item> list, Item value, int index, boolean isSelected, boolean cellHasFocus) {
            setSelected(value.isChecked());
            setText(value.getItemName());
            Color fg = list.getForeground();
            if (isSelected) {
                setBackground(list.getSelectionBackground());
                fg = list.getSelectionForeground();
            } else {
                setBackground(list.getBackground());
            }
            setForeground(fg);
            setOpaque(isSelected);
            Border border = null;
            if (cellHasFocus) {
                if (isSelected) {
                    border = UIManager.getBorder("List.focusSelectedCellHighlightBorder");
                }
                if (border == null) {
                    border = UIManager.getBorder("List.focusCellHighlightBorder");
                }
            } else {
                border = DEFAULT_NO_FOCUS_BORDER;
            }
            setBorder(border);
            return this;
        }

    }

}

此示例只是将 MouseListener 添加到 JList 并更改被单击项目的 selected 状态。

这有一些直接的缺点:

  • 如果用户只想 select 一个项目怎么办?现在必须双击该项目才能重置状态
  • 没有键盘交互。您可以使用键绑定 API 来添加支持,但它并不直观,现在您实现了使用基于 JTable 的解决方案免费获得的功能
  • 一般来说,它不直观,因为 JList 不是可编辑的,所以大多数用户不会有那个 expectation/mindset 并且它可能会导致用户对预期结果感到沮丧不符合你的程序现在正在做什么。

在这一点上,我想说您已经超出了 JList 的预期功能,使用 JTable

可以更好地管理这些功能