Justin Hill

Subscribe to Justin Hill: eMailAlertsEmail Alerts
Get Justin Hill: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn


Related Topics: Java Developer Magazine

Java Developer : Article

Favorite ComboBox

Favorite ComboBox

Swing is a library of graphical components used by Java front-end developers to create robust and functional graphical-user interfaces. Although there are many exciting topics in Swing, one of its most salient features is its ability to be customized.

One of the more powerful and customizable widgets in the Swing library is JComboBox. JComboBox, a combination of a text field and a list, allows the user to select an item from a drop-down list that appears at the user's request. FavoritesComboBox, our extension to JComboBox, is a practical and functional component similar to the Fonts drop-down list in Microsoft Word (see Figure 1).

The concept of FavoritesComboBox is that it provides a listing of the most recently selected items at the top of the list, italicizing and separating them with a horizontal line. A combo box containing a listing of all the countries of the world is an example of a domain in which Fav- oritesComboBox would be applicable. More often than not, if you live in the United States, "United States" is the selected choice, so why make the user traverse the entire list to find it? FavoritesComboBox solves this problem by placing "United States" at the top of the list.

This article closely examines FavoritesComboBox and its associated classes. In addition to discussing how FavoritesComboBox works internally, the article introduces design patterns, their importance, and how we used them in the design and development of FavoritesComboBox.

FavoritesComboBox
Classes in the Swing package that start with the letter "J" serve an important purpose - they act as the glue between the model and the UI-delegate. In addition, JWhatevers perform "special" operations such as setting up renderers and initializing listeners. FavoritesComboBox (see Listing 1), a subclass of JComboBox, is no exception, and along with performing the default setup specified in the super class, it accomplishes the following:

  • Adds a focus listener
  • Creates and sets a custom combo box model
  • Creates and sets a custom renderer

    Before providing a detailed description of the above-mentioned items, we need to clarify some fundamental assumptions regarding user interaction with FavoritesComboBox.

    One of the more interesting problems we came across when designing and developing FavoritesComboBox was trying to determine what constituted a selection in the widget. For example, suppose a user chooses an item from the combo box, but the focus remains in the widget. Does this constitute a selection? Furthermore, when a user is keying the up/down arrows to navigate the combo box's list, does this mean the user is making valid selections? We think not. To solve this problem, FavoritesComboBox assumes a selection has not been made until the component's focus is lost. While this basic assumption may seem vague at this point, the following sections will make things clearer.

    NestedComboBoxModel
    A close look at the pop-up list of possible selections in FavoritesComboBox shows two separate user-selectable lists. We've separated them into distinct ComboBoxModels - one with recent selections and one with all the valid choices. A FavoritesComboBoxModel combines these two models into one for presentation in FavoritesComboBox. The underlying behavior that supports this combination of models into one is provided by the base class - NestedComboBoxModel.

    We developed NestedComboBoxModel as a generic component that takes the contents of any number of individual ComboBoxModel instances and presents them as a single concatenated list of items. Support for the basic features of a ComboBoxModel is inherited from its base class, AbstractComboBoxModel. This provides the initial required interfaces and support for registering event listeners and firing events to them. We had to implement the remaining methods of the ComboBoxModel interface in order to present the combination of several submodels as a single list of elements.

    To report the current selection the model must keep track of the currently selected submodel. If one is defined, getSelectedItem() delegates the request to the selected submodel. For applying new selections the model must search through all elements to find the submodel that contains the new selection. Again, the operation is delegated to the submodel, and the new submodel is recorded as the currently selected one - after the previously selected submodel's selected item is cleared. This ensures that if a submodel is presented in multiple visual components, the selections are consistent.

    NestedComboBoxModel, as we've shown, is a robust ComboBoxModel capable of grouping distinct ComboBoxModels into one entity and is an important part of the success of FavoritesComboBox.

    FavoritesComboBoxModel and FavoritesRecentComboBoxModel
    FavoritesComboBoxModel extends NestedComboBoxModel, thus it combines recent selections with possible selections from two separate ComboBoxModels. One important customer requirement of our component was the ability to share recent lists across similar combo boxes. As a result we introduced a facility for models containing related items to share the list of recent selections. This is possible by assigning a type (of class String) to each instance. Combo boxes that share the same type will adjust their set of recent selections when any other combo box of that type changes selection. Due to the interesting communication interaction between recent lists, we've chosen to delegate this functionality into a separate model - FavoritesRecentComboBoxModel.

    To construct a FavoritesComboBoxModel, a programmer must pass an existing ComboBoxModel into the constructor. Internally, the passed-in model will be used to define the "whole" list. In addition, FavoritesComboBoxModel will create another model, FavoritesRecentComboBoxModel, to be used as the recent list. Finally, both models will be added as submodels to FavoritesComboBoxModel.

    The "shared" recent list behavior is possible because all the FavoritesRecentComboBoxModels are notified when a change occurs in a similar combo box. This event notification is implemented in the FavoritesComboBox component through the use of the Observer design pattern (described in detail in the Design Pattern section).

    Along with handling shared recent lists, FavoritesRecentComboBoxModel has several other obligations. One of the peculiarities of JComboBox is that it has a getSelectedIndex() method. However, the ComboBoxModel can't report the current selection by index, so JComboBox scans the model to determine the selected position when showing the pop-up. JComboBox's implementation means each value must be unique as determined by equals(). However, this paradigm won't work for our component since the same item can exist in both submodels. To overcome this restriction, all items in Favorites- RecentComboBoxModel are encapsulated in a wrapper. The two important methods in our Wrapper class (see Listing 2) are toString(), which delegates the call to the item wrapped, and extendedEquals(), which provides a less restrictive comparison that's needed during focus-gained and recent-model update.

    While Wrapper solves our selection index and equality problem, we use another approach to obtain the combo box's selected item. We now have a FavoritesComboBoxModel that can have selected items that are either Wrappers or the "actual" objects. However, we want FavoritesComboBox getSelectedItem() to report the "actual" object. In the case where the selected item is in the recent list, we want to return its corresponding "actual" object. To achieve this behavior, getSelectedItem() delegates the responsibility to the FavoritesRecentComboBoxModel's getCurrentSelection(). The getCurrentSelection() method determines the selected item [via the model's getSelectedItem()] and, in the case of a Wrapper, returns the encapsulated "actual" object; otherwise, it returns the selected item - the "actual" object.

    FavoritesListCellRenderer
    To display the most recently selected items at the top of the list, italicized and separated by a horizontal line, we needed to create a custom ListCellRenderer. Our class, FavoritesListCellRenderer, extends DefaultListCellRenderer and is capable of determining which items exist in the recent list as well as where the horizontal divider should be placed. With most ListCellRenderers the rendering logic is defined in the getListCellRendererComponent() method; our renderer is no exception. The following code fragment contains the logic for altering the item's font and displaying the underline:

    if(anIndex < recentModel.getSize())
    label.setFont(this.recentListFont);

    if (anIndex >= 0 && anIndex == recentModel.getSize()-1)
    label.setBorder(this.underlineBorder);

    As you can see by the code fragment, the font change and underline placement are contingent on the size of the recent model.

    FavoritesComboBoxFocusAdapter
    FavoritesComboBoxFocusAdapter, a subclass of FocusAdapter, is added as a focus listener to receive focus events from the combo box component. Focus events are fired internally when a component either gains or loses focus. Selections in FavoritesComboBox are not made until the component's focus is lost.

    As we alluded to earlier, JComboBox and its associated default model implementation internally set the current selection whenever items in the combo box change. One example of this behavior is that as a user navigates the drop-down list via the up/down keys, the model changes the current selections. Although this selection behavior is acceptable for JComboBox, it's not adequate for FavoritesComboBox to use the same selection paradigm.

    To handle the selection properly, we determined that an item in the combo box is selectable only when the component loses focus. If we had chosen to follow JComboBox's selection paradigm, then in the case of user navigation via the keyboard, we would be populating the recent list whenever the user pressed the up/down keys - which makes no sense when you think about it. In our selection paradigm the recent list is updated only when the combo box component loses focus. The following sections describe in detail how FavoritesComboBox handles the two types of focus events - focus gained as well as focus lost.

    public void focusLost(FocusEvent e)
    The focusLost() method is defined in FocusListener and is invoked when a component loses the keyboard focus; in our case, when the combo box component loses focus. Although the code contained in focusLost() is minute, it serves a powerful purpose - updating the recent list. The following is focusLost()'s implementation:

    if (!(aFocusEvent.isTemporary())) {
    JComboBox source =
    (JComboBox)aFocusEvent.getSource();
    Object selectedItem = source.getSelectedItem();
    if (selectedItem != null)

    favoritesModel.getRecentModel().updateRecentModel
    (selectedItem);
    }

    The first line of code simply ensures that the focus has not been lost temporarily. The isTemporary() method allows an application programmer to differentiate between a focus shift between components (returns false) and focus events, which are triggered via window deactivated, window activated, and more (returns true). Once we've determined the focus lost is not temporary, we can invoke FavoritesComboBoxModel's updateRecentModel() method, passing in the selected item from the combo box as a parameter. The updateRecentModel() method simply updates its recent list along with firing an event that notifies all registered listeners. The event-firing mechanism (see Design Pattern section) enables the recent list to be shared for all combo boxes of the same type.

    public void focusGained(FocusEvent e)
    The focusGained() method is defined in FocusListener and is invoked when a component gains focus; once again, in our case, when the combo box component gains focus. Initially we decided to use focus events for handling combo box selection and focusLost() exclusively; as a result, focusGained() had an empty implementation. However, during initial development we discovered a substantial problem and consequently found a useful purpose for focusGained().

    The problem we encountered can best be explained through example. Suppose a combo box exists that contains a listing of all the universities in North America in alphabetical order. This combo box would be large and, for argument's sake, let's assume that St. Peter's College is the current selection. When the combo box gains focus and the drop-down list is displayed, the list displays the current selected item (St. Peter's College). Unfortunately, in this example our recent list, residing at the top of the drop-down list, won't be displayed since the visible portion of the list is centered on St. Peter's near the bottom. To solve this problem, we took advantage of focusGained() by simply obtaining the combo box's selected item, finding its match in the recent list, and setting the selected item to the recent list's corresponding item, guaranteeing that the recent list will always be visible when the drop-down list is displayed. As with focusLost(), the focusGained() implementation is fairly straightforward and is as follows:

    JComboBox source = (JComboBox)aFocusEvent.getSource();
    if (source != null)
    return;
    this.favoritesModel.ensureRecentSelectedOnFocusGained(source.getSelectedItem());

    The ensureRecentSelectedOnFocusGained() method uses our extended equality check to search the recent list for the match and, if found, selects it, ensuring recent list visibility.

    Design Patterns
    Design patterns are reusable solutions to common problems that arise during software design.

    With the publication of Design Patterns in 1995, several of these solutions were cataloged and named. One of the many benefits of design patterns is that they enable software developers to abstractly discuss designing and developing software in a common vernacular. During the design of the FavoritesComboBox component, we relied on several design patterns to guide the structure of code and ensure flexibility. One of the more prominent patterns we used, the Observer, played a critical role in maintaining the recent list in FavoritesRecentComboBoxModel.

    In Patterns in Java, Grand says the Observer design pattern "allows objects to dynamically register dependencies between objects, so that an object will notify those objects that are dependent on it when its state changes."

    The roles of the Observer are:

  • ObserverIF: This interface defines a method, which is invoked upon notification. In our component, FavoritesModelChangeListener plays this role.
  • Observer: This class is a concrete implementation of ObserverIF. Our concrete implementation of FavoritesModelChangeListener is FavoritesRecentComboBoxModel.
  • Observable: This class is responsible for maintaining a list of registered Observers and delivering notifications. FavoritesRecentComboBoxModel performs this role.

    Our Observer implementation doesn't include as many classes as the "classic" Observer pattern. The reason we have fewer classes is because, for simplicity, we've chosen to ignore the ObservableIF role and have FavoritesRecentComboBoxModel play the role of both Observer and Observable. To see how these classes interact along with the running of our test application, the FavoritesComboBox component is available on the JDJ Web site.

    When a selection is made in a FavoritesComboBox, it updates its recent model. In addition, it notifies all registered FavoritesModelChangeListeners of the event. When each listener - in this case other FavoritesRecentComboBoxModels - receives notification, the selected item is inserted into its recent list, providing us with the desired functionality of "shared" recent lists. In addition to employing the Observer design pattern, our component uses the Factory Method (creating the renderer) and the Adapter (FavoritesComboBoxFocusAdapter) design patterns.

    Future Enhancements
    FavoritesComboBox has been a useful widget in our library of GUI components. However, as with many components in their infancy, there are always features left unimplemented. The following two features would prove useful to FavoritesComboBox:

    1. Recent List Persistence
    Currently, when an application containing FavoritesComboBoxes is launched, the recent list is empty; however, it would be nice if the combo box was populated with its last known recent items. This could be accomplished in several ways. One way to persist the recent list, albeit a simplistic approach, would be through the use of Java Serialization.

    2. Compound Renderer Support
    To change the font of the recent list and create a visual cue (the separator) between the recent list and the "whole" list, a special renderer, FavoritesListCellRenderer, was implemented. In many cases, a developer will want to use another renderer with FavoritesComboBox, but will be unable to since JComboBox supports only one renderer. For example, suppose a developer created a combo box containing a list of the universities in the Big Ten conference. The developer might want to "spice up" the combo box by providing icons displaying the school's mascot and/or colors. If the developer wanted to make this combo box a FavoritesComboBox, he or she would be unable to render both the icons and the separator/italics. As a result, FavoritesListCellRenderer could be altered to take a ListCellRenderer as one of its constructor parameters. Furthermore, FavoritesListCellRenderer's getListCell- RendererComponent() method would need to contain additional logic in order to compound the two separate renderers into one.

    Conclusion
    FavoritesComboBox and its associated support classes demonstrate how a useful component can be created through extending Swing classes and taking advantage of the flexibility of JComboBox. In our component each aspect of our enhancement to the basic Swing functionality was assigned to a separate custom class - keeping each new class simple and flexible.

    During design and development we leveraged well-known design patterns in order to increase the reliability and maintainability of our custom component. In addition, new requirements, including persistence and custom renderers, were discovered during FavoritesComboBox use in real applications. Although the design of GUI components can be quite complex, we're confident that these, along with other future enhancements, can be accomplished due to the component's good object-oriented design and use of design patterns.

    Acknowledgment
    We would like to thank the ASCltd team for their careful review and thoughtful suggestions.

    Resources
    1. Gamma, E., Helm, R., Johnson, R., and Vlissides, J. (1995). Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley.
    2. Grand, M. (1998). Patterns in Java. Vol. 1. Wiley.
    3. Eckstein, R., Loy, M., and Wood, D. (1998). Java Swing. O'Reilly & Associates.

  • More Stories By Justin Hill

    Justin Hill, a Sun-certified Java programmer, works for Advanced Systems Consulting, a Java-centric consulting firm in Chicago. He’s currently working on a Java-based source control management front-end to be released to the open-source community.

    More Stories By David Lesle

    David Lesle, a Sun-certified Java programmer, works for Advanced Systems Consulting. He's currently designing and developing "front office" multitier Java applications at a Chicago-based trading firm.

    Comments (0)

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.