by Maria Winslow

Creating custom components

how-to
Apr 1, 19979 mins
Core JavaJava

Learn how easy it is to create specialized components, reuse them, and keep your application-level code cleaner with internal event handling

Many applications can work just fine with standard user interface elements supplied with the Java API. And when necessary, programmers can glue these elements together to create a more sophisticated interface. However, if a collection of supplied components are added and handled in order to achieve a higher-level user interface function โ€” a single conceptual user event like a font selector, for example โ€” the event handling can get extremely messy at the application level.

Letโ€™s take our example above and consider an application that needs a font selector. To add this functionality, we would need to use three separate components to gather the name, style, and point size of the font. The applicationโ€™s event-handling code would, of course, need to include filters for all three components. Thatโ€™s a whole lot of internal event handling to keep track of. If instead we created a distinct font selector component, this inherently internal event handling would be hidden. The application would only need to catch a font selector event object, and the target of the event would be the instantiated font selector object itself โ€” a much cleaner approach.

Clearly, an important feature of custom components is this ability to hide details from your application, but the decision to write custom components also should be driven by the reusability factor. When you find yourself in need of a particular functionality over and over again, it would make sense to create a custom component for this task. Taken one step further, you can combine specialized user interface elements that are used repeatedly into a custom component package, making the classes easy to reuse, especially among a group of developers.

Now, before you go writing customized code for every possible variation of a component, I must mention one caveat in regard to custom components. When you are developing applets, your custom component classes will have to be downloaded because they are not part of the core API available locally to all Java virtual machines. As long as your applet doesnโ€™t use a lot of custom classes, youโ€™ve got nothing to worry about, but if optimal download time is critical, make sure you thoroughly consider the necessity of enhanced functionality.

Event handling in custom components

As I mentioned earlier, one really nice advantage of writing specialized components is that your application-level event handling will be much cleaner. The Component superclass has a method, postEvent, that you can use to propagate an event to the parent container. This Event object will contain all of the information that the container needs to determine the details of the event.

The Event object has several variables (target, arg, date, x, and y, among others) for determining such facts as which component generated the event and the time of the action. The arg variable is essentially the โ€œmessageโ€ of the event. This field is of type Object, which means that it can hold any type of information. The Checkbox class, for example, uses this field to store a boolean corresponding to the state of the checkbox, which allows it to notify the application of its new state. In terms of a font selector, the message this component needs to get to the application is the Font object chosen by the user.

The general technique is to post an event with the appropriate type as the arg variable. The application then can check to see which component generated the event, cast the arg variable as the proper object type (as a Font in the case of the font selector), and go from there. Letโ€™s take a look at this technique in practice.

Writing the combo box event-handling code

A Windows-style combo box, one of the more useful user-interface components, is missing from the Java core API. Because a combo box is a widget weโ€™re likely to need again, and one that requires slightly messy internal event handling, it is a good candidate for the custom component toolbox. To build our combo box, we can use standard TextField and List objects, making them work together. Events from each standard component will need to be handled internally. When the user makes a choice, a new Event object will be created and posted with the appropriate string as the arg variable.

As you can see from the following snippet, the ComboBox class extends the Panel class, which means we have a way of cleanly catching events in one place. The panel is the container for the individual elements of the combo box. In order for this component to be useful for an application, we want to hide the implementation so that the application simply instantiates the combo box, adds items to it, and adds it to a container. Meaningful events are expected to be generated when appropriate. From the point of view of the application that uses the combo box, only one event will be generated โ€” the list item that the user commits to.

import java.awt.*;
package my.components;
public class ComboBox extends Panel {
   TextField text;
   List list;
   public ComboBox() {
      text = new TextField();
      list = new List();
      list.setMultipleSelections(false);
      setLayout(new BorderLayout());
      add("North", text);
      add("Center", list);
   } // end constructor

Notice that a global reference is kept to the TextField and List objects. In the constructor, the base components are instantiated and laid out in a border style so that they line up evenly. The List is set to allow only one selection at a time.

   public void addItem(String item) {
      list.addItem(item);
   }

The application that creates the combo box will use the addItem method of this class to add items to the combo box. These items should initially appear in the list portion of the combo box, so a call is made to the addItem method of List.

Now letโ€™s look at the really interesting part of the class โ€” the event handling:

   public boolean handleEvent(Event e) {
     if ((e.id == Event.ACTION_EVENT) && (e.target == text)) {
        postEvent(new Event(this, Event.ACTION_EVENT, text.getText())); 
     }
     else if (e.id == Event.LIST_SELECT) {
        text.setText(list.getSelectedItem());
        text.requestFocus();
     }
     else if (e.id == Event.KEY_ACTION) {
        if ((e.key == Event.UP) && (list.getSelectedIndex() > 0)) {
           list.select(list.getSelectedIndex() - 1);
           text.setText(list.getSelectedItem());
        }
        if ((e.key == Event.DOWN) && (list.getSelectedIndex() < list.countItems())) {
           list.select(list.getSelectedIndex() + 1);
           text.setText(list.getSelectedItem());
        }
     }
     else if (e.id == Event.MOUSE_ENTER) {
        text.requestFocus();
     }
     return(super.handleEvent(e));
   } // end handleEvent
} // end ComboBox

In the handleEvent method, we need to test for five kinds of events: an action event on the text field, a list select, an up arrow keypress, a down arrow keypress, and a mouse enter. The last four are internal events and should not be posted to the application; they are only meaningful within the context of the componentโ€™s internal behavior.

When the user clicks on a list item, it is placed in the text field, which is then given the input focus. If the user presses the up or down arrow keys, the selected list item is changed and the text field is set to the new selected value. MOUSE_ENTER events are monitored and the text field is automatically given the input focus to make the component more convenient to the user. This means that the user does not have to click on anything in order to use the combo box.

When the user presses Return in the text field, it signals that the final selection has been made. The current value is then sent to the calling application as a posted event. A new Event object is created with a target of this (the ComboBox class itself), an id of ACTION_EVENT, and an arg of the text in the text field. The constructor Iโ€™m using here has the following signature:

Event(Component target, int Event.id, Object arg);

Applications that use ComboBoxes will check for ACTION_EVENTs and use the arg as the obtained value.

Applying what weโ€™ve learned

The ComboBox component is now ready to use in an application. The following applet code demonstrates its use. For the sake of completeness, Iโ€™ve included other components (a label, some buttons, and a text area) that could be used in combination with a ComboBox to create an interface.

import java.awt.*;
import java.applet.*;
import my.components.ComboBox;
public class Test extends Applet {
   TextArea textarea;
   public void init() {
      setLayout(new BorderLayout());
      ComboBox cb = new ComboBox();
      cb.addItem("One");
      cb.addItem("Two");
      cb.addItem("Three");
      cb.addItem("Four");
      cb.addItem("Five");
      Label l = new Label("This is a test of the combo box...");
      Panel p = new Panel();
      p.setLayout(new GridLayout(2, 3));
      p.add(new Button("One"));
      p.add(new Button("Two"));
      p.add(new Button("Three"));
      p.add(new Button("Four"));
      p.add(new Button("Five"));
      p.add(new Button("Six"));
      textarea = new TextArea();
      add("East", cb);
      add("Center", textarea);
      add("North", l);
      add("South", p);
   } // end init
   public boolean handleEvent(Event e) {
      if ((e.id == Event.ACTION_EVENT) && (e.target instanceof ComboBox)) {
         textarea.appendText("ComboBox yielded: " + (String)e.arg + "n");
      }
      return(super.handleEvent(e));
   } // end handleEvent
} // end Test

Letโ€™s take a look at whatโ€™s happening here. First, the ComboBox is created and items are added to it. Next, the component is added to the applet just like the other components. (Grouping custom components into a separate package makes them easy to use by other programmers.) To make a truly reusable library and give your API the same look as the core API, I recommend you use the Java commenting style requested by the javadoc documentation generator. See the Resources section of this column to find out where you can learn more about this special documentation.

On your own

Once you understand this technique, you will find it quite easy to create custom components. Some components you might want to consider toying with include auto-sorting lists, text fields that validate on commonly used sequences (such as social security numbers), and an auto-updating text clock. In addition to these common components, any specialized user functions that appear frequently in your projects would be good candidates for this process.

Maria Winslow co-founded Prominence Dot Com in 1995, and is currently developing top-secret Java applications. She co-authored Java Network Programming, from Manning/Prentice-Hall. If Maria had spare time, she would gorge herself on Slim Jims while wrestling with the nature of determinism and free will.