Section 7.7
Frames and Dialogs
APPLETS ARE A FINE IDEA. It's nice to be able to
put a complete program in a rectangle on a Web page. But more serious,
large-scale programs have to run in their own windows, independently of
a Web browser. In Java's Swing GUI library, an independent window is represented
by an object of type JFrame. A stand-alone GUI application
can open one or more JFrames to provide the user interface.
It is even possible for an applet to open a frame. The frame will
be a separate window from the Web browser window in which the applet
is running. Any frame created by an applet includes a warning
message such as "Warning: Insecure Applet Window."
The warning is there so that you can always recognize windows
created by applets. This is just one of the security restrictions
on applets, which, after all, are programs that can be downloaded automatically
from Web sites that you happen to stumble across without knowing anything
about them.
Here is an applet that contains just one small button.
When you click this "Launch ShapeDraw" button,
a JFrame will be opened:
The frame that is created when you click the button is
almost identical to the ShapeDrawWithMenus applet from
Section 5, and it is used in the
same way. However, you can change the size of the window, and
you can make the window go away by clicking its close box.
The window in this example belongs to a class named
ShapeDrawFrame, which is defined as a subclass of
JFrame. The structure of a JFrame is almost identical to
a JApplet, and the programming is almost the same.
In fact, only a few changes had to be made
to the applet class, ShapeDrawWithMenus, to convert it
from a JApplet to a JFrame.
First, a frame does not have an init()
method, so the initialization for ShapeDrawFrame is done in
a constructor instead of in init(). In fact, the only
real change necessary to convert a typical applet class into a frame class
is to convert the applet's init() method into a constructor,
and to add a bit of frame-specific initialization to the constructor.
Everything that you learned about creating and programming GUI components
and adding them to a content pane applies to frames as well. Menus
are also handled in exactly the same way. So, we really only need
to look at the additional programming that is necessary for frames.
One significant difference is that the size and location of an
applet are determined externally to the applet, by the HTML code for
a Web page and by the browser that displays the page. The size of
a frame, on the other hand, has to be set by the frame itself or
by the program that creates the frame. Often, the size is set
in the frame's constructor. (If you forget to do this, the frame
will have size zero and all you will see on the screen is a tiny border.)
There are two methods in the JFrame class that can be used to set the
size of a frame:
void setSize(int width, int height);
and
void pack();
Use setSize() if you know exactly what size the frame
should be. The pack() method is more interesting. It should
only be called after all GUI components have been added to the frame.
Calling pack() will make the frame just big enough to hold
all the components. It will determine the size of the frame by
checking the preferred size of each of the components that it contains.
(As mentioned in Section 3, standard GUI
components come with a preferred size. When you create your own
drawing surface or custom component, you can set its preferred
size by calling its setPreferredSize() method
or by definining a getPreferredSize() method to compute
the size.)
You can also set the location of the frame. This
can be done with by calling the JFrame method:
void setLocation(int x, int y);
The parameters, x and y give the position of
the upper left corner of the frame on the screen. They are given
in terms of pixel coordinates on the screen as a whole, where the
upper left corner of the screen is (0,0).
Creating a frame object does not automatically make a window
appear on the screen. Initially, the window is invisible, You
must make it visible by calling its method
void show();
This can be called at the end of the frame's constructor, but
it can also be reasonable to leave it out. In that case, the
constructor will produce an invisible window, and the program
that creates the frame is responsible for calling its show()
method.
A frame has a title, a string that appears
in the title bar at the top of the window. This title can be provided
as an argument to the constructor or it can be set by calling the method:
void setTitle(String title);
(In the ShapeDrawFrame class, I set the title of the frame
to be "Shape Draw". I do this by calling a constructor in the superclass
with the command:
super("Shape Draw");
at the beginning of the ShapeDrawFrame constructor.)
Now you know how to get a frame onto the screen and how to give
it a title. There is still the matter of getting rid of the frame.
You can hide a frame by calling its hide() method. If you do
this, you can make it visible again by calling show().
If you are completely finished with the window, you can call its
dispose() method to close the window and free the system resources that it uses.
After calling dispose(), you should not use the frame
object again. You also have to be prepared for the fact that the
user can click on the window's close box to indicate that it should be
closed. By default, the system will hide the window when the user
clicks its close box. However, you can tell the system to
handle this event differently. Sometimes, it makes more sense to
dispose of the window or even to call System.exit() and
end the program entirely. It is even possible, as we will see
below, to set up a listener to listen for the event, and to program
any response you want. You can set the default response to a
click in a frame's close box by calling:
void setDefaultCloseOperation(int operation);
where the parameter, operation is one of the constants
- JFrame.HIDE_ON_CLOSE -- just hide the window, so it can be opened again
- JFrame.DISPOSE_ON_CLOSE -- destroy the window, but don't end the program
- JFrame.EXIT_ON_CLOSE -- terminate the Java interpretor by calling System.exit()
- JFrame.DO_NOTHING_ON_CLOSE -- no automatic response; your program is responsible for closing the window.
If a frame is opened by a main program, and if the program has only one window, it might
make sense to use EXIT_ON_CLOSE. However, note that this option is illegal for a frame
that is created by an applet, since an applet is not allowed to shut down the Java interpreter.
In case you are curious, here are the lines that I added to the end of
the ShapeDrawFrame constructor:
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocation(20,50);
setSize(550,420);
show();
I also added a main() routine to the class. This means that it
is possible to run ShapeDrawFrame as a stand-alone application. In a
typical command-line environment, you would do this with
the command:
java ShapeDrawFrame
It has been a while since we
looked at a stand-alone program with a main() routine, and
we have never seen a stand-alone GUI program. It's easy for
a stand-alone program to use a graphical user interface -- all it has
to do is open a frame. Since a ShapeDrawFrame makes itself
visible when it is created, it is only necessary to create the frame
object with the command "new ShapeDrawFrame();".
The complete main routine in this case looks like:
public static void main(String[] args) {
new ShapeDrawFrame();
}
It might look a bit unusual to call a constructor without assigning the result
to a variable, but it is perfectly legal and in this case we have no need to
store the value. The main routine ends as soon as the frame is created,
but the frame continues to exist and the program will continue running.
Since the default close operation for the frame has been set to EXIT_ON_CLOSE,
the frame will close and the program will be terminated when the user clicks
the close box of the window. It might seem a bit odd to have this main()
routine in the same class that defines ShapeDrawFrame, and in fact it
could just as easily be in a separate class. But there is no real need to
create an extra class, as long as you understand what is going on. When you
type "java ShapeDrawFrame" on the command line,
the system looks for a main routine in the ShapeDrawFrame class
and executes it. If the main routine happens to create an object belonging
to the same class, it's not a problem. It's just a command to be executed.
The source code for the frame class is in the file ShapeDrawFrame.java.
The applet, shown above, which opens a frame of this type
is in ShapeDrawLauncher.java.
We'll look at a few more fine points of programming with frames by looking
at another example. In this case, it's a frame version of the HighLowGUI2
applet from Section 1. Click on this button
to open the frame:
The frame in this example is defined in the file HighLowFrame.java.
In many ways, this example is similar to the previous one, but there are several
differences. You can resize the frame in the first example by dragging its border
or corner, but if you try to resize the "High Low" frame, you will find that it
is impossible. A frame is resizable by default. You can make it non-resizable
by calling setResizable(false). I do this in the constructor of the
HighLowFrame class. Another difference shows up if you click the frame's
close box. This will not simply close the window. Instead a new window will open
to ask you whether you really want to close the HighLow frame. The new window is an
example of a "dialog box". You will learn about dialog boxes later in this section.
To proceed, you have to click a button in the dialog box. If you click on "Yes", the
HighLow frame will be closed; if not, the frame will remain open. (This would be more
useful if, for example, you wanted to give the user a chance to save some unsaved
work before closing the window.) To get this behavior, I had to turn off the
system's default handling of the close box with the command:
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
and I had to program my own response instead. I did this by registering a
window listener for the frame. When certain operations
are performed on a window, the window generates an event of type WindowEvent.
You can program a response to such events in the usual way: by registering a
listening object of type WindowListener with the frame. The JFrame
class has an addWindowListener()method for this purpose. The WindowListener
must define seven event-handling methods, including the poorly named
public void windowClosing(WindowEvent evt)
and
public void windowClosed(WindowEvent evt)
The windowClosing event is generated when the user clicks the close
box of the window. The windowClosed event is generated when the
window is actually being disposed. The other five methods in the WindowListener
interface are more rarely used. Fortunately, you don't have to worry about them
if you use the WindowAdapter class. The WindowAdapter class
implements the WindowListener interface, but defines all the WindowListener
methods to be empty. You can define a WindowListener by creating a subclass of
WindowAdapter and providing definitions for just those methods that you
actually need. In the HighLowFrame class, I need a listener to respond to
the windowClosing event that is generated when the user clicks the close box.
The listener is created in the constructor using an anonymous subclass of WindowAdapter
with a command of the form:
addWindowListener( new WindowAdapter() {
// This window listener responds when the user
// clicks the window's close box by giving the
// user a chance to change his mind.
public void windowClosing(WindowEvent evt) {
.
. // Show the dialog box, and respond to it.
.
}
});
Another window listener is used in the little applet that appears on
this page as the "Launch HighLow" button, above. This applet is defined
in the file HighLowLauncher.java.
Note that when you click on the button to open the frame, the name
of the button changes to "Close HighLow". You can close the frame
by clicking the button again, as well as by clicking the frame's close box.
When the frame is closed, for either reason, the name of the button
changes back to "Launch HighLow". The question is, how does the applet
know to change the button's name, when the user closes the frame by
clicking its close box? That doesn't seem to have anything to
do with the applet. The trick is to use an event listener. When
the applet creates the frame, it also creates a WindowListener
and registers it with the frame. This WindowListener is
programmed to respond to the windowClosed event by
changing the name of the button. This is a nice example of the sort
of communication between objects that can be done with events.
You can check the source code to see exactly how it's done.
Images in Applications
In Section 1, we saw how to load an image file
into an applet. The JApplet class has a method, getImage(),
that can be used for this purpose. The JFrame class does not
provide this method, so some other technique is needed for using
images in frames.
The standard class java.awt.Toolkit makes it possible to
load an image into a stand-alone application. A Toolkit object
has a getImage() method for reading an Image from an
image file. To use this method, you must first obtain a Toolkit,
and you have to do this by calling the static method Toolkit.getDefaultToolkit().
(Any running GUI program already has a toolkit, which is used to
perform various platform-dependent functions. You don't need to construct
a new toolkit. Toolkit.getDefaultToolkit()
returns a reference to the toolkit that already exists.) Once you
have a toolkit, you can use its getImage() method to create
an Image object from a file. This getImage method
takes one parameter, which specifies the name of the file. For example:
Toolkit toolkit = getDefaultToolkit();
Image cards = toolkit.getImage( "smallcards.gif" );
or, in one line,
Image cards = Toolkit.getDefaultToolkit().getImage( "smallcards.gif" );
Once the Image has been created, it can be used in the same
way, whether it has been created by an applet or a standalone application.
Note that an applet's getImage() method is used to load an image from a
Web server, while a Toolkit's getImage() loads an image from the
same computer on which the program is running. An applet cannot, in general, use
a Toolkit to load an image, since, for security reasons, an applet is not
usually allowed to read files from the computer on which it is running.
The HighLowFrame example uses an image file named "smallcards.gif"
for the cards that it displays. I designed HighLowFrame with a main()
routine so that it can be run as a stand-alone application. (When it is run as an
application, the "smallcards.gif" file must be in the same directory with the class
files.) But a HighLowFrame can also be opened by an applet, as is done in
the example on this page. When it is run as an application, the image file
must be loaded by a Toolkit. When it is opened by an applet, the
image file must be loaded by the getImage() method of the applet.
How can this be handled? I decided to do it by making the Image
object a parameter to the HighLowFrame constructor. The Image
must be loaded before the frame is constructed, so that it can be passed as a parameter
to the constructor. The main() routine in HighLowFrame does
this using a Toolkit:
Image cards = Toolkit.getDefaultToolkit().getImage("smallcards.gif");
HighLowFrame game = new HighLowFrame(cards);
The HighLowLauncher applet, on the other hand, loads the image
with its own getImage() method:
Image cards = getImage(getCodeBase(),"smallcards.gif");
highLow = new HighLowFrame(cards);
Dialogs
In addition to JFrame, there is another type of window
in Swing. A dialog box is a type of window
that is generally used for short, single purpose interactions with the user.
For example, a dialog box can be used to display a message to the user, to
ask the user a question, or to let the user select a color. In Swing,
a dialog box is represented by an object belonging to the class JDialog.
Like a frame, a dialog box is a separate
window. Unlike a frame, however, a dialog box is not completely
independent. Every dialog box is associated with a frame (or another dialog box), which is
called its parent. The dialog box is
dependent on its parent. For example, if the parent is closed,
the dialog box will also be closed. It is possible to create a dialog
box without specifying a parent, but in that case a default frame
is used or an invisible frame is created to serve as the parent.
Dialog boxes can be either modal
or modeless. When a modal dialog is
created, its parent frame is blocked. That is, the user will not be
able to interact with the parent until the dialog box is closed. Modeless dialog boxes
do not block their parents in the same way, so they seem a lot more
like independent windows. In practice, modal dialog boxes are
easier to use and are much more common than modeless dialogs. All the examples
we will look at are modal.
Aside from having a parent, a JDialog can be created and used
in the same way as a JFrame. However, we will not be using JDialog
directly. Swing has many convenient methods for creating many common types
of dialog boxes. For example, the JColorChooser class has the
static method:
Color JColorChooser.showDialog(JFrame parent, Color initialColor)
When you call this method, a dialog box appears that allows the user to
select a color. The first parameter specifies the parent of the dialog,
or it can be null. When the dialog first appears, initialColor is
selected. The dialog has a sophisticated interface that allows the user
to change the selection. When the user presses an "OK" button,
the dialog box closes and the selected
color is returned as the value of the method. The user can also click
a "Cancel" button or close the dialog box in some other way; in that case,
null is returned as the value of the method. By using this
predefined color chooser dialog, you can write one line of code that
will let the user select an arbitrary color.
The following applet demonstrates a JColorChooser dialog and
three other, simpler standard dialog boxes. When you click one of the
buttons, a dialog box appears. The label at the top of the applet
gives you some feedback about what is happening:
The three simple dialogs in this applet are created by static methods
in the class JOptionPane. This class includes many methods for
making dialog boxes, but they are all variations on the three basic
types shown here: a "message" dialog, a "confirm" dialog, and an "input"
dialog. (The variations allow you to provide a title for the dialog box,
to specify the icon that appears in the dialog, and to add other
components to the dialog box. I will only cover the most basic forms
here.)
A message dialog simply displays a message string to the user.
The user (hopefully) reads the message and dismisses the dialog by clicking the "OK" button.
A message dialog can be shown by calling the method:
void JOptionPane.showMessageDialog(JFrame parent, String message)
The parent, as usual, can be null. The message can be more than
one line long. Lines in the message should be separated by newline characters, \n.
New lines will not be inserted automatically, even if the message is very long.
An input dialog displays a question or request and lets the user type in
a string as a response. You can show an input dialog by calling:
String JOptionPane.showInputDialog(JFrame parent, String question)
Again, the parent can be null, and the question can include newline characters.
The dialog box will contain an input box and an "OK" button and a "Cancel" button.
If the user clicks "Cancel", or closes
the dialog box in some other way, then the return value of the method
is null. If the user clicks "OK", then the return value is the
string that was entered by the user. Note that the return value can be
an empty string (which is not the same as a null value),
if the user clicks "OK" without typing anything in the input box. If you want
to use an input dialog to get a numerical value from the user, you will
have to convert the return value into a number. A technique for doing this
can be found in the first example in Section 4.
Finally, a confirm dialog presents a question and three response buttons:
"Yes", "No", and "Cancel". A confirm dialog can be shown by calling:
int JOptionPane.showConfirmDialog(JFrame parent, String question)
The return value tells you the user's response. It
is one of the following constants:
- JOptionPane.YES_OPTION -- the user clicked the "Yes" button
- JOptionPane.NO_OPTION -- the user clicked the "No" button
- JOptionPane.CANCEL_OPTION -- the user clicked the "Cancel" button
- JOptionPane.CLOSE_OPTION -- the user closed the dialog in some other way.
I use a confirm dialog in the HighLowFrame example, earlier on this page.
When the user clicks the close box of a HighLowFrame, I display a
confirm dialog to ask whether the user really wants to quit. The frame will only be
closed if the return value is JOptionPane.YES_OPTION.
By the way, it is possible to omit the Cancel button from a confirm dialog by
calling one of the other methods in the JOptionPane class. Just
call:
JOptionPane.showConfirmDialog(
parent, question, title, JOptionPane.YES_NO_OPTION )
The final parameter is a constant which specifies that only a "Yes" button and
a "No" button should be used. The third parameter is a string that will be
displayed as the title of the dialog box window.
If you would like to see how dialogs are created and used in the sample
applet, you can find the source code in the file SimpleDialogDemo.java.
End of Chapter 7