Windowed
applications
It’s
possible to see that for safety’s sake you can have only limited behavior
within an applet. In a real sense, the applet is a temporary extension to the
Web browser so its functionality must be limited along with its knowledge and
control. There are times, however, when you’d like to make a windowed
program do something else than sit on a Web page, and perhaps you’d like
it to do some of the things a “regular” application can do and yet
have the vaunted instant portability provided by Java. In previous chapters in
this book we’ve made command-line applications, but in some operating
environments (the Macintosh, for example) there isn’t a command line. So
for any number of reasons you’d like to build a windowed, non-applet
program using Java. This is certainly a reasonable desire.
A
Java windowed application can have menus and dialog boxes (impossible or
difficult with an applet), and yet if you’re using an older version of
Java you sacrifice the native operating environment’s look and feel. The
JFC/Swing library allows you to make an application that preserves the look and
feel of the underlying operating environment. If you want to build windowed
applications, it makes sense to do so only if you can use the latest version of
Java and associated tools so you can deliver applications that won’t
confound your users. If for some reason you’re forced to use an older
version of Java, think hard before committing to building a significant
windowed application.
Menus
It’s
impossible to put a menu directly on an applet (in Java 1.0 and Java 1.1; the
Swing library
does
allow
it), so they’re for applications. Go ahead, try it if you don’t
believe me and you’re sure that it would make sense to have menus on
applets. There’s no setMenuBar( )
method in
Applet
and that’s the way a menu is attached. (You’ll see later that
it’s possible to spawn a
Frame
from within an
Applet,
and the
Frame
can contain menus.)
There
are four different types of MenuComponent,
all derived from that abstract class: MenuBar
(you can have one
MenuBar
only on a particular Frame),
Menu
to hold one individual drop-down menu or submenu, MenuItem
to represent one single element on a menu, and CheckboxMenuItem,
which is derived from
MenuItem
and produces a checkmark to indicate whether that menu item is selected.
Unlike
a system that uses resources, with Java and the AWT you must hand assemble all
the menus in source code. Here are the ice cream flavors again, used to create
menus:
//: Menu1.java
// Menus work only with Frames.
// Shows submenus, checkbox menu items
// and swapping menus.
import java.awt.*;
public class Menu1 extends Frame {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
TextField t = new TextField("No flavor", 30);
MenuBar mb1 = new MenuBar();
Menu f = new Menu("File");
Menu m = new Menu("Flavors");
Menu s = new Menu("Safety");
// Alternative approach:
CheckboxMenuItem[] safety = {
new CheckboxMenuItem("Guard"),
new CheckboxMenuItem("Hide")
};
MenuItem[] file = {
new MenuItem("Open"),
new MenuItem("Exit")
};
// A second menu bar to swap to:
MenuBar mb2 = new MenuBar();
Menu fooBar = new Menu("fooBar");
MenuItem[] other = {
new MenuItem("Foo"),
new MenuItem("Bar"),
new MenuItem("Baz"),
};
Button b = new Button("Swap Menus");
public Menu1() {
for(int i = 0; i < flavors.length; i++) {
m.add(new MenuItem(flavors[i]));
// Add separators at intervals:
if((i+1) % 3 == 0)
m.addSeparator();
}
for(int i = 0; i < safety.length; i++)
s.add(safety[i]);
f.add(s);
for(int i = 0; i < file.length; i++)
f.add(file[i]);
mb1.add(f);
mb1.add(m);
setMenuBar(mb1);
t.setEditable(false);
add("Center", t);
// Set up the system for swapping menus:
add("North", b);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b)) {
MenuBar m = getMenuBar();
if(m == mb1) setMenuBar(mb2);
else if (m == mb2) setMenuBar(mb1);
}
else if(evt.target instanceof MenuItem) {
if(arg.equals("Open")) {
String s = t.getText();
boolean chosen = false;
for(int i = 0; i < flavors.length; i++)
if(s.equals(flavors[i])) chosen = true;
if(!chosen)
t.setText("Choose a flavor first!");
else
t.setText("Opening "+ s +". Mmm, mm!");
}
else if(evt.target.equals(file[1]))
System.exit(0);
// CheckboxMenuItems cannot use String
// matching; you must match the target:
else if(evt.target.equals(safety[0]))
t.setText("Guard the Ice Cream! " +
"Guarding is " + safety[0].getState());
else if(evt.target.equals(safety[1]))
t.setText("Hide the Ice Cream! " +
"Is it cold? " + safety[1].getState());
else
t.setText(arg.toString());
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Menu1 f = new Menu1();
f.resize(300,200);
f.show();
}
} ///:~
In
this program I avoided the typical long lists of
add( )
calls for each menu because that seemed like a lot of unnecessary typing.
Instead, I placed the menu items into arrays and then simply stepped through
each array calling
add( )
in a
for
loop. This makes adding or subtracting a menu item less tedious.
As
an alternative approach (which I find less desirable since it requires more
typing), the
CheckboxMenuItems
are created in an array of handles called
safety;
this is true for the arrays
file
and
other
as well.
This
program creates not one but two
MenuBars
to demonstrate that menu bars can be actively swapped while the program is
running. You can see how a
MenuBar
is made up of
Menus,
and each
Menu
is made up of
MenuItems,
CheckboxMenuItems,
or even other
Menus
(which produce submenus). When a
MenuBar
is assembled it can be installed into the current program with the
setMenuBar( )
method. Note that when the button is pressed, it checks to see which menu is
currently installed using
getMenuBar( ),
then puts the other menu bar in its place.
When
testing for “Open,” notice that spelling and capitalization are
critical, but Java signals no error if there is no match with
“Open.” This kind of string comparison is a clear source of
programming errors.
The
checking and un-checking of the menu items is taken care of automatically, but
dealing with CheckboxMenuItems can be a bit surprising since for some reason
they don’t allow string matching. (Although string matching isn’t a
good approach, this seems inconsistent.) So you can match only the target
object and not its label. As shown, the getState( )
method
can be used to reveal the state. You can also change the state of a
CheckboxMenuItem
with
setState( ). You
might think that one menu could reasonably reside on more than one menu bar.
This does seem to make sense because all you’re passing to the
MenuBar
add( )
method is a handle. However, if you try this, the behavior will be strange and
not what you expect. (It’s difficult to know if this is a bug or if they
intended it to work this way.)
This
example also shows what you need to do to create an application instead of an
applet. (Again, because an application can support menus and an applet cannot
directly have a menu.) Instead of inheriting from
Applet,
you inherit from
Frame.
Instead of
init( )
to set things up, you make a constructor for your class. Finally, you create a
main( )
and in that you build an object of your new type, resize it, and then call
show( ).
It’s different from an applet in only a few small places, but it’s
now a standalone
windowed application and you’ve got menus.
Dialog
boxes
A
dialog
box is a window that pops up out of another window. Its purpose is to deal with
some specific issue without cluttering the original window with those details.
Dialog boxes are heavily used in windowed programming environments, but as
mentioned previously, rarely used in applets.
To
create a dialog box, you inherit from Dialog,
which is just another kind of
Window,
like a
Frame.
Unlike a
Frame,
a
Dialog
cannot have a menu bar or change the cursor, but other than that they’re
quite similar. A dialog has a layout manager (which defaults to
BorderLayout)
and you override
action( )
etc., or
handleEvent( )
to deal with events. One significant difference you’ll want to note in
handleEvent( ):
when the WINDOW_DESTROY
event occurs, you don’t want to shut down the application! Instead, you
release the resources used by the dialog’s window by calling dispose( ). In
the following example, the dialog box is made up of a grid (using
GridLayout)
of a special kind of button that is defined here as class
ToeButton.
This button draws a frame around itself and, depending on its state, a blank,
an “x,” or an “o” in the middle. It starts out blank,
and then depending on whose turn it is, changes to an “x” or an
“o.” However, it will also flip back and forth between
“x” and “o” when you click on the button. (This makes
the tic-tac-toe concept only slightly more annoying than it already is.) In
addition, the dialog box can be set up for any number of rows and columns by
changing numbers in the main application window.
//: ToeTest.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;
class ToeButton extends Canvas {
int state = ToeDialog.BLANK;
ToeDialog parent;
ToeButton(ToeDialog parent) {
this.parent = parent;
}
public void paint(Graphics g) {
int x1 = 0;
int y1 = 0;
int x2 = size().width - 1;
int y2 = size().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2/4;
y1 = y2/4;
int wide = x2/2;
int high = y2/2;
if(state == ToeDialog.XX) {
g.drawLine(x1, y1, x1 + wide, y1 + high);
g.drawLine(x1, y1 + high, x1 + wide, y1);
}
if(state == ToeDialog.OO) {
g.drawOval(x1, y1, x1+wide/2, y1+high/2);
}
}
public boolean
mouseDown(Event evt, int x, int y) {
if(state == ToeDialog.BLANK) {
state = parent.turn;
parent.turn= (parent.turn == ToeDialog.XX ?
ToeDialog.OO : ToeDialog.XX);
}
else
state = (state == ToeDialog.XX ?
ToeDialog.OO : ToeDialog.XX);
repaint();
return true;
}
}
class ToeDialog extends Dialog {
// w = number of cells wide
// h = number of cells high
static final int BLANK = 0;
static final int XX = 1;
static final int OO = 2;
int turn = XX; // Start with x's turn
public ToeDialog(Frame parent, int w, int h) {
super(parent, "The game itself", false);
setLayout(new GridLayout(w, h));
for(int i = 0; i < w * h; i++)
add(new ToeButton(this));
resize(w * 50, h * 50);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
dispose();
else
return super.handleEvent(evt);
return true;
}
}
public class ToeTest extends Frame {
TextField rows = new TextField("3");
TextField cols = new TextField("3");
public ToeTest() {
setTitle("Toe Test");
Panel p = new Panel();
p.setLayout(new GridLayout(2,2));
p.add(new Label("Rows", Label.CENTER));
p.add(rows);
p.add(new Label("Columns", Label.CENTER));
p.add(cols);
add("North", p);
add("South", new Button("go"));
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(arg.equals("go")) {
Dialog d = new ToeDialog(
this,
Integer.parseInt(rows.getText()),
Integer.parseInt(cols.getText()));
d.show();
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Frame f = new ToeTest();
f.resize(200,100);
f.show();
}
} ///:~
The
ToeButton
class keeps a handle to its parent, which must be of type
ToeDialog.
As before, this introduces high coupling because a
ToeButton
can be used only with a
ToeDialog,
but it solves a number of problems, and in truth it doesn’t seem like
such a bad solution because there’s no other kind of dialog that’s
keeping track of whose turn it is. Of course, you can take another approach,
which is to make
ToeDialog.turn
a
static
member of
ToeButton.
This eliminates the coupling, but prevents you from having more than one
ToeDialog
at a time. (More than one that works properly, anyway.)
The
paint( )
method is concerned with the graphics:
drawing the square around the button and drawing the “x” or the
“o.” This is full of tedious calculations, but it’s
straightforward.
A
mouse click is captured by the overridden mouseDown( )
method, which first checks to see if the button has anything written on it. If
not, the parent window is queried to find out whose turn it is and that is used
to establish the state of the button. Note that the button then reaches back
into the parent and changes the turn. If the button is already displaying an
“x” or an “o” then that is flopped. You can see in
these calculations the convenient use of the ternary if-else described in
Chapter 3. After a button state change, the button is repainted.
The
constructor for
ToeDialog
is quite simple: it adds into a
GridLayout
as many buttons as you request, then resizes it for 50 pixels on a side for
each button. (If you don’t resize a
Window,
it won’t show up!) Note that
handleEvent( )
just calls
dispose( )
for a
WINDOW_DESTROY
so the whole application doesn’t go away.
ToeTest
sets up the whole application by creating the
TextFields
(for inputting the rows and columns of the button grid) and the
“go” button. You’ll see in
action( )
that this program uses the less-desirable “string match” technique
for detecting the button press (make sure you get spelling and capitalization
right!). When the button is pressed, the data in the
TextFields
must be fetched, and, since they are in
String
form, turned into
ints
using the
static
Integer.parseInt( )
method. Once the
Dialog
is created, the
show( )
method must be called to display and activate it.
You’ll
notice that the
ToeDialog
object is assigned to a
Dialog
handle
d.
This is an example of upcasting, although it really doesn’t make much
difference here since all that’s happening is the
show( )
method is called. However, if you wanted to call some method that existed only
in
ToeDialog
you would want to assign to a
ToeDialog
handle and not lose the information in an upcast.
File
dialogs
Some
operating systems have a number of special built-in dialog boxes to handle the
selection of things such as fonts, colors, printers, and the like. Virtually
all graphical operating systems support the opening and saving of files,
however, and so Java’s FileDialog
encapsulates these for easy use. This, of course, makes no sense at all to use
from an applet since an applet can neither read nor write files on the local
disk. (This will change for trusted applets in newer browsers.)
The
following application exercises the two forms of file dialogs, one for opening
and one for saving. Most of the code should by now be familiar, and all the
interesting activities happen in
action( )
for the two different button clicks:
//: FileDialogTest.java
// Demonstration of File dialog boxes
import java.awt.*;
public class FileDialogTest extends Frame {
TextField filename = new TextField();
TextField directory = new TextField();
Button open = new Button("Open");
Button save = new Button("Save");
public FileDialogTest() {
setTitle("File Dialog Test");
Panel p = new Panel();
p.setLayout(new FlowLayout());
p.add(open);
p.add(save);
add("South", p);
directory.setEditable(false);
filename.setEditable(false);
p = new Panel();
p.setLayout(new GridLayout(2,1));
p.add(filename);
p.add(directory);
add("North", p);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.WINDOW_DESTROY)
System.exit(0);
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(open)) {
// Two arguments, defaults to open file:
FileDialog d = new FileDialog(this,
"What file do you want to open?");
d.setFile("*.java"); // Filename filter
d.setDirectory("."); // Current directory
d.show();
String openFile;
if((openFile = d.getFile()) != null) {
filename.setText(openFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
else if(evt.target.equals(save)) {
FileDialog d = new FileDialog(this,
"What file do you want to save?",
FileDialog.SAVE);
d.setFile("*.java");
d.setDirectory(".");
d.show();
String saveFile;
if((saveFile = d.getFile()) != null) {
filename.setText(saveFile);
directory.setText(d.getDirectory());
} else {
filename.setText("You pressed cancel");
directory.setText("");
}
}
else
return super.action(evt, arg);
return true;
}
public static void main(String[] args) {
Frame f = new FileDialogTest();
f.resize(250,110);
f.show();
}
} ///:~
For
an “open file” dialog, you use the constructor that takes two
arguments; the first is the parent window handle and the second is the title
for the title bar of the
FileDialog.
The method setFile( )
provides an initial file name – presumably the native OS supports
wildcards, so in this example all the
.java
files will initially be displayed. The setDirectory( )
method chooses the directory where the file selection will begin. (In general,
the OS allows the user to change directories.)
The
show( )
command doesn’t return until the dialog is closed. The
FileDialog
object still exists, so you can read data from it. If you call getFile( )
and it returns
null
it means the user canceled out of the dialog. Both the file name and the
results of getDirectory( )
are displayed in the
TextFields. The
button for saving works the same way, except that it uses a different
constructor for the
FileDialog.
This constructor takes three arguments and the third argument must be either
FileDialog.SAVE
or
FileDialog.OPEN.