3

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)

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214

2 Answers2

7

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.

Reverend Gonzo
  • 39,701
  • 6
  • 59
  • 77
  • +1 - Don't modify the model to make the UI change. Instead, extend the renderer to add the additional pieces. – Nate W. Oct 12 '11 at 00:03
  • 1
    Unfortunately, this doesn’t quite work. The JComboBox doesn’t seem to use the `DefaultListCellRenderer` by default: using the above code, the display of the currently selected item differs from the normal rendering (at least on OS X). /EDIT: It’s `com.apple.laf.AquaComboBoxRender`. Still, this works with only tiny modifications. – Konrad Rudolph Oct 12 '11 at 08:48
  • 3
    Addition to my previos comment: instead of using `super.getListCellRendererComponent`, store the previously used renderer before setting the new one, and use its implementation. – Konrad Rudolph Oct 12 '11 at 09:00
  • @Konrad: Yeah, that's actually a better idea to support default renderers in different look and feels. – Reverend Gonzo Oct 12 '11 at 13:00
  • Thank you. Thank you. Thank you Konrad Rudolph. I extended a JComboBox to use a custom renderer and have spent hours trying to get the background of the combobox to look right. Without saving off the render ahead of time, I was just calling super.getListCellRendererComponent and ending up with a white background when using windows LaF. Even oracle's custom combo demo has a white back ground when you set the LaF to windows because it doesn't respect the UI LaF. – Justin Wiseman Sep 16 '12 at 18:56
-1

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

Community
  • 1
  • 1
mKorbel
  • 109,525
  • 20
  • 134
  • 319