The Java AWT: Dialogs

by Jan Newmarch

Copyright: © 1996 Jan Newmarch, All Rights Reserved. Used With Permission.
Key Words: JAVA, Web programming, WWW, HTML


Contents


Manipulating dialogs

Creating a dialog in a frame

A dialog is created using new with one of the following constructors:
Dialog(Frame frame, boolean modal)
Dialog(Frame frame, String title, boolean modal)
A minimal example is
import java.awt.*;

public class Minimal extends Frame {

    public static void main(String argv[]) {
	new Minimal().show();
    }

    Minimal() {
	add("Center", new InvokeDialog(this));
	pack();
    }
}

class InvokeDialog extends Button {

    Frame frame;

    InvokeDialog(Frame fr) {
	super("Show dialog");
	frame = fr;
    }

    public boolean action(Event evt, Object what) {
	Dialog d = new Dialog(frame, false);
	d.add("Center", new Label("Hello"));
	d.pack();
	d.show();
	return true;
    }
}
There are several immediate points from this example

Creating a dialog in an applet

An applet subclasses from Applet, and this is not a Frame. If we want to have a dialog appearing from within an applet, we need to create or find a Frame just for the dialog.

The applet may be run from appletviewer. The grandparent of the Applet is a class from Sun called AppletViewer which is a subclass of Frame. On the other hand, the applet may be run from Netscape. The parent of the Applet in this case is a netscape.applet.EmbeddedAppletFrame which is also a subclass of Frame. However, there will in the future be many more possible ways of running applets, and I have not seen a definitive statement from Sun that an applet must eventually be contained in a Frame. So there may or may not be a convenient Frame for use.

Why not just create a brand-new Frame when needed? This frame need contain no objects and need not be visible. This frame must have a non-null peer so that its associated native window exists, and this can be done by addNotify(). There is a minor problem with this, that only shows up window managers that use interactive placement (such as fvwm with default configuration under Linux). Even though the frame is not visible, the window manager still insists on placing it. Presumably this is a bug in AWT that will eventually disappear, but for the meantime we can try to avoid it by using any existing frame:

import java.awt.*;
import java.applet.*;

public class MinimalApplet extends Applet {

    public void init() {
	add("Center", new InvokeDialog());
    }
}

class InvokeDialog extends Button {

    InvokeDialog() {
	super("Show dialog");
    }

    public boolean action(Event evt, Object what) {
	Frame frame = getFrame();
	Dialog d = new Dialog(frame, false);

	d.add("Center", new Label("Hello"));
	d.pack();
	d.show();
	return true;
    }

    // see if an ancestor is a Frame
   // or create a new one
    private Frame getFrame() {
	Component parent = this;

	while (parent != null &&
		! (parent instanceof Frame)) {
	    parent = parent.getParent();
	}
	if (parent != null) {
	    return (Frame) parent;
	} else {
	    Frame frame = new Frame();
	    frame.addNotify();
	    return frame;
	}
    }
}

The program looks like
this when run from appletviewer:

Hiding and destroying dialogs

When you finished with a dialog, you dismiss it in some way, either by clicking on a "Done" button or using the Window manager's "Close" mechanism. Typically, an application would then destroy the dialog or simply hide it for reuse later. Destruction is done by the dispose() method, hiding by hide(). For example, hiding on click of an Ok button can be done for this simple About... dialog:
import java.awt.*;

public class About extends Frame {

    public static void main(String argv[])
    {
	new About();
    }

    About() {
	add("Center", new PopupDialogBtn("Popup about..."));
	resize(200, 200);
	show();
    }
}

class PopupDialogBtn extends Button {
    Dialog dialog;

    PopupDialogBtn(String name) {
	super(name);
    }

    public boolean action(Event event, Object what) {
	if (dialog == null) {
	    dialog = new Dialog((Frame)getParent(), "About ...", true);
	    OkButton btn = new OkButton("Ok");
	    Label label = new Label("About: Version 0.0, 1995");
	    dialog.add("South", btn);
	    dialog.add("Center", label);
	    dialog.resize(200, 100);
	}
	dialog.show();
	return true;
    }
}

class OkButton extends Button {
    OkButton(String name) {
	super(name);
    }

    public boolean action(Event evt, Object what) {
	getParent().hide();
	return true;
    }
}
This hides the dialog when the Ok button is pressed. However, it does not behave correctly under window manager functions - WM_DELETE_WINDOW has no effect, and WM_DESTROY_WINDOW kills the entire application, not just the dialog. WM_DELETE_WINDOW is caught by the toolkit and generates an AWT WINDOW_DESTROY event. WM_DESTROY_WINDOW is not caught.

To handle WINDOW_DESTROY, we need to use a subclass of Dialog. There is no convenience method for this event, so we need to override handleEvent:

public class ClosableDialog extends Dialog {

    public ClosableDialog(Frame fr, boolean modal) {
	super(fr, modal);
    }

    public ClosableDialog(Frame fr, String name, boolean modal) {
	super(fr, name,  modal);
    }

    public boolean handleEvent(Event evt) {
	if (evt.id == Event.WINDOW_DESTROY) {
	    return windowQuit();
	}
	return super.handleEvent(evt);
    }

    protected boolean windowQuit() {
	dispose();
	return true;
    }
}
The ClosableDialog disposes of itself by default. If you would rather hide it for later reuse, then this can be subclassed and the windowQuit() method overridden to call hide() instead of dispose().

The code given for ClosableDialog is a little less general than it should be. Other window manager events that appear as AWT events are WINDOW_ICONIFY, WINDOW_DEICONIFY and WINDOW_MOVED (the event WINDOW_EXPOSE is never generated). While the default behaviour appears to be ok, there are no convenience handlers for them. This class is a convenient place to define such handlers so they can be overridden in subclasses if desired.

Modal dialogs

If the modal parameter in a constructor is set to true, then the dialog is created FULL_APPLICATION_MODAL. This means that it blocks any further interaction with the application until the dialog is either hidden or disposed of. Modal dialogs can run as "inline" code, with application logic immediately following show() of the dialog.

For a simple example of a modal dialog, consider a password protecting access to the system. The password is required to be correctly entered before the user is allowed to proceed to the next stage. This is best done by a modal dialog that pops up a TextField. When an entry is made in this, the dialog should be examined for the entry in its textfield and then password verification proceeds. Code for this would look like

    PasswordDialog dialog = new PasswordDialog(frame, true);
    // start modal dialog
    dialog.show();
    // reach here when the dialog is hidden
    String passwd = dialog.getText());
    dialog.dispose();
    // check password ...
Note that disposing of the dialog must be done after the string has been extracted from it - otherwise it doesn't exist to give it!

So much for the principle. In JDK 1.0 and 1.01 the code is unfortunately broken, and if you attempt to run something like the above then the dialog will flash up, disappear and you will get an empty string for password. It is quite simply a timing issue: sending events to the server will take place after this piece of code has finished, so the dialog doesn't become modal until long after the code to dispose of it has executed. The Motif "bible" by Dan Heller shows what is going on. After a dialog has been declared modal it is necessary to enter an event loop till some terminating condition is reached, such as the user clicking on a Ok button. This modal-dialog event loop is not present in the AWT implementation under X.

You have to work around this bug by allowing control back into the X event loop. Essentially, this means that you have to write clumsy non-modal code to emulate sequential processing because control has to return to the underlying event loop half-way through.

The logic in the following program is complex because of this. When the modal dialog is created and shown, control passes back to the underlying event loop handler as soon as InvokeDialog's action() finishes. Within the dialog, the action() method is called when the password is entered. This extracts the text, destroys the dialog and then proceeds with the rest of the application via the method gotPassword(). This is, without doubt, more complex than if modal dialogs worked as they were meant to!

import java.awt.*;

public class Password extends Frame {

    public static void main(String argv[]) {
	new Password().show();
    }

    Password() {
	add("Center", new InvokePasswordDialog(this));
	pack();
    }

    void gotPassword(String password) {
	System.out.println("Password: " + password);
	// other application processing
    }
}

class InvokePasswordDialog extends Button {

    Password frame;

    InvokePasswordDialog(Password fr) {
	super("Ask for password");
	frame = fr;
    }

    public boolean action(Event evt, Object what) {
	PasswordDialog d = new PasswordDialog(frame);
	d.show();
	return true;
    }
}

class PasswordDialog extends Dialog {

    Password frame;
    LabelledTextField edit;

    PasswordDialog(Password frame) {
	// make the dialog modal
	// it can't be dismissed by the window manager
	super(frame, "Password", true);

	this.frame = frame;
	edit = new LabelledTextField("Password:", 20);
	edit.setEchoCharacter('*');
	add("Center", edit);
	Button ok = new Button("Ok");
	add("South", ok);
	pack();
    }

    // invoked by  in either ok button
    // the edit labelledtextfield
    public boolean action(Event evt, Object what) {
	String text = edit.getText();
	dispose();
	frame.gotPassword(text);
	return true;
    }
}

class LabelledTextField extends Panel {
    Label label;
    TextField text;

    public LabelledTextField(String l, int cols) {
	setLayout(new BorderLayout());
	label = new Label(l);
	text = new TextField(cols);
	add("West", label);
	add("Center", text);
    }

    public String getText() {
	return text.getText();
    }

    public void setEchoCharacter(char ch) {
	text.setEchoCharacter(ch);
    }

}

An applet using this is

At the time of writing, JDK 1.02 has just been released, and it is stated that this bug has been fixed. When all compilers and viewers move to this release then such unpleasant code will no longer be required.

Prettier dialogs

In the example programs given so far the layout has been pretty basic and unappealing. Given that the last two articles in this series have been about geometry layout, we should be able to improve on them!

Nearly all dialogs have a row of buttons along the bottom, giving a set of ``actions'' that can be performed from the dialog. On resizing, this area should remain constant in height. Horizontally the buttons are often all the same size, nicely spaced apart. If the available width is too small, then extra buttons should wrap onto the next line. This cannot easily be done using the standard layout managers. A custom-built manager for your local GUI style would be the ideal solution. We can get a reasonably close approximation to this by placing all the buttons under GridBagLayout and setting suitable constraints. (This only uses a single row of buttons - the ability to flow onto multiple rows if the width is too small would require a FlowLayout as well.)

Each button is made a bit taller than normal by setting ipady. The width of each button is made the same by setting ipadx. First the maximum preferred width is found. If the dialog is wide, using this for all buttons may make them look too narrow. So the width is adjusted so that in total the buttons will use at least half the available space. Then each button has its ipadx set to increase its width from its preferred value to the common value.

Layout of the buttons is done whenever the dialog is resized i.e. when a WINDOW_MOVED event is received. This has to be done by overriding handleEvent() since there is no convenience method for this event.

Buttons are added to the action area by addButton(). The remaining space in the dialog can be filled with specific objects. The example allows one object to set in this area by addControl(). This would generally be a container for the other dialog elements.

import java.awt.*;

public class ButtonedDialog extends ClosableDialog {

    protected ActionBox actionBox;

    public ButtonedDialog(Frame frame, boolean modal) {
	this(frame, "", modal);
    }

    public ButtonedDialog(Frame frame, String name, boolean modal) {
        super(frame, name, modal);
	actionBox = new ActionBox();
	add("South", actionBox);
    }

    public boolean handleEvent(Event evt) {
	if (evt.id == Event.WINDOW_MOVED) {
	    actionBox.layoutButtons(size().width);
	}
	return super.handleEvent(evt);
    }

    public void addButton(Button b) {
	actionBox.addButton(b);
    }

    public void addControl(Component c) {
	add("Center", c);
    }
}

class ActionBox extends Panel {
    GridBagLayout gridbag;
    GridBagConstraints constraints;

    public void layoutButtons(int width) {

	int nbuttons = countComponents();
	int preferredHeight;

	if (nbuttons == 0)
	    return;

	// split is half the width allowed to each button
	int split = nbuttons * 4;

	if (gridbag == null) {
	    gridbag = new GridBagLayout();
	    setLayout(gridbag);

	    constraints = new GridBagConstraints();
	    constraints.weightx = 1.0;
	    constraints.ipady = getComponent(0).preferredSize().height/2;
	}

	// find the largest width
	Dimension compSize;
	int maxWidth = 0;
	for (int n = 0; n < nbuttons; n++) {
	    compSize = getComponent(n).preferredSize();
	    maxWidth = Math.max(compSize.width, maxWidth);
	}
	// use the largest width or increase using available space
	maxWidth = Math.max(width/(nbuttons*2), maxWidth);

	// set the ipadx to make each button the same size
	for (int n = 0; n < nbuttons; n++) {
	    Component component = getComponent(n);

	    compSize = component.preferredSize();
	    constraints.ipadx = maxWidth - compSize.width;
	    gridbag.setConstraints(component, constraints);
	}
 
	layout();
    }
 
    public void addButton(Button b) {
	add(b);
    }  
}

The dialog can be popped up from the following trivial applet:
since you do are not running a Java-enabled browser, here is an image of the dialog

FileDialog

There is one structured dialog supplied with AWT, and that is a file selection dialog FileDialog. This dialog is modal only (and it works!). This class has two constructors
FileDialog(Frame parent, String title);
FileDialog(Frame parent, String title, 
           int mode);
The mode can be LOAD or SAVE - under X all this does is to set a label stating the mode of file access and does not have any semantics. This is set in the "default" button as either "Open" or "Save". If the mode is not specified, it is set to LOAD.

The modal nature of FileDialog makes it very easy to use. After showing it, you get its file value:

FileDialog fd = new FileDialog();
fd.show();
System.out.println("Selected: " + 
		   fd.getFile());

There is a serious "problem" with FileDialog which means that we cannot show any examples from this document: in order to function, FileDialog needs access to the filesystem where the Java code is running, and this is forbidden by browsers such as Netscape. Running code on browser machines brings up security issues that are answered by disallowing access to the filesystem.

So how about showing files from the server system? It is certainly possible for an applet to access files from the server site. If you wanted to allow, say, file transfer access from the server then a FileDialog showing files on the server could be useful. Unfortunately the native implementation of FileDialog (under X at least) does not allow such remote filesystem access. The Broadway project from the X Consortium may be able to solve such a problem, running the file dialog on the server, and distributing the presentation to the browser.

An applet using this is

Frame

Creating frames

A Frame is created by the constructors
Frame()
Frame(String title)
where the title is the title used by the window manager. A Frame is not a dialog, but is implemented for X by an XtApplicationShell, a Motif MainWindow and a Motif DrawingArea. The XtApplicationShell gives a new toplevel window, the MainWindow allows a menubar to be placed in it, and the DrawingArea is used to place all other components of the application.

A Frame is non-modal - there is no control over this.

Setting the cursor

It is possible to change the cursor showing to a limited degree. The possbile cursor values are defined as constants in class Frame as