JComboBox Cell Renderer Fails with Windows Look and Feel

817 views Asked by At

I am writing a Java application that uses the local system look and feel. In the program there is a ListCellRenderer that renders a colored dot (a custom JComponment) followed by some text given an object. This works fine when using Swing's default Metal look and feel.

However, when I use the Windows Look and feel, the cells are rendered correctly in the drop-down list, but the selected item (the one displayed when the user is not in the act of selecting a different option) renders only the text and ignores the colored dot. If I change the renderer to set the font, the proper font is observed in both the drop down and the selected item, so I know that the cell renderer is being used, at least in part.

I've read some posts around the web about the different LAFs causing problems like this but haven't come across anyone who's discussing my particular problem.

In case anyone is curious here is the code:

.

@Override
public Component getListCellRendererComponent(JList<? extends ColoredDisplayable> jlist, ColoredDisplayable e, int i, boolean isSelected, boolean hasFocus) {

    JPanel cell = new JPanel(new GridBagLayout());
    cell.setOpaque(false);

    JLabel label = new JLabel(e.getDisplayString());
    label.setOpaque(false);
    label.setBorder(BorderFactory.createEmptyBorder(1, 4, 1, 4));
    label.setHorizontalAlignment(JLabel.LEFT);

    Color deselectedBackground = cell.getBackground();
    Color deselectedTextColor = cell.getForeground();

    // LAYOUT COMPONENTS
    // Dot
    GridBagConstraints gbc = new GridBagConstraints();
    gbc.gridx = 0;
    gbc.gridy = 0;
    gbc.insets = INSETS;
    gbc.anchor = GridBagConstraints.LINE_START;
    gbc.weightx = 0.0f;
    gbc.fill = GridBagConstraints.NONE;
    cell.add(new Dot(e.getColor()), gbc);

    // Label
    gbc.gridx = 1;
    gbc.weightx = 1.0f;
    gbc.fill = GridBagConstraints.HORIZONTAL;
    cell.add(label, gbc);


    if (isSelected){
        cell.setOpaque(true);
        cell.setBackground(MetalLookAndFeel.getTextHighlightColor());
    } else {
        cell.setBackground(deselectedBackground);
        cell.setForeground(deselectedTextColor);
    }

    return cell;
}

Also, here's the code for the custom component inc ase anyone wants to try this out to see if I'm just doing something silly here:

public class Dot extends JComponent {

    /** The size of the dot. */
    private static final int SIZE = 10;

    /** The size of the dot. */
    private static final int PAD = 4;

    private static final Dimension DIM = new Dimension(SIZE + PAD, SIZE + PAD);

    /** The Color to render the dot. */
    private final Color m_color;

    /** The Dot itself. */
    private static final Ellipse2D.Double DOT = new Ellipse2D.Double(PAD / 2, PAD / 2, SIZE, SIZE);

    /**
     * Creates a dot of the specified color.
     * @param color the color to make the dot.
     */
    public Dot(Color color) {
        m_color = color;
    }

    @Override
    public void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D)g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(m_color);
        g2d.fill(DOT);
    }

    @Override
    public Dimension getPreferredSize() {
        return DIM;
    }
}

Edit: I just tested this on Ubuntu 12.04 and the cell renderer worked as expected there (although the JCombobox didn't render its outside border as it does if no custom renderer is applied).

Edit: As I look into this more and more, it seems as though there may be something to the setEditor method on JComboBox, however when not editable, the renderer should be used as the javadoc for the method states:

Sets the editor used to paint and edit the selected item in the JComboBox field. The editor is used only if the receiving JComboBox is editable. If not editable, the combo box uses the renderer to paint the selected item.

That just doesn't appear to be the behavior that I'm seeing. What must I do in order to have ALL parts of my cell renderer be observed for users of the Windows LAF?

1

There are 1 answers

1
clyde On BEST ANSWER

Digging into this, I found that for the Windows LAF, I need to set a ComboBoxEditor and set the JComboBox to be editable in order for the selected cell to render properly.

This appears to me to be a bug/unintended function specific to the Windows Look and Feel as the API for JComboBox's setEditor method states that when not editable, the renderer will be used - and it is when run in both default Metal LAF and on Ubuntu.

In addition to that, I wasn't able to just have the Editor return a new cell each time getEditorComponent was called as one does in a ListCellRenderer. Which I suppose makes sense.

This website provides an example (albeit sort of a lackluster one) of how to create an editor:

The API for the JComboBox and BasicComboBox were also helpful:

And finally, my Editor Code:

public class ColoredDisplayableComboBoxEditor extends BasicComboBoxEditor {

    private ColoredDisplayable m_cd = null;
    private static final Insets INSETS = new Insets(3, 1, 3, 1);
    private final JPanel m_cell;
    private final JLabel m_label;
    private final Dot m_dot;

    public ColoredDisplayableComboBoxEditor() {
        // INITIALIZE
        // Panel
        m_cell = new JPanel(new GridBagLayout());
        m_cell.setOpaque(false);

        // Label
        m_label = new JLabel();
        m_label.setOpaque(false);
        m_label.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 4));
        m_label.setHorizontalAlignment(JLabel.LEFT);

        // Dot
        m_dot = new Dot(Color.BLACK);

        // LAYOUT
        // Dot
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.insets = INSETS;
        gbc.anchor = GridBagConstraints.LINE_START;
        gbc.weightx = 0.0f;
        gbc.fill = GridBagConstraints.NONE;
        m_cell.add(m_dot, gbc);

        // Label
        gbc.gridx = 1;
        gbc.weightx = 1.0f;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        m_cell.add(m_label, gbc);
    }

    @Override
    public Component getEditorComponent() {
        return m_cell;
    }

    @Override
    public Object getItem() {
        System.out.println("getting item.");
        return m_cd;
    }

    @Override
    public void setItem(Object item) {
        System.out.println("setting item.");
        if (item instanceof ColoredDisplayable) {
            ColoredDisplayable cd = (ColoredDisplayable)item;
            if (!cd.equals(m_cd)) {
                System.out.println("--item actually set.");
                m_cd = cd;
                m_label.setText(m_cd.getDisplayString());
                m_dot.setColor(m_cd.getColor());
            }            
        } else {
            throw new IllegalArgumentException("Parameter item must be a ColoredDisplayable.");
        }
    }
}