Making a dialog box a JavaBean

If your programming projects require you to repeatedly create a dialog box that performs some common task, you might find it useful to create a dialog box as a JavaBean. Then when you need such a dialog box in your project, you can add it to your application from JBuilder’s Component Palette. The JBCL already contains a few common dialog boxes, such as the ColorChooser, FontChooser, and Message dialog boxes on the JBCL Containers page of the Component Palette.

This chapter uses the source code of ColorChooser, ColorChooserDialog, and ButtonDialog as sample code. All are found in the borland.jbcl.control package. You might want to examine the actual source code as you read through this chapter.


Creating the dialog box

Your first step is to create the dialog box itself. JBuilder has these ways to get you started:

Both of these methods provide a framework to modify. You might want to edit your resulting class so that it extends borland.jbcl.control.ButtonDialog. ButtonDialog also extends java.awt.Dialog, but it is designed to be used as a base class for any dialog box with one or more buttons.


Wrapping the dialog box class

To make a dialog box a bean, you must take some special steps. A class that subclasses java.awt.Dialog cannot by itself be a JavaBean. A subclass of Dialog must have a Frame component as a parent, so it requires that its constructor have a parameter of type Frame. JavaBeans, however, must have a parameterless constructor. If you simply add a parameterless constructor to your subclass of Dialog, your constructor won’t have the necessary Frame to create the dialog box.

To make a bean out of a dialog box, you must wrap the real dialog class with a separate class that has the dialog class as a subcomponent. The wrapper class surfaces the properties and events, delegates their handling to the actual dialog box when it is instantiated.

Wrapping the dialog box consists of several tasks; the following sections discuss each of these in detail:

  1. Create the wrapper class
  2. Instantiate the dialog box class in a constructor
  3. Add a frame property to the wrapper class
  4. Get the user’s input
  5. Display the dialog box
  6. Generate action events
  7. Respond to window events

Creating the wrapper class

To begin wrapping the dialog box,

  1. Create the wrapper class for your dialog box.

    If you use the JavaBean Wizard to begin, select java.lang.Object as the class to inherit from. If you use the Class Wizard, select the option to have a default (parameterless) constructor.

  2. Add implements WindowListener to the class declaration.

    Later you’ll register this wrapper class as a listener for window events that occur in your dialog box and you’ll write the code to respond to the window events your dialog box needs to pay attention to.

The following code and all remaining code in this chapter comes from the ColorChooser component available on the JBCL Containers page of the Component Palette. Here is the class declaration of ColorChooser:
public class ColorChooser implements WindowListener
{
  ...
ColorChooser has four different constructors including the very important parameterless constructor:
  public ColorChooser(Frame frame, String title, Color value) {
    this.frame = frame;
    this.title = title;
    this.value = value;
    if (frame != null)
      dialog = new ColorChooserDialog(frame, title, value);
  )
  public ColorChooser(Frame frame, String title) {
    this(frame, title, null);
  }
  public ColorChooser(Frame frame) {
    this(frame, "", null);
  )
public ColorChooser() {           parameterless constructor required to be JavaBean
  this(null, "", null);
}

Instantiating the dialog box class in a constructor

Note that the first constructor in ColorChooser instantiates an instance of ColorChooserDialog and assigns it to a variable called dialog. ColorChooserDialog is the dialog class that ColorChooser is a wrapper for. Your wrapper class should define a dialog variable and, within one of its constructors, instantiate your dialog box class:
  1. Add a dialog variable to your class. For example, this is the definition in ColorChooser:
    protected ColorChooserDialog dialog;
    
  2. Within a constructor, instantiate your dialog box class and assign it to the dialog variable:
    if (frame != null)
      dialog = new ColorChooserDialog(frame, title, value);

Adding a frame property

Your wrapper class needs a frame property that can be set to the desired parent Frame before the show() method displays the dialog box.

To add a frame property to your wrapper class, write the read and write access methods for frame:

public void setFrame(Frame frame) {
  this.frame = frame;
}

public getFrame() {
  return frame;
)

Getting user input

Your applications use dialog boxes to get input for the user. Your dialog box must have a way to get the values the user types in and to allow the user to change the current values.

Your application must also obtain another type of user input: which button the user chose. The user might display the dialog box, then put it away again without making any changes to the values.

First you’ll see how to retrieve the values the user enters in the dialog box, then how to track which button the user chose.

Getting and setting the values

For each value you are interested in obtaining from the user, you must add a property to both the wrapper class and the actual dialog class.

The ColorChooser is interested in only one value: the color the user selects. Therefore, ColorChooser contains the code to get and set just one property, the value property. This is how the getter and setter for the value property are written for ColorChooser:

public void setValue(Color value) {
  this.value = value;
  if (dialog != null)
    dialog.setValue(value);
}

public Color getValue() {
  if (dialog != null)
    value = dialog.getValue();
  return value;
)
Note that both setValue() and getValue() call the same methods in the dialog class. This is how the same methods are written in ColorChooserDialog:
public void setValue(Color value) {
  panel.setColor.getColorValue();
}

public Color getValue() {
  return panel.getColorValue();
}
The property read and write access methods you write for your wrapper class should be capable of getting and setting a property value even if the actual dialog class is not instantiated yet. When the dialog class is instantiated, they should delegate the getting and setting to the dialog class, just as the read and write access methods for the value property of ColorChooser do.

Finding out which button was chosen

You also need to know what the user does with the dialog box itself. Did the user fill in values and press the OK button? Or did the user click Cancel and put the dialog away? A dialog box can have a number of different buttons and your application needs to know which the user chose. Usually the user’s choice is stored in a result property. This is how ColorChooser implements result:
public void setResult(int i) {
  result = i;
  if (dialog != null)
    dialog.setResult(result);
}

public int getResult() {
  if (dialog != null)
    result = dialog.getResult();
  return result;
}
Again you can see that if the dialog class is instantiated, the same method is called in the dialog class. In this case, getResult() and setResult() are not in ColorChooserDialog, but in ButtonDialog, which ColorChooserDialog extends. So here are the result getter and setter methods in ButtonDialog:
public void setResult(int id) {
  result = buttonFromID(id);
}

public int getResult() {
  return result != null ? result.getID() : NONE;
}
The id parameter in setResult() can be one of the several constants in ButtonDialog that identify a particular button. There are constants for OK, YES, NO, CANCEL, DONE, HELP, APPLY, and so on. The getResult() method identifies which button the user chose.

Displaying the dialog box

To provide a way to display the dialog box, add a show() method to your wrapper class. Here is the show() method of ColorChooser:
public void show() {
  setVisible(true)
}
The show() method, or the method it calls, should instantiate the actual dialog class, using the value of the frame property as the parent frame in the dialog’s constructor. After the dialog is instantiated, you should set the properties in the dialog class to the values stored in the properties of the same name in the wrapper class.

Because the show() method of ColorChooser simply sets the visible property to true, here is the code of the setVisible() method:

public void setVisible(boolean visible) {
  if (visible) {
    if (dialog == null) {
      if (frame == null)                            // if frame isn’t set, throw exception
        throw new IllegalStateException(Res.getString(Res.NoFrame));
      dialog = new ColorChooserDialog(frame, title, value);   // instantiates dialog class
      dialog.setResult(result);                               // sets result of dialog class
      dialog.addWindowListener(this);
      dialog.addActionListener(actionMulticaster);
    }
    if (frame.isShowing())
      focus = frame.getFocusOwner();
    dialog.show();                                           // calls show() of dialog class
  }
  else {
    if (dialog != null)
      dialog.setVisible(false);
  }
}
setVisible() checks to see if the frame property has a value. If it doesn’t, an exception is thrown; otherwise, the actual dialog class is instantiated with the frame value.

Next the value of the result property is used to set the result property of the newly instantiated dialog. The wrapper class registers itself as a listener for window events. Later, the show() method of the dialog class is called; show() is defined in the ButtonDialog class.

Generating action events

Dialog boxes generate action events only, regardless of which type of action occurs within the dialog box. To prepare the dialog box to generate action events, implement the addActionListener() and removeActionListener() registration methods in the wrapper class. This is how they appear in ColorChooser:
public void addActionListener(ActionListener l) {
  actionMulticaster.add(l);
}

public void removeActionListener(ActionListener l) {
  actionMulticaster.remove(l);
}
actionMulticaster is declared as an instance of the ActionMulticaster class, which maintains a list of listeners for action events:
protected transient ActionMulticaster actionMulticaster = new ActionMulticaster();
If your dialog class extends the ButtonDialog class, that’s all you need to do to make your dialog box capable of generating action events.

If you don’t use ButtonDialog, you must add each button in the dialog as a listener for action events. You must also write the method that dispatches an ActionEvent to all listeners. Examine the setButtonSet() method of ButtonDialog to see how buttons are registered as listeners for actions events, and look at the actionPerformed() and processActionEvent() methods to see how action events are dispatched.

Responding to window events

In Displaying the dialog box, you saw the setVisible() method of ColorChooser. Within that code, the wrapper class added itself as a listener for window events. You must decide which window events you want your dialog box to respond to and then write the code that responds.

The WindowListener interface has seven methods to implement:

Add each of these methods to the wrapper class and implement them with empty bodies. Then decide which of these you want to write more code for.

The ColorChooser added additional code for the windowClosing() and windowClosed() events only:

public void windowOpened(WindowEvent e) {}
public void windowClosing(WindowEvent e) {
  value = dialog.getValue();
  result = dialog.getResult();
}

public void windowClosed(WindowEvent e) {
  if (dialog != null) {
    value = dialog.getValue();
    result = dialog.getResult();
  }
  
  dialog = null;
  if (frame.isShowing()) {
    if (focus != null)
      focus.requestFocus();
    else
      frame.requestFocus();
  }
}
Note that each of these retrieve the current value and result property values from the dialog box and store them in the properties with the same name in the wrapper class. The user’s application can then retrieve these values from the wrapper class and use them as needed.


Closing the dialog box

Your dialog box must have a way to close, or disappear from the screen, when the user is done with it. If you extend the ButtonDialog class, the dialog disappears when any of these buttons are clicked: OK, YES, NO, CANCEL, DONE. If your class doesn’t extend ButtonDialog, you must decide which buttons close the dialog box and call hide() or setVisble(false) at the appropriate times. You might also want to call dispose() to remove the dialog from memory. See the processActionEvent() method of ButtonDialog for an example.


Using the dialog box

When you use a bean-wrapped dialog box in an application, you must always set the frame property before calling its show() method. Likewise, if you drop such a JavaBean on the Component Tree, you must set its frame property to this before setting its visible property to true in the Component Inspector; otherwise, the dialog box does not appear in the UI Designer.

If the dialog box you create is modeless, the call to the show() method returns as soon as the dialog box appears. For modeless dialog boxes, show() does not wait for the dialog box to be closed by the user. Code that uses a modeless dialog must listen to dialog events you surface to know when to respond to user actions.

If the dialog box is modal, your application can also listen for events within the dialog box, or it can wait for the show() method to return. Because the show() method of a modal dialog box does not return until the user closes the dialog box, your code can wait until then to check the result code and any property values.