Adding Auto-Completion Support to Swing ComboboxesProviding auto-completion support on application text components and comboboxes is quickly becoming a standard UI feature. You can find it in the Firefox search bar, the address field in Google Maps, and many other places. In this article we compare four alternative implementations for providing auto-completion support on Swing comboboxes: GlazedLists, SwingX, JIDE, and Laf-Widget.
The basic functionality is similar across the available implementations. An editable combobox uses either the model entries or an external list to dynamically select entries based on the text currently typed by the user. This is especially useful on comboboxes with large models where a specific entry (searched for by the user) is not located near the top of the list. By typing the few first letters that match that entry, the user is able to navigate to the "region of interest" in a much more efficient and quick fashion. Compare this to moving the mouse to the scroll bar of the pop-up and scrolling down the list; this tends to be inefficient on large lists.
At this time, there are a considerable number of mature and stable implementations that provide this functionality on different Swing text components. Here, we will compare four open-source implementations on comboboxes. The libraries are:
JComboBoxes.JComboBoxes as well as for generic
JTextComponents.JComboBoxes and
JTextComponents.JComboBoxes on the
look-and-feel level. It is based on the original implementation by
Thomas Bierhance as well, and is maintained by the author of this
article.In the following sections, we will look at the basic usage of
each of the implementations under two scenarios. The first
scenario is a simple one--the combobox in question is backed by a
String model. The second scenario is more complex (and
closer to real-life applications). In this scenario, the combobox is
backed by a model with custom objects. In such a case, the
auto-completion layer has to provide hooks for translating between
user input (characters) and the model elements (custom objects
that do not necessarily implement a useful toString()
method).
The simplest combobox in Swing is backed by a
String model. Here is how you can create such a
combobox:
Object[] elements = new Object[] { "Ester", "Jordi", "Jordina",
"Jorge", "Sergi" };
JComboBox comboBox = new JComboBox(elements);
In order to install the auto-completion support on such a
combobox with GlazedLists, you can use the following code (where
AutoCompleteSupport comes from the
ca.odell.glazedlists.swing package):
Object[] elements = new Object[] { "Ester", "Jordi", "Jordina",
"Jorge", "Sergi" };
this.comboBox = new JComboBox();
AutoCompleteSupport support = AutoCompleteSupport.install(
this.comboBox, GlazedLists.eventListOf(elements));
System.out.println("Is editable - " + this.comboBox.isEditable()
+ ". Surprise!");
Note that unlike other implementations, GlazedLists uses an
"external" list of available items. In our case, we create an empty
combobox and provide an external list (the second parameter to the
AutoCompleteSupport.install method) that will be used
as the combobox model by GlazedLists. Another interesting point to
note is that GlazedLists makes the combobox editable as a
side-effect of AutoCompleteSupport.install, displaying
the "convention over configuration" approach (you don't explicitly
need to make the combobox editable in your code). At runtime, the
last line will print
Is editable - true. Surprise!
A nice thing about this implementation is that the pop-up will only show entries that match the currently typed prefix (instead of showing the whole list and scrolling to the first matching item as in other implementations), as shown in Figure 1.

Figure 1. The pop-up only shows the matching items
By default, the auto-completion in GlazedLists allows entering elements that are not in the list (freetyping). To enable strict completion, use the following code:
AutoCompleteSupport support = AutoCompleteSupport.install(
this.comboBox, GlazedLists.eventListOf(elements));
support.setStrict(true);
In order to install auto-completion support on our combobox with
SwingX, you can use the following code (where
AutoCompleteDecorator comes from the
org.jdesktop.swingx.autocomplete package):
this.comboBox = new JComboBox(new Object[] { "Ester", "Jordi",
"Jordina", "Jorge", "Sergi" });
AutoCompleteDecorator.decorate(this.comboBox);
System.out.println("Is editable - " +
this.comboBox.isEditable() + ". Surprise!");
As with GlazedLists, the combobox has been made editable after
installing the auto-completion support. The current implementation
doesn't allow for the proper setting of strict selection mode. Instead, it
uses the original editable status: if the combobox was editable
before the call to decorate, the auto-completion will
be freetyping. If the combobox was not editable, the
auto-completion will be strict. This behavior is specified in the
Javadoc comments of the method.
In order to install the auto-completion support on our combobox
with JIDE, you can use the following code (where
AutoCompletion comes from the
com.jidesoft.swing package):
Object[] elements = new Object[] { "Ester", "Jordi", "Jordina",
"Jorge", "Sergi" };
this.comboBox = new JComboBox(elements);
this.comboBox.setEditable(true);
AutoCompletion ac = new AutoCompletion(this.comboBox);
ac.setStrict(false);
With JIDE, the combobox has to be editable in order to have
auto-completion installed on it with the
AutoCompletion class. In order to install
auto-completion on non-editable comboboxes, you can use the
Searchable feature. In addition, by default JIDE
provides strict selection mode. If you want to have the freetyping
functionality, call the setStrict method on the
AutoCompletion object that you have created.
An even simpler way to provide auto-completion on a combobox
with JIDE is to use AutoCompletionComboBox:
Object[] elements = new Object[] { "Ester", "Jordi", "Jordina",
"Jorge", "Sergi" };
this.comboBox = new AutoCompletionComboBox(elements);
this.comboBox.setStrict(false);
In order to install the auto-completion support on our combobox with Laf-Wigdet, you will need to run under a look and feel that has been "widgetized" to provide this functionality. You can read about the "widgetizing" process in the blog entry " Spring Effects and Widgets: Now in Windows Look and Feel," which also points to the Laf-Widget-Windows project. This project provides extensions to the Windows look and feel, and one of these extensions is auto-completion support for editable comboboxes.
First, you need to set the widgetized look and feel using the following code:
UIManager.setLookAndFeel(
new org.jvnet.lafwidget.windows.WindowsLookAndFeel());
Now, you create a combobox and set it to editable:
this.comboBox = new JComboBox(new Object[] { "Ester", "Jordi",
"Jordina", "Jorge", "Sergi" });
this.comboBox.setEditable(true);
The Laf-Widget approach takes the "convention over
configuration" a step further and automatically installs the
auto-completion support on all editable comboboxes. If you want to
uninstall this functionality, use the
LafWidget.COMBO_BOX_NO_AUTOCOMPLETION client property. By
default, the auto-completion is freetyping. If you want to install
the strict mode, call the following code:
this.comboBox = new JComboBox(new Object[] { "Ester", "Jordi",
"Jordina", "Jorge", "Sergi" });
this.comboBox.setEditable(true);
this.comboBox.putClientProperty(
LafWidget.COMBO_BOX_USE_MODEL_ONLY,
Boolean.TRUE);
Note that under the strict mode, Laf-Widget implementation will install a "lock" border that provides a visual indication or "read-only" mode of the selection (as shown in Figure 2):
Figure 2. Lock border on strict auto-completion under Laf-Widget
implementation
As can be seen, the libraries are very similar in the basic support for installing auto-completion on comboboxes. The main differences are in the "convention over configuration" assumptions. GlazedLists and SwingX make the combobox editable; Laf-Widget doesn't even require the user to explicitly install the auto-completion. GlazedLists provides a nice option to install an "external" choice list, which may be very useful in certain situations, while JIDE provides a custom component that has the auto-completion functionality out of the box.
While the previous section showed a simple model backed by
Strings, most real-life applications will have custom
objects backing up the model. In this section, we will show how the
libraries in question deal with auto-completion on such
comboboxes.
First, we'll start with a simple custom bean that contains information on a single model object:
public class UserInfo {
private String firstName;
private String lastName;
public UserInfo(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
In addition, we'll simulate the user database with a
UserRepository class (see the Resources section below). The implementation is
quite naive in that it assumes uniqueness of the first name, but it
will suffice for our purposes. In order to create a combobox with
all available users, we use the following code:
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new JComboBox(allUsers);
The next step is to provide a custom cell renderer for the
combobox pop-up. One option is to provide a custom
toString() implementation on the model class
(UserInfo), and another option is to provide a custom
implementation of a ListCellRenderer and install it on
the combobox. A sample implementation of such a renderer that uses
the first name of the specific user is:
public class UserInfoRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
JLabel result = (JLabel) super.getListCellRendererComponent(list,
value, index, isSelected, cellHasFocus);
UserInfo userInfo = (UserInfo) value;
result.setText(userInfo.getFirstName());
return result;
}
}
And to install this renderer on our combobox, use:
this.comboBox.setRenderer(new UserInfoRenderer());
The interesting part comes with installing the editor. While the
core Swing layer (ComboBoxEditor.getEditorComponent)
doesn't mandate it, in most cases you would have a text field as
the actual editor of the combobox. In our case (a model backed up by
custom objects), we will have to provide translation between the
model objects and the text field contents (string). In order to be
in sync with the renderer implementation (which shows the user's
first name), here is a possible implementation of such a
"translation" editor:
public class UserInfoEditor extends BasicComboBoxEditor {
public UserInfoEditor(ComboBoxEditor origEditor) {
super();
editor.setBorder(((JComponent) origEditor.getEditorComponent())
.getBorder());
}
@Override
public void setItem(Object anObject) {
if (anObject instanceof UserInfo) {
super.setItem(((UserInfo) anObject).getFirstName());
} else {
super.setItem(anObject);
}
}
@Override
public Object getItem() {
Object superRes = super.getItem();
if (superRes instanceof String) {
UserInfo result = UserRepository.getInstance().getUserInfo(
(String) superRes);
return result;
}
return superRes;
}
}
The implementation is quite straightforward. The
setItem converts a model object into a string
representation, and the getItem converts a string
(typed by the user into the editable combobox) into the matching
model object.
Note that up until now, none of code samples in this section have mentioned auto-completion. However, the basic steps of installing auto-completion support with all four libraries are the same--you need to provide some sort of a translation "implementation" that the specific library uses to select the best-matching item based on the currently typed prefix. While the specific interfaces that you need to implement are different, the implementation itself is very similar.
Here is what you need to do for GlazedLists. First, you provide
a custom java.text.Format implementation for
model-string conversions:
private static class UserInfoFormat extends Format {
@Override
public StringBuffer format(Object obj, StringBuffer toAppendTo,
FieldPosition pos) {
if (obj != null)
toAppendTo.append(((UserInfo) obj).getFirstName());
return toAppendTo;
}
@Override
public Object parseObject(String source, ParsePosition pos) {
return UserRepository.getInstance().getUserInfo(
source.substring(pos.getIndex()));
}
}
And now, you provide a custom TextFilterator
implementation to use the firstName property of your
bean:
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new JComboBox();
TextFilterator<UserInfo> textFilterator = GlazedLists.textFilterator(
UserInfo.class, "firstName");
AutoCompleteSupport support = AutoCompleteSupport.install(
this.comboBox, GlazedLists.eventListOf(allUsers),
textFilterator, new UserInfoFormat());
support.setStrict(true);
Note that for GlazedLists, there is no need to install the custom editor, the custom renderer, or the model. The implementation uses the formatter, the filterator, and the event list to wrap the original (default) editor, renderer, and model with its own implementations that convert between model objects and the string editor contents.
The code for SwingX follows the same lines. As with GlazedLists, you do not need to install a custom editor (but you do need the custom renderer and the model):
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new JComboBox(allUsers);
this.comboBox.setRenderer(new UserInfoRenderer());
AutoCompleteDecorator.decorate(comboBox,
new ObjectToStringConverter() {
@Override
public String getPreferredStringForItem(Object item) {
if (item == null)
return null;
return ((UserInfo) item).getFirstName();
}
});
Unlike with GlazedLists and SwingX, the implementations of JIDE and Laf-Widget require you to install a custom editor on your combobox. In some cases this might seem like coding overhead, but at least your code will not be "surprised" when the original editor is "yanked" and replaced by a custom one installed by the auto-completion code. Here is how you install the auto-completion support on a combobox with JIDE:
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new JComboBox(allUsers);
this.comboBox.setEditor(new UserInfoEditor(this.comboBox
.getEditor()));
this.comboBox.setRenderer(new UserInfoRenderer());
this.comboBox.setEditable(true);
AutoCompletion ac = new AutoCompletion(this.comboBox,
new ComboBoxSearchable(this.comboBox) {
@Override
protected String convertElementToString(Object object) {
return ((UserInfo) object).getFirstName();
}
});
As you can see, you have to install a custom editor and
explicitly set the combobox to be editable. Other than that, the
implementation is pretty much the same as with other libraries. You
can also use the AutoCompletionComboBox to make the
code a little bit simpler:
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new AutoCompletionComboBox(allUsers) {
@Override
protected AutoCompletion createAutoCompletion() {
return new AutoCompletion(this, new ComboBoxSearchable(this) {
@Override
protected String convertElementToString(Object object) {
return ((UserInfo) object).getFirstName();
}
});
}
};
this.comboBox.setEditor(new UserInfoEditor(this.comboBox.getEditor()));
this.comboBox.setRenderer(new UserInfoRenderer());
Here is how you install the auto-completion with Laf-Widget:
UserInfo[] allUsers = UserRepository.getInstance().getAllUsers();
this.comboBox = new JComboBox(allUsers);
this.comboBox.setEditor(new UserInfoEditor(comboBox
.getEditor()));
this.comboBox.setRenderer(new UserInfoRenderer());
this.comboBox.setEditable(true);
this.comboBox.putClientProperty(LafWidget.COMBO_BOX_USE_MODEL_ONLY,
Boolean.TRUE);
this.comboBox.putClientProperty(
LafWidget.COMBO_BOX_AUTOCOMPLETION_MATCHER,
new AutoCompletionMatcher() {
public Object getFirstMatching(ComboBoxModel model,
String prefix) {
for (int i = 0; i < model.getSize(); i++) {
UserInfo userInfo = (UserInfo) model
.getElementAt(i);
if (userInfo.getFirstName().startsWith(prefix))
return userInfo;
}
return null;
}
});
Note that you need to explicitly set a custom editor and mark the combobox as editable, as well.
As you have seen from the code examples, the surveyed solutions for installing the auto-completion support on Swing comboboxes provide very similar capabilities with comparable implementation efforts (some of this is due to the fact that three out of four have branched from the same base implementation, and the fourth has drawn a few ideas from it). Some use a "convention over configuration" approach, while others require explicit settings. If you are already using one of these libraries in your code, it would be a wise choice to use auto-completion from the same library. If you're starting a new project, look at the other APIs provided by the relevant libraries and compare the support for different operating systems, the look and feels, and the licensing requirements.
You can compare the four libraries in action by running the
SimpleAll class from the bundled sample code (see the
Resources section). In this example, you
will see the subtle differences in the highlighting and pop-up
handling.
Thanks to James Lemieux of GlazedLists and David Qiao of JIDE for reviewing this article.
Kirill Grouchnikov has been writing software for the last 15 years, the last seven doing it for a living.
|
|