[Java] Getting Combo Box Renderers to Look Consistent with Other Combo Boxes

One way of controlling the text in a JComboBox is to provide a custom ListCellRenderer. I have seen implementations that subclass a concrete implementation (such as DefaultListCellRenderer or the BasicComboBoxRenderer). Generally though, I find that the combo boxes using such renderers can appear inconsistent with another combo box that may not have a renderer set or one that extends a different implementation. To illustrate the inconsistency that could happen, I prepared a small test program that you can download from my Github repository. It displays a number of combo boxes that are based on different renderers. Just running it produces the following dialog in Windows 7:

For reference, the following table describes the types of renderers that are displayed:

Combo Box Renderer
Unchanged No custom renderer. This is how the combo box would normally appear if renderer is set.
Basic A custom renderer that is based on BasicComboBoxRenderer.
Substance A custom renderer that is based on Substance’s default list cell renderer (SubstanceDefaultListCellRenderer).
Static Default Based on DefaultListCellRenderer.
Refreshing Default A renderer that is also based on DefaultListCellRenderer, but the renderer is reset each time the look and feel is changed.
Static Delegate A custom renderer that will delegates to the implementation that would normally be used in a combo box.
Refreshing Delegate A custom renderer that also delegates to an implementation that would normally be used in a combo box. However, it will also update the renderer that it delegates to when the look and feel is changed.

The Unchanged combo box is the reference and would be how the combo boxes would normally appear, if you did not change the renderer. It can be seen that the Basic combo box appears to with a different background, whereas the Substance appears to indent its contents. Next, the results of changing to one of the Substance look and feels:



Now the Substance and Refreshing Delegate renderers appear consistent with the unchanged and the Static Delegate does not appear to have the same indent (more on the delegate renderers later). From these, it should be obvious that changing the renderers will effect how the combo boxes will appear on the screen. If you had a custom render that extended one of base implementations (e.g. , BasicComboBoxRenderer or SubstanceDefaultListCellRenderer, you might have to change it to extend a different implementation when you change the application’s look and feel and makes it harder to reuse the renderer across different applications that use different look and feels.

Avoiding a Custom Renderer

Normally, a JComboBox will get its renderer through an UI object that implements ComboBoxUI(see JComboBox.setUI()). In most cases, this is also a subclass of BasicComboBoxUI, which provides the renderer through createRenderer(). By overriding this method, different look and feels can provide their own renderers. But, according to the Javadocs for createRenderer(), it is used only “if a renderer has not been explicitly set with setRenderer“. So, one way of achieving a consistent look is to never to change the renderer. In this case, you would have to override the object’s toString() method to control the text in the combo box. If you want to put objects from an external library into a combo box, you could “wrap” them into one that you define (of course, you could also consider extending the class of the object and override toString(), but this will only work if the class of the object is not final). For example, the look and feel combo box in the above examples uses this method. The following listing is an extract that shows how it is implemented:

private static JComboBox<?> lnfComboBox() {
    final JComboBox<LnfLoader> skinSelector = new JComboBox<LnfLoader>();

    // ...
    for (LookAndFeelInfo lookAndFeel : UIManager.getInstalledLookAndFeels()) {
        LnfLoader item = standard(lookAndFeel);
        skinSelector.addItem(item);
        // ...
    }

    // ...
    
    for (SkinInfo skin : skins.values()) {
        LnfLoader item = substance(skin);
        skinSelector.addItem(item);
        // ...
    }

    // ...
    return skinSelector
}

// ...
private static LnfLoader standard(final LookAndFeelInfo lookAndFeel) {
    return new LnfLoader() {

        // ... 
        public String toString() {
            return lookAndFeel.getName();
        }
    };
}

// ...
private static LnfLoader substance(final SkinInfo skin) {
    return new LnfLoader() {

        // ... 
        public String toString() {
            return skin.getDisplayName();
        }
    };
}

// ...

Of course this only works if you want to control only the text in the combo box. If you want to change other aspects (such as the background or the icon), you will still need to provide your own renderer and some people may prefer to change the renderer instead. Next, we will have a look at the Static Delegate and the Refreshing Delegate renderers and how they help to produce a consistent looking combo box.

The Static Delegate Renderer

This renderer works by creating a custom renderer that translates the object into text and uses an original renderer to render the contents. In using this approach, it will appear as if the original renderer was being used. This is how the renderer is created and implemented:

public void installRenderer(JComboBox<Fruit> comboBox) {
    final ListCellRenderer<? super Object> original = new JComboBox<Object>()
            .getRenderer();
    comboBox.setRenderer(new ListCellRenderer<Fruit>() {

        public Component getListCellRendererComponent(
                JList<? extends Fruit> list, Fruit value,
                int index, boolean isSelected, boolean cellHasFocus) {

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

This approach works in cases where the renderer is created and set after the look and feel is set and does not change. In one of the above screenshots the renderer produces an inconsistent look, but it was produced by starting the application with a Windows look and feel and then was changed to Substance. This happens because the combo box’s UI class provides Substance’s renderer, which chooses to render its contents differently to the standard ones. However, if the demo was modified to start with Substance instead, then Static Delegate will delegate to a Substance renderer:

The Refreshing Delegate Renderer

The Refreshing Delegate solves the problem of the changing look and feel by updating the delegate renderer when ever the look and feel is changed:

public class DelegatingRenderer implements ListCellRenderer<Fruit> {

	// ...
	public static void install(JComboBox<Fruit> comboBox) {
		DelegatingRenderer renderer = new DelegatingRenderer(comboBox);
		renderer.initialise();
		comboBox.setRenderer(renderer);
	}

    // ...
	private final JComboBox<Fruit> comboBox;

	// ...
	private ListCellRenderer<? super Object> delegate;

	// ...
	private DelegatingRenderer(JComboBox<Fruit> comboBox) {
		this.comboBox = comboBox;
	}

	// ...
	private void initialise() {
		delegate = new JComboBox<Object>().getRenderer();
		comboBox.addPropertyChangeListener("UI", new PropertyChangeListener() {

			public void propertyChange(PropertyChangeEvent evt) {
				delegate = new JComboBox<Object>().getRenderer();
			}
		});
	}

	// ...
	public Component getListCellRendererComponent(JList<? extends Fruit> list,
			Fruit value, int index, boolean isSelected, boolean cellHasFocus) {

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

The PropertyChangeListener listens for changes to the UI property. This is property that is fired when the new combo box’s UI is set (which is what happens when changing the look and feel). When triggered, it simply updates the delegate. In doing so, it will ensure that combo box will appear as if the renderer had not been changed and can also be reused, without modification, in another application that may have a different look and feel.

4 Responses to [Java] Getting Combo Box Renderers to Look Consistent with Other Combo Boxes

  1. martin says:

    Wow… this is really nice 🙂 I have a custom ComboBox using a custom renderer. Until now i had to maintain 2 versions of the listcellrenderer: one extended DefaultListCellRenderer (for metal, windows, motif… l’n’f), the other extended SubstanceDefaultListCellRenderer, plus a RendererFactory to provide the correct renderer for the combobox.
    Using the delegate is so much more elegant, not only because i can throw away the double-implementation for the renderer, but more so because i can get rid of the RendererFactory, making the ComboBox portable to any future project i might come up with.
    And i had similar problems with some tablecellrenderers where the delegate-solution might come in handy, too 😉
    Think i need to rework quite a bit of code…

  2. Chris L. says:

    Great suggestion and works well for me, thanks. It’s worth mentioning that the code examples are written for Java 7, which supports a generic (i.e., parameterizable) version of JComboBox. This means you need Eclipse Juno or later.

  3. Carlos says:

    There is another way to do this that doesn’t involve creating a custom renderer and will work well with both Substance and Swing renderers. If you look at the code of their default renderer implementations near the end of the of the getListCellRendererComponent() method there is a check to see if the value is an Icon (if (value instanceof Icon)). Icon is an interface that allows you to paint the content of the line on the combobox. This method allows for some fancy display of combobox content using graphics, fonts, colors, etc. Just remember not to fill the background or else you will paint over what Substance has done already. I’ve used this to make combobox for Font with sample text, Enums where it instead shows a drawing, Stroke where the line shows a sample of the stroke, etc. Mind you that you will have an issue with the combobox returning the wrapper class that implements Icon but it felt to me like a small price to pay and easy to work around by providing methods like setSelectedFont() and getSelectedFont() on the wrapper class.

    • Carlos says:

      Sorry, the setSelectedFont() and getSelectedFont() would be part of a class extended from JComboBox not on the wrapper that carries the value and implements Icon. If you choose to make one.

Leave a comment