Java Tip 48: How to create a reusable MessageBox class

Display a message on your browser screen, with Java 1.1 or later

As a programmer and consultant, I frequently field questions (often on newsgroups) such as: "Why does my modal dialog box keep locking up?", "How do I extract which button the user clicked in my OKCancel dialog?" and "Why doesn't Java have a standard MessageBox?" In this Java tip, I've provided one solution that takes into account all of these questions. So what's this all-encompassing answer? A reusable MessageBox class (for Java 1.1 or later versions) that allows you to display a message and clickable buttons in a browser window and gather a response from the user. It comes in the form of a JavaBean.

The bare minimum

Let's consider what we want from a use case point of view. A use case is a series of steps an actor performs to accomplish a goal. Putting our analyst hats on, it would be nice if we had a MessageBox class that allowed us to ask a user a question and get a response, say, as follows:

MessageBox box = new MessageBox(this);
box.setTitle("Delete Confirmation");
box.addChoice("Yes", "DeleteConfirmYes");
box.addChoice("No", "DeleteConfirmNo");
box.addChoice("Help", "DeleteConfirmHelp");
box.setCloseWindowCommand("DeleteConfirmNo");
box.ask("Do you really want to delete this customer?");

The above code is a use case at the lowest possible level. Note that we can configure the MessageBox for a wide variety of uses -- not just a limited one that provides the responses "Yes," "No," or "Cancel." It's a common beginner's error to build a MessageBox that handles only a few combinations of buttons. But once you begin to understand configurability, as is demonstrated in this simple MessageBox, you are on the path to designing reusable classes.

To receive notification that a button was clicked, we must implement ActionListener and test for the action command in public void actionPerformed(Action evt).

Adding features

For MessageBox to be a top-notch reusable class, we need a few more features. For example, what if we have a Frame and are opening a modal dialog box by using MessageBox? Shouldn't we provide MessageBox with our Frame so that when the MessageBox is gone, the focus will return to the Frame? What we need to add is the following optional use case feature:

box.setFrame(myFrame);

With GUIs getting more polished all the time on the Web, how can we snazz up our MessageBox and provide the user with a more conceptual ease of use? One way to achieve this is by allowing an image to be displayed along with the message. For this, we need to add an additional optional use case feature:

box.useImageCanvas(lightBulbImage);

But this means the client must create the image, and often the client simply wants to use a standard image in the same directory as MessageBox. In this case, we would like an easier method:

box.useImageCanvas("LightBulb.gif");

What if we find ourselves frequently using MessageBox to as questions that demand yes and no answers, thus creating "Yes" and "No" answer boxes? What if, even more often, we ask questions that are best answered with "Okay"? In that case, the more useful features would be:

box.askYesNo("Is Java now the defacto 3GL for smart developers?");

and:

box.askOkay("James Gosling come here I need you.");

Additional requirements are:

  • The dialog should not deadlock the thread that called it (see below for a section on what deadlocking is)

  • The window should close itself when a button is clicked

  • The dialog should center itself on the screen for easy reading

  • The dialog should be modal, whether or not a Frame is provided. By modal, we mean that users can only click in the MessageBox window, nowhere else in the application

Finally, what you've been waiting for: MessageBox code

Now that we have our requirements down, we can reveal the fabulous MessageBox.

Examine the source code for MessageBox in a separate window. As this code listing is too long to include in this tip, we will examine only the code highlights. MessageBox uses another reusable class: ImageCanvas. Note the class declaration:

public class MessageBox implements Runnable,
    ActionListener, WindowListener, KeyListener {
and the most important method:
public void ask(String message) {
    if (frame == null) {
        frame = new Frame();
        frameNotProvided = true;
    } else {
        frameNotProvided = false;
    }
    dialog = new Dialog(frame, true); // Modal
    dialog.addWindowListener(this);
    dialog.addKeyListener(this);
    dialog.setTitle(title);
    dialog.setLayout(new BorderLayout(5, 5));
    Panel messagePanel = createMultiLinePanel(message);
    if (imageCanvas == null) {
        dialog.add("Center", messagePanel);
    } else {
        Panel centerPanel = new Panel();
        centerPanel.add(imageCanvas);
        centerPanel.add(messagePanel);
        dialog.add("Center", centerPanel);
    }
    dialog.add("South", buttonPanel);
    dialog.pack();
    enforceMinimumSize(dialog, 200, 100);
    centerWindow(dialog);
    Toolkit.getDefaultToolkit().beep();
    // Start a new thread to show the dialog
    Thread thread = new Thread(this);
    thread.start();
}

We implement the listeners so as to receive these events, and implement Runnable so we can create a fine and dandy Java thread. Let's study the related methods:

public void run() {
    dialog.setVisible(true);
}

It couldn't get much simpler, could it? Notice in ask(), we start a new thread that causes run() to be called, and this shows the dialog. This is how we avoid deadlock, which we'll now pause for a few Web seconds to discuss.

Deadlock: A definition

All Java code runs in a thread or in threads. When starting a Java program by calling a main(), for example, the Java runtime creates a thread and calls main() within that thread. Typically, the main() method will instantiate an entry-point class, which will initialize the system and present a Frame or Dialog to the user. The initial thread dies when the main() method has finished running. The reason the Java runtime itself doesn't end is because the AWT has spawned one or more user threads to manage AWT behavior, including user input via buttons and such.

When the user clicks a button, the underlying "AWT thread" dispatches an ActionEvent to the button's ActionListeners that have the method actionPerformed(ActionEvent evt). Now, suppose in actionPerformed(), you decide to open a modal dialog box to ask the user something. When the modal dialog box is shown on the screen, the code blocks. ("Blocks" means a thread is waiting for notification to proceed, which, in the case of a modal dialog box, will not happen until the window is closed.) This means that the AWT thread that invoked actionPerformed() is waiting for the method to return. That AWT thread now is unavailable to process user input, such as on the dialog box we just opened -- so your application is deadlocked. Shucks.

To avoid this deadlock catastrophe, either switch to a "better" language or use the advanced features of Java (which makes Java the better language). Simply show the modal dialog box in a new thread, and all is peaches and roses in Javaland. This is what we have done in the code above. This type of deadlock is common until one understands its cause and has a simple solution for preventing it.

Conclusion

The rest of MessageBox is self-explanatory. Study the MessageBox code and the MessageBoxTest application and have fun.

JavaWorld would like to pass on your Java Tip to the rest of the Java world. Write up your coolest tips and tricks now, and send them to javatips@javaworld.com. You may find yourself an author in JavaWorld with your helpful hints chosen as the next Java Tip!

Jack Harich, aka "Happy Jack," is a fun-loving Renaissance man who switched to software after his career as a sculptor came to a quick end due to a neck injury. He's currently a consultant in Atlanta (the Silicon Cotton Field of the South) and is very active with the Atlanta Java User's Group, it's Java As A Second Language SIG, and the Atlanta Java Consortium.

Learn more about this topic

Related: