Search code examples
javaswingjcomboboxrenderer

How to create multiselect combo


I want to create a multiselect combo box in Swing that displays the items selected by user delimited by semicolon or another character.

For example:

Select articles(s) <- Displays the user's selection
Select articles(s)
No article
a
the

It the user has selected "a" and "the", "a; the" will be displayed instead of "Select articles(s)".

I have tried to program such a combo, but my problem is that "Select articles(s)" isn't replaced with the current user selection.

You can only see something like:
Select articles(s) <- Displays the user's selection (isn't replaced by "a; the")
a; the
No article
a
the

Here is my code:

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.plaf.basic.BasicComboBoxRenderer;

public class MultiSelectionComboBox {

    private DefaultComboBoxModel model;

    private JPanel getContent() {
        Object[] items = { "Select article(s)", "No article", "a", "the" };

        model = new DefaultComboBoxModel(items);
        JComboBox combo = new JComboBox(model);

        SelectionManager manager = new SelectionManager();
        manager.setNonSelectable(items[0]);

        Renderer renderer = new Renderer(manager);
        combo.addActionListener(manager);
        combo.setRenderer(renderer);

        JPanel panel = new JPanel();
        panel.add(combo);
        return panel;
    }

    class SelectionManager implements ActionListener {
        JComboBox combo = null;
        private List<Object> selectedItems = new ArrayList<Object>();
        private Object nonSelectable;

        public void setNonSelectable(Object val) {
            nonSelectable = val;
        }
        public void actionPerformed(ActionEvent e) {
            if (combo == null) {
                combo = (JComboBox) e.getSource();
            }
            Object item = combo.getSelectedItem();
            // Toggle the selection state for item.  
            if (selectedItems.contains(item)) {
                selectedItems.remove(item);
            } else if (!item.equals(nonSelectable)) {
                selectedItems.add(item);
            }

            combo.setSelectedIndex(0);
        }

        public List<Object> getSelectedItems() {
            return selectedItems;
        }
    }

    class Renderer extends BasicComboBoxRenderer {
        SelectionManager selectionManager;

        public Renderer(SelectionManager sm) {
            selectionManager = sm;
        }

        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
                boolean cellHasFocus) {
            setFont(list.getFont());

            if (index == 0) { // first item shows currently selected items delimited by ;
                StringBuffer firstItem = new StringBuffer();
                for (Object sel : selectionManager.getSelectedItems()) {
                    firstItem.append(sel + "; ");
                }
                if (firstItem.toString().endsWith("; ")) {
                    firstItem.deleteCharAt(firstItem.length() - 2);
                }
                setText((value == null) ? "" : firstItem.toString());
            } else {// other items
                setText((value == null) ? "" : value.toString());
            }

            return this;
        }
    }

    public static void main(String[] args) {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(new MultiSelectionComboBox().getContent());
        f.setSize(300, 145);
        f.setLocation(200, 200);
        f.setVisible(true);
    }
}

I know that combo isn't destined for multiple selection, but in my case I don't see the better UI element, because I want to place such combos in sentences. For example: "Where is |a; the| key?"


Solution

  • In your cell renderer, you are assuming a index of 0 is the selected value, it's not. It's actually -1 (or more precisely, that's the index used to represent the value of the editor)

    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
        setFont(list.getFont());
    
        if (index == -1 && selectionManager.getSelectedItems().size() > 0) {
            StringBuffer firstItem = new StringBuffer();
            for (Object sel : selectionManager.getSelectedItems()) {
                firstItem.append(sel + "; ");
            }
            if (firstItem.toString().endsWith("; ")) {
                firstItem.deleteCharAt(firstItem.length() - 2);
            }
            setText((value == null) ? "" : firstItem.toString());
        } else {// other items
            setText((value == null) ? "" : value.toString());
        }
    
        return this;
    }