Stuff to Play With: the EnumComboBoxModel

One of the great things about having a project like SwingLabs is that it gives me a place to put classes I've built that others might find useful. Today I'm going to describe my recent addition to SwingLabs, the EnumComboBoxModel, a cute little class that lets you stuff enums directly into your comboboxes with no extra work.

I often use comboboxes (also known as dropdown lists) in my user interfaces and I've got a few problems with them:

  • Ninety percent of the time I use a combobox represent a choice between a static set of options. This means I don't really need a full model with realtime updating. I just need a way to select from a single list.
  • I usually need to interact with the list of options in code (ie: it's not just a mockup) and I'd like not to work directly with hardcoded strings because they aren't typesafe and won't work with IDE auto-completion.
  • My static list of items is really a double list. I have a set of items and a set of display names. The value I use in my code is different from what the user sees.

So how can I address all of these concerns? Using a hardcoded array or List will take care of the first problem but not the second and third. Using a clever HashMap would let me take care of 1 and 3, but it's still not typesafe. There must be a better way:

Enter The Enum

Java 1.5 introduced a lot of new features to the language. One of my favorites is the enum keyword. Now I can define a list with a set number of values in just one place in my program and know that it will be typesafe throughout. It's also extremely compact in both declaration and at runtime. If you haven't learned about enums yet I suggest you check them out.

The problem with enums is that the combobox doesn't know how to use them. Fortunately all Swing components support pluggable models, so I've created a new class called the EnumComboBoxModel which you can use like this:

enum MyEnum { Foo, Bar };
...
public void createGui() {
	JComboBox box = new JComboBox(new EnumComboBoxModel(MyEnum.class));
}

It's that easy. You pass your enum into the model which goes in the combobox. At runtime your combo will look like this:

TestScreenSnapz001.png

If you add new values to MyEnum in the future they will automatically show up in the combobox. This keeps the code simple and localized to where it really matters, the data model, not the GUI.

Now there's still two problems with the code above. First of all, when you want to get the current value of the combobox you will get back an Object, which would have to be cast and isn't typesafe. What you want back is an instance of your enum. The second problem is that the actual constant names in your enum may not be fit for display on screen. Fortunately we can fix both of these.

Generics to the Rescue

To get the current value of the combobox you must call getSelectedValue() on it's model. The EnumComboBoxModel overrides this method with a typesafe version, but you must use the generics form of the constructor to use it. Here's an example:

enum MyEnum { Foo, Bar };
public void createGui() {
	EnumComboBoxModel<MyEnum> model = new EnumComboBoxModel<MyEnum>(MyEnum.class);
	MyEnum selected = model.getSelectedValue();
}

Now you can call getSelectedValue() and save it to an enum instance without any casting. I'm not completely happy with this solution because it requires you specify the type of your enum model twice, once in the generics type token (the <MyEnum> part) and once in the constructor argument (the MyEnum.class part). I can't see a way around this, however, since generics information isn't available at runtime. If anyone has a cleaner way to do this I'd love to see it.

The last issue is the display value. Since the enum model just uses the toString() on the enum, you can override that to return the display value you want.

enum MyEnum { Foo, Bar;
	public String toString() {
  		if(this == Foo) {
   	   		return "Foo Type (Good)";
		}
  		if(this == Bar) {
      		return "Bar Type (Bad)";
  		}
  		return "error"; // should never get here
 	}
};

By overriding the toString() method you can make the combo display whatever you want, while keeping this information close to the data. The combobox itself never knows the difference.

The one thing I haven't been able to do is make the model.setSelectedItem() method be typesafe. I can implement a generics version of the method, but the original Object method is still there so it won't do any good. I'm still looking for a way to fix that.

You can find the EnumComboBoxModel in the org.jdesktop.swingx.combobox package of the SwingLabs.org CVS repository. Let me know if you like it.

- Josh

Talk to me about it on Twitter

Posted April 17th, 2006

Tagged: java.net