Skip to main content

Adding Auto-Completion Support to Swing Comboboxes

July 19, 2007

{cs.r.title}



Providing auto-completion support on application text components
and comboboxes is quickly becoming a standard UI feature. You can
find it in the "http://www.mozilla.com/en-US/firefox/central/">Firefox search
bar
, the address field in "http://maps.google.com/">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.

Basic Overview

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:

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).

Simple Case: A Model Backed by Strings

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);

Simple Scenario with GlazedLists

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.

<br "The pop-up only shows the matching items" />
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);

Simple Scenario with SwingX

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.

Simple Scenario with JIDE

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);

Simple Scenario with Laf-Widget

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 " "http://weblogs.java.net/blog/kirillcool/archive/2007/03/spring_effects.html">
Spring Effects and Widgets: Now in Windows Look and Feel
," which also points to the "https://laf-widget-windows.dev.java.net/">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 "https://laf-widget.dev.java.net/docs/clientprops/ComboBoxNoAutoCompletion.html">
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):

<br "Lock border on strict auto-completion under Laf-Widget implementation" />

Figure 2. Lock border on strict auto-completion under Laf-Widget
implementation

Simple Scenario: Summary

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.

Complex Case: A Model Backed by Custom Objects

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">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.

Complex Scenario with GlazedLists

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&lt;UserInfo&gt; 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.

Complex Scenario with SwingX

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();
        }
    });

Complex Scenario with JIDE

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());

Complex Scenario with Laf-Widget

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 &lt; 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.

Conclusion

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.

Resources

Kirill Grouchnikov has been
writing software for the last 15 years, the last seven doing it for
a living.

width="1" height="1" border="0" alt=" " />
Kirill Grouchnikov has been writing software since he was in junior high school, and after finishing his BSc in computer science, he happily continues doing it for a living. His main fields of interest are desktop applications, imaging algorithms, and advanced UI technologies.
Related Topics >> Swing   |   

Comments

I really like your article

I really like your article about adding autocompletion support to swing comboboxes. The example based on the GlazedLists project is pretty cool, although it uses the same popup menu as the combo box. Every time you enter a character, the popup menu disappears and appears again. For this reason I developed a new combo box with a second popupmenu. You’ll find an executable jar on my site (source code within jar): http://techtalk.fabio.li/wordpress/wp-content/uploads/2009/06/dynamiccom...