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
Frame
as one parameter.
pack()
to set its preferred size.
show()
.
BorderLayout
.
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
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 byin 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:
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
These may be set in a Frame
by the method
setCursor()
. For example,
import java.awt.*; public class TestCursor extends Frame { static public void main(String argv[]) { new TestCursor().show(); } public TestCursor() { String cursorNames[] = { "default", "crosshair", "text", "wait", "sw resize", "se resize", "nw resize", "ne resize", "n resize", "s resize", "w resize", "e resize", "hand", "move" }; CursorList list = new CursorList(); add("Center", list); for (int n = Frame.DEFAULT_CURSOR; n <= Frame.MOVE_CURSOR; n++) { list.addItem(cursorNames[n]); } pack(); } } class CursorList extends List { public boolean handleEvent(Event evt) { if (evt.id == Event.LIST_SELECT) { Frame frame = (Frame) getParent(); frame.setCursor(((Integer) (evt.arg)).intValue()); return true; } return super.handleEvent(evt); } }
An applet using this is
There are limitations in setting cursors:
setCursor()
for Applet
.
Dialog
- there is no
setCursor()
there either.
Frame
and then a Dialog
created from there, the Dialog
will keep its default cursor
whatever happens in the Frame
.
Text
will set their own cursor,
but this level of control comes from the native toolkit and is not
yet controllable from AWT.
Frame
may be set by the method
setIcon()
. The icon is loaded from a "gif" file
by the default toolkit's getImage()
method:
import java.awt.*; public class TestIcon extends Frame { static public void main(String argv[]) { new TestIcon().show(); } public TestIcon() { add("Center", new Label("Changed icon")); pack(); Toolkit toolkit = getToolkit(); Image img = toolkit.getImage("glacier.gif"); setIconImage(img); } }
There are limitations in setting icons, just like there were with cursors:
setIconImage()
anyway.
Dialog
- there is no
setCursor()
. Since Dialog
is created
as a popup shell, some window managers might not even show an
iconify button for it.
setIconImage()
allows the application to inform
the window manager about which icon to use.
All of Frame
,
FileDialog
and Dialog
allow the title of the
window to be set in a constructor. Both Frame
and
Dialog
have a method setTitle()
to change
the title.
Sizing windows
Some of the characteristics of the current display can be found
from the Toolkit
methods
Dimension getScreenSize() int getScreenResolution() // dots per inch ColorModel getColorModel()
A Frame
,
FileDialog
or Dialog
can all be located on the
screen by the resize()
method.
Stacking order
Windows can be moved brought to the front or back of other windows
by the methods
toFront() toBack()
Here is a potentially aggravating applet using these methods: it creates a number of frames and moves and resizes them around the screen for about 20 seconds. Each frame is handled by its own thread (just for fun). A frame with a reassuring message (Don't Panic) is placed on top to ... reassure you.
import java.awt.*; import java.applet.*; import java.lang.*; import java.util.*; public class MovingFrames extends Applet { public MovingFrames() { Toolkit toolkit = getToolkit(); Dimension screenSize = toolkit.getScreenSize(); add("Center", new MovingFramesButton(screenSize)); } } // This class creates the frames when on action class MovingFramesButton extends Button { Dimension screenSize; MovingFramesButton(Dimension size) { super("Make some moving frames"); screenSize = size; } public boolean action(Event evt, Object what) { Color colors[] = { Color.blue, Color.cyan, Color.green, Color.pink, Color.yellow, Color.red }; DontPanic dontPanic = new DontPanic(screenSize); Thread threads[] = new Thread[colors.length]; MovingFrame frames[] = new MovingFrame[colors.length]; // one frame per color in its own thread for (int n = 0; n < colors.length; n++) { frames[n] = new MovingFrame(screenSize, colors[n], dontPanic); threads[n] = new Thread(frames[n]); threads[n].start(); } // be nice - stop all the other frames after 20 seconds Thread thisThread; try { thisThread = Thread.currentThread(); thisThread.sleep(20000); } catch(Exception e) {} for (int n = 0; n < threads.length; n++) { threads[n].stop(); frames[n].dispose(); } return true; } } // a frame that moves around the screen class MovingFrame extends Frame implements Runnable { Dimension screenSize; Thread thread; Random rand = new Random(); Frame dontPanic; MovingFrame(Dimension size, Color color, Frame dontPanic) { screenSize = size; this.dontPanic = dontPanic; setBackground(color); moveMe(); show(); } // called when this thread is run public void run() { while (true) { try { thread = Thread.currentThread(); thread.sleep(5000); } catch(Exception e) {} moveMe(); } } void moveMe() { // make us a random size int width = (int) Math.floor(rand.nextFloat() * screenSize.width); int height = (int) Math.floor(rand.nextFloat() * screenSize.height); // and fit us somewhere on the screen int x = (int) Math.floor(rand.nextFloat() * (screenSize.width - width)); int y = (int) Math.floor(rand.nextFloat() * (screenSize.height - height)); reshape(x, y, width, height); // put us on top toFront(); repaint(0); // put the reassuring messge to the front dontPanic.toFront(); } } // a Don't Panic message class DontPanic extends Frame { DontPanic(Dimension size) { super("Don't Panic"); Label label = new Label("Don't Panic", Label.CENTER); label.setFont(new Font("Courier", Font.BOLD, 36)); // label.addNotify(); add("Center", label); pack(); Dimension preferred = label.preferredSize(); reshape((size.width - preferred.width)/2, (size.height - preferred.height)/2, preferred.width * 2, preferred.height * 2); show(); } }
An applet using this is
Conclusion
This article has discussed top-level windows of dialogs and frames.
Basic techniques to deal with them have been presented.
Some discussion of window manger interactions has been given.
There are aspects of such interaction that are sometimes used by X applications,
such as adding entries to the window manager's menu - these are not
yet accessible from AWT.