Konrad Rudolph
Konrad Rudolph

Reputation: 545518

Modify ComboBox display in Swing

I want to modify the display of a (non-editable) JComboBox in such a fashion that the currently selected entry has some extra text in the edit field (not the dropdown list, though).

Something like this:

Mockup

My first guess was to override the ComboBox’ model so that getSelectedItem returns a wrapper object modifying the display:

petList.setModel(new ComboBoxModel() {
    private Object selected;

    public void setSelectedItem(Object anItem) {
        selected = anItem;
    }

    public Object getSelectedItem() {
        return new ActiveComboItem(selected);
    }

    // … The rest of the methods are straightforward.
});

Where ActiveComboItem looks as follows:

static class ActiveComboItem {
    private final Object item;

    public ActiveComboItem(Object item) { this.item = item; }

    @Override
    public boolean equals(Object other) {
        return item == null ? other == null : item.equals(other);
    }

    @Override
    public String toString() { return String.format("Animal: %s", item); }
}

Indeed, this works as far as modifying the display goes. Unfortunately, the current entry is no longer marked as active:

Wrong display

(Note the missing check mark … or however the selection is displayed by your OS.)

Further inspection shows that the model’s getElementAt method is called with an index of -1 every time the user selects a new item in the box. This is only the case when using the modified selected item. When the model’s getSelectedItem method returns the plain object without wrapper, then the selected item is marked as selected in the dropdown box, and getElementAt is not called with an argument of -1.

Apparently, the ComboBox is comparing each item in turn to the currently active item but, despite my overriding the equals method, it finds no match. How can I fix this?

(Full, compilable code for this problem at gist.github.com)

Upvotes: 4

Views: 5739

Answers (2)

mKorbel
mKorbel

Reputation: 109815

create JTextField and JButton with Icon, for JButton implements ButtonModel, by overriding its methods isRollover(), isPressed(), isArmed()

don't extract Icon from JComboBox, paint own Triangle, then JButton with Icon will be Look and Feel and Native OS resist, for nicer output implements JButton#setRolloverIcon() too

create a JPopup or JWindow, put here JScrollPane, display this Container from model.isPressed() or isArmed

now you have two choises

1) create JList that contains JCheckBox (remove rectangle from JCheckBox)

import java.awt.Component;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;

public class CheckList {

    public static void main(String args[]) {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Create a list containing CheckListItem's
        JList list = new JList(new CheckListItem[]{
                    new CheckListItem("apple"),
                    new CheckListItem("orange"),
                    new CheckListItem("mango"),
                    new CheckListItem("paw paw"),
                    new CheckListItem("banana")});
        // Use a CheckListRenderer (see below) to renderer list cells
        list.setCellRenderer(new CheckListRenderer());
        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        list.addMouseListener(new MouseAdapter() {// Add a mouse listener to handle changing selection

            @Override
            public void mouseClicked(MouseEvent event) {
                JList list = (JList) event.getSource();
                int index = list.locationToIndex(event.getPoint());// Get index of item clicked
                CheckListItem item = (CheckListItem) list.getModel().getElementAt(index);
                item.setSelected(!item.isSelected()); // Toggle selected state
                list.repaint(list.getCellBounds(index, index));// Repaint cell
            }
        });
        frame.getContentPane().add(new JScrollPane(list));
        frame.pack();
        frame.setVisible(true);
    }
}

// Represents items in the list that can be selected
class CheckListItem {

    private String label;
    private boolean isSelected = false;

    public CheckListItem(String label) {
        this.label = label;
    }

    public boolean isSelected() {
        return isSelected;
    }

    public void setSelected(boolean isSelected) {
        this.isSelected = isSelected;
    }

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

// Handles rendering cells in the list using a check box
class CheckListRenderer extends JCheckBox implements ListCellRenderer {

    private static final long serialVersionUID = 1L;

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean hasFocus) {
        setEnabled(list.isEnabled());
        setSelected(((CheckListItem) value).isSelected());
        setFont(list.getFont());
        setBackground(list.getBackground());
        setForeground(list.getForeground());
        setText(value.toString());
        return this;
    }
}

.

2) or implements ListSelectionModel and add/remove Icon on MouseClick

hide JPopup or JWindow from MouseListener Event

Upvotes: -1

Reverend Gonzo
Reverend Gonzo

Reputation: 40811

You need to provide a custom ListCellRenderer. The following works:

    final JComboBox animalCombo = new JComboBox(animals);
    animalCombo.setRenderer(new DefaultListCellRenderer() {
        @Override
        public Component getListCellRendererComponent(final JList list, Object value, final int index, final boolean isSelected,
                final boolean cellHasFocus) {

            if (index == -1) {
                value = "Animal: " + value;
            }

            return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
        }
    });

index is -1 when the value its painting is the one that's not in the dropdown.

For future reference, when you just want to change how something is displayed in Swing, you never want to modify the backing model. Every component has a renderer, and generally you just need to slightly modify the default one.

Upvotes: 8

Related Questions