![]() |
James Thornton |
| Internet Business Consultant | Call Toll Free: 1 (800) 409-2501 |
| About James | My MySpace | Internet Marketing | Enron Loophole | Lock Bumping | Contact Me |
|---|
The original design goal
of the graphical user interface (GUI) library in Java
1.0 was to allow the programmer to build a GUI that
looks good on all platforms.
That
goal was not achieved. Instead, the Java 1.0
Abstract
Window Toolkit (AWT) produces a GUI that looks equally mediocre on all
systems. In addition it’s restrictive: you can use only four fonts and you
cannot access any of the more sophisticated GUI elements that exist in your
operating system (OS). The Java 1.0 AWT programming model is also awkward and
non-object-oriented.
Much of this situation has been
improved with the Java 1.1 AWT event model, which takes
a much clearer, object-oriented approach, along with the introduction of Java
Beans, a component programming model that is particularly oriented toward the
easy creation of visual programming environments. Java
1.2 finishes the transformation away from the old Java
1.0 AWT by adding the Java
Foundation Classes (JFC), the GUI portion of which is called
“Swing.” These are a rich set of
easy-to-use, easy-to-understand Java Beans that can be dragged and dropped (as
well as hand programmed) to create a GUI that you can (finally) be satisfied
with. The “revision 3” rule of the software industry (a product
isn’t good until revision 3) seems to hold true with programming languages
as well.
One of Java’s primary design
goals is to create applets, which are little programs that run inside a
Web browser. Because they must be safe, applets are limited in what they can
accomplish. However, they are a powerful tool in supporting client-side
programming, a major issue for the Web.
Programming within an applet is so
restrictive that it’s often referred to as being “inside the
sandbox,” since you always have someone – the Java run-time security
system – watching over you. Java 1.1 offers
digital signing for applets so you can choose to allow trusted applets to have
access to your machine. However, you can also step outside the sandbox and write
regular applications, in which case you can access the other features of your
OS. We’ve been writing regular applications all along in this book, but
they’ve been console applications without any graphical components.
The AWT can also be used to build GUI interfaces for regular
applications.
In this chapter you’ll first
learn the use of the original “old” AWT, which is still supported
and used by many of the code examples that you will come across. Although
it’s a bit painful to learn the old AWT, it’s necessary because you
must read and maintain legacy code that uses the old AWT. Sometimes you’ll
even need to write old AWT code to support environments that haven’t
upgraded past Java 1.0. In the second part of the
chapter you’ll learn about the structure of the “new” AWT in
Java 1.1 and see how much better the event model is. (If
you can, you should use the newest tools when you’re creating new
programs.) Finally, you’ll learn about the new JFC/Swing components, which
can be added to Java 1.1 as a library – this means you can use the library
without requiring a full upgrade to Java 1.2.
Most of the examples will show the
creation of applets, not only because it’s easier but also because
that’s where the AWT’s primary usefulness might reside. In addition
you’ll see how things are different when you want to create a regular
application using the AWT, and how to create programs that are both applets and
applications so they can be run either inside a browser or from the command
line.
Please be aware that this is not a
comprehensive glossary of all the methods for the described classes. This
chapter will just get you started with the essentials. When you’re looking
for more sophistication, make sure you go to your information browser to look
for the classes and methods that will solve your problem. (If you’re using
a development environment your information browser might be built in; if
you’re using the Sun JDK then you use your Web browser and start in the
java root directory.) Appendix F lists other resources for learning library
details.
One of the problems with the
“old” AWT that you’ll learn about in this chapter is that it
is a poor example of both object-oriented design and GUI development kit design.
It throws us back into the dark ages of programming (some suggest that the
‘A’ in AWT stands for “awkward,” “awful,”
“abominable,” etc.). You must write lines of code to do
everything, including tasks that are accomplished much more easily using
resources in other environments.
So why learn to
use the old AWT? “Because it’s there.” In this case,
“there” has a much more ominous meaning and points to a tenet of
object-oriented library design: Once you publicize a component in your
library, you can never take it out. If you do, you’ll wreck
somebody’s existing code. In addition, there are many existing code
examples out there that you’ll read as you learn about Java and they all
use the old AWT.
The AWT must reach into the GUI
components of the native OS, which means that it performs a task that an applet
cannot otherwise accomplish. An untrusted applet cannot make any direct calls
into an OS because otherwise it could do bad things to the user’s machine.
The only way an untrusted applet can access important functionality such as
“draw a window on the screen” is through calls in the standard Java
library that’s been specially ported and safety checked for that machine.
The original model that Sun created is that this “trusted library”
will be provided only by the trusted vendor of the Java system in your Web
browser, and the vendor will control what goes into that library.
But what if you want to extend the
system by adding a new component that accesses functionality in the OS? Waiting
for Sun to decide that your extension should be incorporated into the standard
Java library isn’t going to solve your problem. The new model in Java 1.1
is “trusted code” or “signed code” whereby a special
server verifies that a piece of code that you download is in fact
“signed” by the stated author using a public-key encryption system.
This way, you’ll know for sure where the code comes from, that it’s
Bob’s code and not just someone pretending to be Bob. This doesn’t
prevent Bob from making mistakes or doing something malicious, but it does
prevent Bob from shirking responsibility – anonymity is what makes
computer viruses possible. A digitally signed applet – a “trusted
applet” – in Java 1.1 can reach into your machine and
manipulate it directly, just like any other application you get from a
“trusted” vendor and install onto your computer.
But the point of all this is that
the old AWT is there. There will always be old AWT code floating around
and new Java programmers learning from old books will encounter that code. Also,
the old AWT is worth studying as an example of poor library design. The coverage
of the old AWT given here will be relatively painless since it won’t go
into depth and enumerate every single method and class, but instead give you an
overview of the old AWT design.
Libraries are often grouped
according to their functionality. Some libraries, for example, are used as is,
off the shelf. The standard Java library String and Vector classes
are examples of these. Other libraries are designed specifically as building
blocks to build other classes. A certain class of library is the
application framework,
whose goal is to help you build applications by providing a class or set of
classes that produces the basic behavior that you need in every application of a
particular type. Then, to customize the behavior to your own needs you inherit
from the application class and override the methods of interest. The application
framework’s default control mechanism will call your overridden methods at
the appropriate time. An application framework is a good example of
“separating the things that change from the things that stay the
same,” since it attempts to localize all the unique parts of a program in
the overridden methods.
Applets are
built using an application framework. You inherit from class Applet and
override the appropriate methods. Most of the time you’ll be concerned
with only a few important methods that have to do with how the applet is built
and used on a Web page. These methods are:
|
Method |
Operation |
|---|---|
|
init( ) |
Called when the applet is first
created to perform first-time initialization of the applet |
|
start( ) |
Called every time the applet moves
into sight on the Web browser to allow the applet to start up its normal
operations (especially those that are shut off by stop( )). Also
called after init( ). |
|
paint( ) |
Part of the base class
Component (three levels of inheritance up). Called as part of an
update( ) to perform special painting on the canvas of an
applet. |
|
stop( ) |
Called every time the applet moves
out of sight on the Web browser to allow the applet to shut off expensive
operations. Also called right before destroy( ). |
|
destroy( ) |
Called when the applet is being
unloaded from the page to perform final release of resources when the applet is
no longer used |
Consider the paint( )
method. This method is called automatically when the
Component (in this case,
the applet) decides that it needs to update itself – perhaps because
it’s being moved back onto the screen or placed on the screen for the
first time, or perhaps some other window had been temporarily placed over your
Web browser. The applet calls its
update( ) method
(defined in the base class Component), which goes about restoring
everything, and as a part of that restoration calls paint( ). You
don’t have to override paint( ), but it turns out to be an
easy way to make a simple applet, so we’ll start out with
paint( ).
When update( ) calls
paint( ) it hands it a handle to a
Graphics object that
represents the surface on which you can paint. This is important because
you’re limited to the surface of that particular component and thus cannot
paint outside that area, which is a good thing or else you’d be painting
outside the lines. In the case of an applet, the surface is the area inside the
applet.
The Graphics object also has
a set of operations you can perform on it. These operations revolve around
painting on the canvas, so most of them have to do with drawing images, shapes,
arcs, etc. (Note that you can look all this up in your online Java documentation
if you’re curious.) There are some methods that allow you to draw
characters, however, and the most commonly used one is
drawString( ). For
this, you must specify the String you want to draw and its starting
location on the applet’s drawing surface. This location is given in
pixels, so it will look different on different machines, but at least it’s
portable.
With this information you can
create a simple applet:
//: Applet1.java
// Very simple applet
package c13;
import java.awt.*;
import java.applet.*;
public class Applet1 extends Applet {
public void paint(Graphics g) {
g.drawString("First applet", 10, 10);
}
} ///:~
Note that applets are not required
to have a main( ). That’s all wired in to the application
framework; you put any startup code in init( ).
To run this program you must place
it inside a Web page and view that page inside your Java-enabled Web browser. To
place an applet inside a Web
page you put a special tag inside the HTML source for that Web
page[51] to
tell the page how to load and run the applet. This is the applet
tag, and it looks like this for
Applet1:
<applet code=Applet1 width=200 height=200> </applet>
The code value gives the
name of the .class file where the applet resides. The width and
height specify the initial size of the applet (in pixels, as before).
There are other items you can place within the applet tag: a place to find other
.class files on the Internet
(codebase), alignment
information (align), a
special identifier that makes it possible for applets to communicate with each
other
(name),
and applet
parameters to provide
information that the applet can retrieve. Parameters are in the
form
<param name=identifier value = "information">
and there can be as many as you
want.
For simple applets all you need to
do is place an applet tag in the above form inside your Web page and that will
load and run the applet.
You can perform a simple test
without any network connection by starting up your Web browser and opening the
HTML file containing the applet tag. (Sun’s JDK also contains a tool
called the appletviewer that picks the
<APPLET> tags out of the HTML file and runs the applets without displaying
the surrounding HTML
text.[52]) As
the HTML file is loaded, the browser will discover the applet tag and go hunt
for the .class file specified by the code value. Of course, it
looks at the CLASSPATH to find out where to hunt, and if your .class file
isn’t in the CLASSPATH then it will give an error message on the status
line of the browser to the effect that it couldn’t find that .class
file.
When you want to try this out on
your Web site things are a little more complicated. First of all, you must
have a Web site, which for most people means a third-party
Internet Service Provider (ISP)
at a remote location. Then you must have a way to move the HTML files and the
.class files from your site to the correct directory (your WWW directory)
on the ISP machine. This is typically done with a
File Transfer Protocol (FTP)
program, of which there are many different types freely available. So it would
seem that all you need to do is move the files to the ISP machine with FTP, then
connect to the site and HTML file using your browser; if the applet comes up and
works, then everything checks out, right?
Here’s where you can get
fooled. If the browser cannot locate the .class file on the server, it
will hunt through the CLASSPATH
on your local machine. Thus, the applet might not be loading properly
from the server, but to you it looks fine because the browser finds it on your
machine. When someone else logs in, however, his or her browser can’t find
it. So when you’re testing, make sure you erase the relevant .class
files on your machine to be safe.
One of the most insidious places
where this happened to me is when I innocently placed an applet inside a
package. After uploading the HTML file and applet, it turned out that the
server path to the applet was confused because of the package name. However, my
browser found it in the local CLASSPATH. So I was the only one who could
properly load the applet. It took some time to discover that the package
statement was the culprit. In general, you’ll want to leave the
package statement out of
an applet.
The example above isn’t too
thrilling, so let’s try adding a slightly more interesting graphic
component:
//: Applet2.java
// Easy graphics
import java.awt.*;
import java.applet.*;
public class Applet2 extends Applet {
public void paint(Graphics g) {
g.drawString("Second applet", 10, 15);
g.draw3DRect(0, 0, 100, 20, true);
}
} ///:~
This puts a box around the string.
Of course, all the numbers are hard-coded and are based on pixels, so on some
machines the box will fit nicely around the string and on others it will
probably be off, because fonts will be different on different
machines.
There are other interesting things
you can find in the documentation for the Graphic class. Any sort of
graphics activity is usually entertaining, so further experiments of this sort
are left to the reader.
It’s interesting to see some
of the framework methods in action. (This example will look only at
init( ),
start( ), and
stop( ) because
paint( ) and destroy( ) are self-evident and not so
easily traceable.) The following applet keeps track of the number of times these
methods are called and displays them using
paint( ):
//: Applet3.java
// Shows init(), start() and stop() activities
import java.awt.*;
import java.applet.*;
public class Applet3 extends Applet {
String s;
int inits = 0;
int starts = 0;
int stops = 0;
public void init() { inits++; }
public void start() { starts++; }
public void stop() { stops++; }
public void paint(Graphics g) {
s = "inits: " + inits +
", starts: " + starts +
", stops: " + stops;
g.drawString(s, 10, 10);
}
} ///:~
Normally when you override a method
you’ll want to look to see whether you need to call the base-class version
of that method, in case it does something important. For example, with
init( ) you might need to call super.init( ). However,
the Applet documentation specifically states that the
init( ), start( ), and stop( ) methods in
Applet do nothing, so it’s not necessary to call them
here.
When you experiment with this
applet you’ll discover that if you minimize the Web browser or cover it up
with another window you might not get calls to stop( ) and
start( ). (This behavior seems to vary among implementations;
you might wish to contrast the behavior of Web browsers with that of applet
viewers.) The only time the calls will occur is when you move to a different Web
page and then come back to the one containing the
applet.
Making a button is quite simple:
you just call the Button
constructor with the label you want on the button. (You can also use the default
constructor if you want a button with no label, but this is not very useful.)
Usually you’ll want to create a handle for the button so you can refer to
it later.
The Button is a component,
like its own little window, that will automatically get repainted as part of an
update. This means that you don’t explicitly paint a button or any other
kind of control; you simply place them on the form and let them automatically
take care of painting themselves. So to place a button on a form you override
init( ) instead of overriding paint( ):
//: Button1.java
// Putting buttons on an applet
import java.awt.*;
import java.applet.*;
public class Button1 extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public void init() {
add(b1);
add(b2);
}
} ///:~
It’s not enough to create the
Button (or any other control). You must also call the Applet
add( ) method to cause the button to be placed on the applet’s
form. This seems a lot simpler than it is, because the call to
add( ) actually decides, implicitly, where to place the control on
the form. Controlling the layout of a form is examined
shortly.
You’ll notice that if you
compile and run the applet above, nothing happens when you press the buttons.
This is where you must step in and write some code to determine what will
happen. The basis of
event-driven
programming, which comprises a lot of what a GUI is about, is tying events to
code that responds to those events.
After working your way this far
through the book and grasping some of the fundamentals of object-oriented
programming, you might think that of course there will be some sort of
object-oriented approach to handling events. For example, you might have to
inherit each button and override some “button pressed” method (this,
it turns out, is too tedious and restrictive). You might also think
there’s some master “event” class that contains a method for
each event you want to respond to.
Before objects, the typical
approach to handling events was the “giant switch statement.” Each
event would have a unique integer value and inside the master event handling
method you’d write a switch on that value.
The AWT in Java
1.0 doesn’t use any object-oriented approach.
Neither does it use a giant switch statement that relies on the
assignment of numbers to events. Instead, you must create a cascaded set of
if statements. What you’re trying to do with the if
statements is detect the object that was the
target of the event. That
is, if you click on a button, then that particular button is the target.
Normally, that’s all you care about – if a button is the target of
an event, then it was most certainly a mouse click and you can continue based on
that assumption. However, events can contain other information as well. For
example, if you want to find out the pixel location where a mouse click occurred
so you can draw a line to that location, the Event object will contain
the location. (You should also be aware that Java 1.0 components can be limited
in the kinds of events they generate, while Java 1.1 and Swing/JFC components
produce a full set of events.)
The Java
1.0 AWT method where your cascaded if statement
resides is called
action( ). Although
the whole Java 1.0 Event model has been deprecated in
Java 1.1, it is still widely used for simple applets and
in systems that do not yet support Java 1.1, so I recommend you become
comfortable with it, including the use of the following action() method
approach.
action( ) has two
arguments: the first is of type
Event and contains all
the information about the event that triggered this call to
action( ). For example, it could be a mouse click, a normal keyboard
press or release, a special key press or release, the fact that the component
got or lost the focus, mouse movements, or drags, etc. The second argument is
usually the target of the event, which you’ll often ignore. The second
argument is also encapsulated in the Event object so it is redundant as
an argument.
The situations in which
action( ) gets called are extremely limited: When you place controls
on a form, some types of controls (buttons, check boxes, drop-down lists, menus)
have a “standard action” that occurs, which causes the call to
action( ) with the appropriate Event object. For example,
with a button the action( ) method is called when the button is
pressed and at no other time. Usually this is just fine, since that’s what
you ordinarily look for with a button. However, it’s possible to deal with
many other types of events via the
handleEvent( )
method as we will see later in this chapter.
The previous example can be
extended to handle button clicks as follows:
//: Button2.java
// Capturing button presses
import java.awt.*;
import java.applet.*;
public class Button2 extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public void init() {
add(b1);
add(b2);
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b1))
getAppletContext().showStatus("Button 1");
else if(evt.target.equals(b2))
getAppletContext().showStatus("Button 2");
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
} ///:~
To see what the
target is, ask the Event
object what its target member is and then use the
equals( ) method to see if it matches the
target object handle you’re interested in. When you’ve written
handlers for all the objects you’re interested in you must call
super.action(evt, arg) in
the else statement at the end, as shown above. Remember from Chapter 7
(polymorphism) that your overridden method is called instead of the base class
version. However, the base-class version contains code to handle all of the
cases that you’re not interested in, and it won’t get called unless
you call it explicitly. The return value indicates whether you’ve handled
it or not, so if you do match an event you should return true, otherwise
return whatever the base-class event( ) returns.
For this example, the simplest
action is to print what button is pressed. Some systems allow you to pop up a
little window with a message in it, but applets discourage this. However, you
can put a message at the bottom of the
Web
browser window on its status line by calling the Applet method
getAppletContext( )
to get access to the browser and then
showStatus( ) to put
a string on the status
line.[53] You
can print out a complete description of an event the same way, with
getAppletContext().showStatus(evt + "" ). (The empty String forces
the compiler to convert evt to a String.) Both of these reports
are really useful only for testing and debugging since the browser might
overwrite your message.
Strange as it might seem, you can
also match an event to the
text that’s on a button through the second argument in
event( ). Using this technique, the example above
becomes:
//: Button3.java
// Matching events on button text
import java.awt.*;
import java.applet.*;
public class Button3 extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public void init() {
add(b1);
add(b2);
}
public boolean action (Event evt, Object arg) {
if(arg.equals("Button 1"))
getAppletContext().showStatus("Button 1");
else if(arg.equals("Button 2"))
getAppletContext().showStatus("Button 2");
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
} ///:~
It’s difficult to know
exactly what the equals( ) method is doing here. The biggest problem
with this approach is that most new Java programmers who start with this
technique spend at least one frustrating session discovering that they’ve
gotten the capitalization or spelling wrong when comparing to the text on a
button. (I had this experience.) Also, if you change the text of the button, the
code will no longer work (but you won’t get any compile-time or run-time
error messages). You should avoid this approach if
possible.
A
TextField is a one line
area that allows the user to enter and edit text. TextField is inherited
from TextComponent,
which lets you select text, get the selected text as a String, get or
set the text, and set whether the TextField is editable, along with other
associated methods that you can find in your online reference. The following
example demonstrates some of the functionality of a TextField; you can
see that the method names are fairly obvious:
//: TextField1.java
// Using the text field control
import java.awt.*;
import java.applet.*;
public class TextField1 extends Applet {
Button
b1 = new Button("Get Text"),
b2 = new Button("Set Text");
TextField
t = new TextField("Starting text", 30);
String s = new String();
public void init() {
add(b1);
add(b2);
add(t);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(b1)) {
getAppletContext().showStatus(t.getText());
s = t.getSelectedText();
if(s.length() == 0) s = t.getText();
t.setEditable(true);
}
else if(evt.target.equals(b2)) {
t.setText("Inserted by Button 2: " + s);
t.setEditable(false);
}
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
} ///:~
There are several ways to construct
a TextField; the one shown here provides an initial string and sets the
size of the field in characters.
Pressing button 1 either gets the
text you’ve selected with the mouse or it gets all the text in the field
and places the result in String s. It also allows the field to be edited.
Pressing button 2 puts a message and s into the text field and prevents
the field from being edited (although you can still select the text). The
editability of the text is controlled by passing
setEditable( ) a
true or
false.
A
TextArea is like a
TextField except that it can have multiple lines and has significantly
more functionality. In addition to what you can do with a TextField, you
can append text and insert or replace text at a given location. It seems like
this functionality could be useful for TextField as well, so it’s a
little confusing to try to detect how the distinction is made. You might think
that if you want TextArea functionality everywhere you can simply use a
one line TextArea in places where you would otherwise use a
TextField. In Java 1.0, you also got scroll bars
with a TextArea even when they weren’t appropriate; that is, you
got both vertical and horizontal scroll bars for a one line TextArea. In
Java 1.1 this was remedied with an extra constructor
that allows you to select which scroll bars (if any) are present. The following
example shows only the Java 1.0 behavior, in which the
scrollbars are always on. Later in the chapter you’ll see an example that
demonstrates Java 1.1 TextAreas.
//: TextArea1.java
// Using the text area control
import java.awt.*;
import java.applet.*;
public class TextArea1 extends Applet {
Button b1 = new Button("Text Area 1");
Button b2 = new Button("Text Area 2");
Button b3 = new Button("Replace Text");
Button b4 = new Button("Insert Text");
TextArea t1 = new TextArea("t1", 1, 30);
TextArea t2 = new TextArea("t2", 4, 30);
public void init() {
add(b1);
add(t1);
add(b2);
add(t2);
add(b3);
add(b4);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(b1))
getAppletContext().showStatus(t1.getText());
else if(evt.target.equals(b2)) {
t2.setText("Inserted by Button 2");
t2.appendText(": " + t1.getText());
getAppletContext().showStatus(t2.getText());
}
else if(evt.target.equals(b3)) {
String s = " Replacement ";
t2.replaceText(s, 3, 3 + s.length());
}
else if(evt.target.equals(b4))
t2.insertText(" Inserted ", 10);
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
} ///:~
There are several different
TextArea constructors, but the one shown here gives a starting string and
the number of rows and columns. The different buttons show getting, appending,
replacing, and inserting
text.
A
Label does exactly what
it sounds like it should: places a label on the form. This is particularly
important for text fields and text areas that don’t have labels of their
own, and can also be useful if you simply want to place textual information on a
form. You can, as shown in the first example in this chapter, use
drawString( ) inside paint( ) to place text in an exact
location. When you use a Label it allows you to (approximately) associate
the text with some other component via the layout manager (which will be
discussed later in this chapter).
With the constructor you can create
a blank label, a label with initial text in it (which is what you’ll
typically do), and a label with an alignment of CENTER, LEFT, or
RIGHT (static final ints defined in class Label). You can
also change the label and its alignment with
setText( ) and
setAlignment( ), and
if you’ve forgotten what you’ve set these to you can read the values
with getText( ) and
getAlignment( ).
This example shows what you can do with labels:
//: Label1.java
// Using labels
import java.awt.*;
import java.applet.*;
public class Label1 extends Applet {
TextField t1 = new TextField("t1", 10);
Label labl1 = new Label("TextField t1");
Label labl2 = new Label(" ");
Label labl3 = new Label(" ",
Label.RIGHT);
Button b1 = new Button("Test 1");
Button b2 = new Button("Test 2");
public void init() {
add(labl1); add(t1);
add(b1); add(labl2);
add(b2); add(labl3);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(b1))
labl2.setText("Text set into Label");
else if(evt.target.equals(b2)) {
if(labl3.getText().trim().length() == 0)
labl3.setText("labl3");
if(labl3.getAlignment() == Label.LEFT)
labl3.setAlignment(Label.CENTER);
else if(labl3.getAlignment()==Label.CENTER)
labl3.setAlignment(Label.RIGHT);
else if(labl3.getAlignment() == Label.RIGHT)
labl3.setAlignment(Label.LEFT);
}
else
return super.action(evt, arg);
return true;
}
} ///:~
The first use of the label is the
most typical: labeling a TextField or TextArea. In the second part
of the example, a bunch of empty spaces are reserved and when you press the
“Test 1” button setText( ) is used to insert text into
the field. Because a number of blank spaces do not equal the same number of
characters (in a
proportionally-spaced
font) you’ll see that the text gets truncated when inserted into the
label.
The third part of the example
reserves empty space, then the first time you press the “Test 2”
button it sees that there are no characters in the label (since
trim( ) removes all of the blank spaces at
each end of a String) and inserts a short label, which is initially
left-aligned. The rest of the times you press the button it changes the
alignment so you can see the effect.
You might think that you could
create an empty label and then later put text in it with setText( ).
However, you cannot put text into an empty label – presumably because it
has zero width – so creating a label with no text seems to be a useless
thing to do. In the example above, the “blank” label is filled with
empty spaces so it has enough width to hold text that’s placed inside
later.
Similarly, setAlignment( )
has no effect on a label that you’d typically create with text in the
constructor. The label width is the width of the text, so changing the alignment
doesn’t do anything. However, if you start with a long label and then
change it to a shorter one you can see the effect of the
alignment.
These behaviors occur because of
the default layout
manager that’s used for applets, which causes things to be squished
together to their smallest size. Layout managers will be covered later in this
chapter, when you’ll see that other layouts don’t have the same
effect.
A check box provides a way to make
a single on-off choice; it consists of a tiny box and a label. The box typically
holds a little ‘x’ (or some other indication that it is set) or is
empty depending on whether that item was selected.
You’ll normally create a
Checkbox using a
constructor that takes the label as an argument. You can get and set the state,
and also get and set the label if you want to read or change it after the
Checkbox has been created. Note that the capitalization of
Checkbox is inconsistent with the other controls, which could catch you
by surprise since you might expect it to be
“CheckBox.”
Whenever a Checkbox is set
or cleared an event occurs, which you can capture the same way you do a button.
The following example uses a TextArea to enumerate all the check boxes
that have been checked:
//: CheckBox1.java
// Using check boxes
import java.awt.*;
import java.applet.*;
public class CheckBox1 extends Applet {
TextArea t = new TextArea(6, 20);
Checkbox cb1 = new Checkbox("Check Box 1");
Checkbox cb2 = new Checkbox("Check Box 2");
Checkbox cb3 = new Checkbox("Check Box 3");
public void init() {
add(t); add(cb1); add(cb2); add(cb3);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(cb1))
trace("1", cb1.getState());
else if(evt.target.equals(cb2))
trace("2", cb2.getState());
else if(evt.target.equals(cb3))
trace("3", cb3.getState());
else
return super.action(evt, arg);
return true;
}
void trace(String b, boolean state) {
if(state)
t.appendText("Box " + b + " Set\n");
else
t.appendText("Box " + b + " Cleared\n");
}
} ///:~
The trace( ) method
sends the name of the selected Checkbox and its current state to the
TextArea using
appendText( ) so
you’ll see a cumulative list of the checkboxes that were selected and what
their state
is.
The concept of a
radio
button in GUI programming comes from pre-electronic car radios with mechanical
buttons: when you push one in, any other button that was pressed pops out. Thus
it allows you to force a single choice among many.
The AWT does not have a separate
class to represent the radio button; instead it reuses the
Checkbox. However, to put
the Checkbox in a radio button group (and to change its shape so
it’s visually different from an ordinary Checkbox) you must use a
special constructor that takes a
CheckboxGroup object as
an argument. (You can also call
setCheckboxGroup( )
after the Checkbox has been created.)
A CheckboxGroup has no
constructor argument; its sole reason for existence is to collect some
Checkboxes into a group of radio buttons. One of the Checkbox
objects must have its state set to true before you try to display the
group of radio buttons; otherwise you’ll get an exception at run time. If
you try to set more than one radio button to true then only the final one
set will be true.
Here’s a simple example of
the use of radio buttons. Note that you capture radio button events like all
others:
//: RadioButton1.java
// Using radio buttons
import java.awt.*;
import java.applet.*;
public class RadioButton1 extends Applet {
TextField t =
new TextField("Radio button 2", 30);
CheckboxGroup g = new CheckboxGroup();
Checkbox
cb1 = new Checkbox("one", g, false),
cb2 = new Checkbox("two", g, true),
cb3 = new Checkbox("three", g, false);
public void init() {
t.setEditable(false);
add(t);
add(cb1); add(cb2); add(cb3);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(cb1))
t.setText("Radio button 1");
else if(evt.target.equals(cb2))
t.setText("Radio button 2");
else if(evt.target.equals(cb3))
t.setText("Radio button 3");
else
return super.action(evt, arg);
return true;
}
} ///:~
To display the state, an text field
is used. This field is set to non-editable because it’s used only to
display data, not to collect it. This is shown as an alternative to using a
Label. Notice the text in the field is initialized to “Radio button
2” since that’s the initial selected radio button.
Like a group of radio buttons, a
drop-down
list is a way to force the user to select only one element from a group of
possibilities. However, it’s a much more compact way to accomplish this,
and it’s easier to change the elements of the list without surprising the
user. (You can change radio buttons dynamically, but that tends to be visibly
jarring).
Java’s
Choice box is not like
the combo box in Windows, which lets you select from a list or type in
your own selection. With a Choice box you choose one and only one element
from the list. In the following example, the Choice box starts with a
certain number of entries and then new entries are added to the box when a
button is pressed. This allows you to see some interesting behaviors in
Choice boxes:
//: Choice1.java
// Using drop-down lists
import java.awt.*;
import java.applet.*;
public class Choice1 extends Applet {
String[] description = { "Ebullient", "Obtuse",
"Recalcitrant", "Brilliant", "Somnescent",
"Timorous", "Florid", "Putrescent" };
TextField t = new TextField(30);
Choice c = new Choice();
Button b = new Button("Add items");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
c.addItem(description[count++]);
add(t);
add(c);
add(b);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(c))
t.setText("index: " + c.getSelectedIndex()
+ " " + (String)arg);
else if(evt.target.equals(b)) {
if(count < description.length)
c.addItem(description[count++]);
}
else
return super.action(evt, arg);
return true;
}
} ///:~
The TextField displays the
“selected index,” which is the sequence number of the currently
selected element, as well as the String representation of the second
argument of action( ), which is in this case the string that was
selected.
When you run this applet, pay
attention to the determination of the size of the Choice box: in Windows,
the size is fixed from the first time you drop down the list. This means that if
you drop down the list, then add more elements to the list, the elements will be
there but the drop-down list won’t get any
longer[54]
(you can scroll through the elements). However, if you add all the elements
before the first time the list is dropped down, then it will be sized correctly.
Of course, the user will expect to see the whole list when it’s dropped
down, so this behavior puts some significant limitations on adding elements to
Choice
boxes.
List boxes are significantly
different from Choice boxes, and not just in appearance. While a
Choice box drops down when you activate it, a
List occupies some fixed
number of lines on a screen all the time and doesn’t change. In addition,
a List allows multiple selection: if you click on more than one item the
original item stays highlighted and you can select as many as you want. If you
want to see the items in a list, you simply call
getSelectedItems( ),
which produces an array of String of the items that have been
selected. To remove an item from a group you have to click it
again.
A problem with a List is
that the default action is double clicking, not single clicking. A single click
adds or removes elements from the selected group and a double click calls
action( ). One way around this is to re-educate your user, which is
the assumption made in the following program:
//: List1.java
// Using lists with action()
import java.awt.*;
import java.applet.*;
public class List1 extends Applet {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
// Show 6 items, allow multiple selection:
List lst = new List(6, true);
TextArea t = new TextArea(flavors.length, 30);
Button b = new Button("test");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
lst.addItem(flavors[count++]);
add(t);
add(lst);
add(b);
}
public boolean action (Event evt, Object arg) {
if(evt.target.equals(lst)) {
t.setText("");
String[] items = lst.getSelectedItems();
for(int i = 0; i < items.length; i++)
t.appendText(items[i] + "\n");
}
else if(evt.target.equals(b)) {
if(count < flavors.length)
lst.addItem(flavors[count++], 0);
}
else
return super.action(evt, arg);
return true;
}
} ///:~
When you press the button it adds
items to the top of the list (because of the second argument 0 to
addItem( )). Adding elements to a List is more reasonable
than the Choice box because users expect to scroll a list box (for one
thing, it has a built-in scroll bar) but they don’t expect to have to
figure out how to get a drop-down list to scroll, as in the previous
example.
However, the only way for
action( ) to be called is through a double-click. If you need to
monitor other activities that the user is doing on your List (in
particular, single clicks) you must take an alternative
approach.
So far we’ve been using
action( ), but there’s another method that gets first crack at
everything:
handleEvent( ). Any
time an event happens, it happens “over” or “to” a
particular object. The handleEvent( ) method for that object is
automatically called and an
Event object is created
and passed to handleEvent( ). The default handleEvent( )
(which is defined in
Component, the base class
for virtually all the “controls” in the AWT) will call either
action( ), as
we’ve been using, or other similar methods to indicate mouse activity,
keyboard activity, or to indicate that the focus has moved. We’ll look at
those later in this chapter.
What if these other methods –
action( ) in particular – don’t satisfy your needs? In
the case of List, for example, what if you want to catch single mouse
clicks but action( ) responds to only double clicks? The solution is
to override handleEvent( ) for your applet, which after all is
derived from Applet and can therefore override any non-final
methods. When you override handleEvent( ) for the applet
you’re getting all the applet events before they are routed, so you cannot
just assume “This has to do with my button so I can assume it’s been
pressed,” since that’s true only for action( ). Inside
handleEvent( ) it’s possible that the button has the focus and
someone is typing to it. Whether it makes sense or not, those are events that
you can detect and act upon in handleEvent( ).
To modify the List example
so that it will react to single mouse clicks, the button detection will be left
in action( ) but the code to handle the List will be moved
into handleEvent( ) as follows:
//: List2.java
// Using lists with handleEvent()
import java.awt.*;
import java.applet.*;
public class List2 extends Applet {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
// Show 6 items, allow multiple selection:
List lst = new List(6, true);
TextArea t = new TextArea(flavors.length, 30);
Button b = new Button("test");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
lst.addItem(flavors[count++]);
add(t);
add(lst);
add(b);
}
public boolean handleEvent(Event evt) {
if(evt.id == Event.LIST_SELECT ||
evt.id == Event.LIST_DESELECT) {
if(evt.target.equals(lst)) {
t.setText("");
String[] items = lst.getSelectedItems();
for(int i = 0; i < items.length; i++)
t.appendText(items[i] + "\n");
}
else
return super.handleEvent(evt);
}
else
return super.handleEvent(evt);
return true;
}
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b)) {
if(count < flavors.length)
lst.addItem(flavors[count++], 0);
}
else
return super.action(evt, arg);
return true;
}
} ///:~
The example is the same as before
except for the addition of handleEvent( ). Inside, a check is made
to see whether a list selection or deselection has occurred. Now remember,
handleEvent( ) is being overridden for the applet, so this
occurrence could be anywhere on the form and it could be happening to another
list. Thus, you must also check to see what the target is. (Although in this
case there’s only one list on the applet so we could have made the
assumption that all list events must be about that list. This is bad practice
since it’s going to be a problem as soon as another list is added.) If the
list matches the one we’re interested in, the same code as before will do
the trick.
Note that the form for
handleEvent( ) is similar to action( ): if you
deal with a particular event you return true, but if you’re
not interested in any of the other events via handleEvent( ) you
must return
super.handleEvent(evt). This
is vital because if you don’t do this, none of the other event-handling
code will get called. For example, try commenting out the return
super.handleEvent(evt) in the code above. You’ll discover that
action( ) never gets called, certainly not what you want. For both
action( ) and handleEvent( ) it’s important to
follow the format above and always return the base-class version of the method
when you do not handle the event yourself (in which case you should return
true). (Fortunately, these kinds of bug-prone details are relegated to
Java 1.0. The new design in Java
1.1 that you will see later in the chapter eliminates
these kinds of issues.)
In Windows, a list box
automatically allows
multiple
selections if you hold down the shift key. This is nice because it allows the
user to choose a single or multiple selection rather than fixing it during
programming. You might think you’ll be clever and implement this yourself
by checking to see if the shift key is held down when a mouse click was made by
testing for evt.shiftDown( ). Alas, the design of the AWT stymies
you – you’d have to be able to know which item was clicked on if the
shift key wasn’t pressed so you could deselect all the rest and
select only that one. However, you cannot figure that out in Java
1.0. (Java 1.1 sends all mouse,
keyboard, and focus events to a List, so you’ll be able to
accomplish
this.)
The way that you place components
on a form in Java is probably different from any other GUI system you’ve
used. First, it’s all code; there are no “resources” that
control placement of components. Second, the way components are placed on a form
is controlled by a “layout manager” that
decides how the components lie based on the order that you
add( ) them. The
size, shape, and placement of components will be remarkably different from one
layout manager to another. In addition, the layout managers adapt to the
dimensions of your applet or application window, so if that window dimension is
changed (for example, in the HTML page’s applet specification) the size,
shape, and placement of the components could change.
Both the
Applet and
Frame classes are derived
from Container, whose job
it is to contain and display Components. (The Container is a
Component so it can also
react to events.) In Container, there’s a method called
setLayout( ) that
allows you to choose a different layout manager.
In this section we’ll explore
the various layout managers by placing buttons in them (since that’s the
simplest thing to do). There won’t be any capturing of button events since
this is just intended to show how the buttons are laid
out.
So far, all the applets that have
been created seem to have laid out their components using some mysterious
internal logic. That’s because the applet uses a default layout scheme:
the FlowLayout. This
simply “flows” the components onto the form, from left to right
until the top space is full, then moves down a row and continues flowing the
components.
Here’s an example that
explicitly (redundantly) sets the layout manager in an applet to
FlowLayout and then places buttons on the form. You’ll notice that
with FlowLayout the components take on their “natural” size.
A Button, for example,
will be the size of its string.
//: FlowLayout1.java
// Demonstrating the FlowLayout
import java.awt.*;
import java.applet.*;
public class FlowLayout1 extends Applet {
public void init() {
setLayout(new FlowLayout());
for(int i = 0; i < 20; i++)
add(new Button("Button " + i));
}
} ///:~
All components will be compacted to
their smallest size in a FlowLayout, so you might get a little bit of
surprising behavior. For example, a label will be the size of its string, so
right-justifying it yields an unchanged
display.
This layout manager has the concept
of four border regions and a center area. When you add something to a panel
that’s using a
BorderLayout you must use
an add( ) method that takes a String object as its first
argument, and that string must specify (with proper capitalization)
“North” (top),
“South” (bottom),
“East” (right),
“West” (left), or
“Center.” If you misspell or mis-capitalize, you won’t get a
compile-time error, but the applet simply won’t do what you expect.
Fortunately, as you will see shortly, there’s a much-improved approach in
Java 1.1.
Here’s a simple
example:
//: BorderLayout1.java
// Demonstrating the BorderLayout
import java.awt.*;
import java.applet.*;
public class BorderLayout1 extends Applet {
public void init() {
int i = 0;
setLayout(new BorderLayout());
add("North", new Button("Button " + i++));
add("South", new Button("Button " + i++));
add("East", new Button("Button " + i++));
add("West", new Button("Button " + i++));
add("Center", new Button("Button " + i++));
}
} ///:~
For every placement but
“Center,” the element that you add is compressed to fit in the
smallest amount of space along one dimension while it is stretched to the
maximum along the other dimension. “Center,” however, spreads out
along both dimensions to occupy the middle.
A
GridLayout allows you to
build a table of components, and as you add them they are placed left-to-right
and top-to-bottom in the grid. In the constructor you specify the number of rows
and columns that you need and these are laid out in equal
proportions.
//: GridLayout1.java
// Demonstrating the GridLayout
import java.awt.*;
import java.applet.*;
public class GridLayout1 extends Applet {
public void init() {
setLayout(new GridLayout(7,3));
for(int i = 0; i < 20; i++)
add(new Button("Button " + i));
}
} ///:~
In this case there are 21 slots but
only 20 buttons. The last slot is left empty; no “balancing” goes on
with a
GridLayout.
The
CardLayout allows you to
create the rough equivalent of a “tabbed dialog,” which in more
sophisticated environments has actual file-folder tabs running across one edge,
and all you have to do is press a tab to bring forward a different dialog. Not
so in the AWT: The CardLayout is simply a blank space and you’re
responsible for bringing forward new cards. (The JFC/Swing library contains
tabbed panes that look much better and take care of all the details for
you.)
This example will combine more than
one layout type, which seems rather difficult at first since only one layout
manager can be operating for an applet or application. This is true, but if you
create more Panel
objects, each one of those Panels can have its own layout manager and
then be integrated into the applet or application as simply another component,
using the applet or application’s layout manager. This gives you much
greater flexibility as seen in the following example:
//: CardLayout1.java
// Demonstrating the CardLayout
import java.awt.*;
import java.applet.Applet;
class ButtonPanel extends Panel {
ButtonPanel(String id) {
setLayout(new BorderLayout());
add("Center", new Button(id));
}
}
public class CardLayout1 extends Applet {
Button
first = new Button("First"),
second = new Button("Second"),
third = new Button("Third");
Panel cards = new Panel();
CardLayout cl = new CardLayout();
public void init() {
setLayout(new BorderLayout());
Panel p = new Panel();
p.setLayout(new FlowLayout());
p.add(first);
p.add(second);
p.add(third);
add("North", p);
cards.setLayout(cl);
cards.add("First card",
new ButtonPanel("The first one"));
cards.add("Second card",
new ButtonPanel("The second one"));
cards.add("Third card",
new ButtonPanel("The third one"));
add("Center", cards);
}
public boolean action(Event evt, Object arg) {
if (evt.target.equals(first)) {
cl.first(cards);
}
else if (evt.target.equals(second)) {
cl.first(cards);
cl.next(cards);
}
else if (evt.target.equals(third)) {
cl.last(cards);
}
else
return super.action(evt, arg);
return true;
}
} ///:~
This example begins by creating a
new kind of Panel: a ButtonPanel. This contains a single button,
placed at the center of a BorderLayout, which means that it will expand
to fill the entire panel. The label on the button will let you know which panel
you’re on in the CardLayout.
In the applet, both the Panel
cards where the cards will live and the layout manager cl for the
CardLayout must be members of the class because you need to have access
to those handles when you want to manipulate the cards.
The applet is changed to use a
BorderLayout instead of its default FlowLayout, a Panel is
created to hold three buttons (using a FlowLayout), and this panel is
placed at the “North” end of the applet. The cards panel is
added to the “Center” of the applet, effectively occupying the rest
of the real estate.
When you add the
ButtonPanels (or whatever other components you want) to the panel of
cards, the add( ) method’s first argument is not
“North,” “South,” etc. Instead, it’s a string that
describes the card. Although this string doesn’t show up anywhere on the
card, you can use it if you want to flip that card using the string. This
approach is not used in action( ); instead the first( ),
next( ), and last( ) methods are used. Check your
documentation for the other approach.
In Java, the use of some sort of
“tabbed panel” mechanism is quite important because (as you’ll
see later) in applet programming the use of pop-up dialogs is heavily
discouraged. For Java 1.0 applets, the CardLayout
is the only viable way for the applet to have a number of different forms that
“pop up” on
command.
Some time ago, it was believed that
all the stars, planets, the sun, and the moon revolved around the earth. It
seemed intuitive from observation. But then astronomers became more
sophisticated and started tracking the motion of individual objects, some of
which seemed at times to go backward in their paths. Since it was known that
everything revolved around the earth, those astronomers spent large amounts of
time coming up with equations and theories to explain the motion of the stellar
objects.
When trying to work with
GridBagLayout, you
can consider yourself the analog of one of those early astronomers. The basic
precept (decreed, interestingly enough, by the designers at “Sun”)
is that everything should be done in code. The Copernican revolution (again
dripping with irony, the discovery that the planets in the solar system revolve
around the sun) is the use of resources to determine the layout and make
the programmer’s job easy. Until these are added to Java, you’re
stuck (to continue the metaphor) in the Spanish Inquisition of
GridBagLayout and GridBagConstraints.
My recommendation is to avoid
GridBagLayout. Instead, use the other layout managers and especially the
technique of combining several panels using different layout managers within a
single program. Your applets won’t look that different; at least
not enough to justify the trouble that GridBagLayout entails. For my
part, it’s just too painful to come up with an example for this (and I
wouldn’t want to encourage this kind of library design). Instead,
I’ll refer you to Core Java by Cornell & Horstmann
(2nd ed., Prentice-Hall, 1997) to get
started.
As noted previously,
action( ) isn’t the only method that’s automatically
called by
handleEvent( ) once
it sorts everything out for you. There are three other sets of methods that are
called, and if you want to capture certain types of events (keyboard, mouse, and
focus events) all you have to do is override the provided method. These methods
are defined in the base class
Component, so
they’re available in virtually all the controls that you might place on a
form. However, you should be aware that this approach is deprecated in Java
1.1, so although you might see legacy code using this
technique you should use the Java 1.1 approaches (described later in this
chapter) instead.
|
Component method |
When it’s
called |
|---|---|
|
action (Event evt, Object
what) |
When the “typical”
event occurs for this component (for example, when a button is pushed or a
drop-down list item is selected) |
|
keyDown (Event evt, int
key) |
A key is pressed when this
component has the focus. The second argument is the key that was pressed and is
redundantly copied from evt.key. |
|
keyUp(Event evt, int
key) |
A key is released when this
component has the focus. |
|
lostFocus(Event evt, Object
what) |
The focus has moved away from the
target. Normally, what is redundantly copied from
evt.arg. |
|
gotFocus(Event evt, Object
what) |
The focus has moved into the
target. |
|
mouseDown(Event evt,
|
A mouse down has occurred over the
component, at the coordinates x, y. |
|
mouseUp(Event evt, int x, int
y) |
A mouse up has occurred over the
component. |
|
mouseMove(Event evt, int x, int
y) |
The mouse has moved while
it’s over the component. |
|
mouseDrag(Event evt, int x, int
y) |
The mouse is being dragged after a
mouseDown occurred over the component. All drag events are reported to
the component in which the mouseDown occurred until there is a
mouseUp. |
|
mouseEnter(Event evt, int x, int
y) |
The mouse wasn’t over the
component before, but now it is. |
|
mouseExit(Event evt, int x, int
y) |
The mouse used to be over the
component, but now it isn’t. |
You can see that each method
receives an Event object along with some information that you’ll
typically need when you’re handling that particular situation – with
a mouse event, for example, it’s likely that you’ll want to know the
coordinates where the mouse event occurred. It’s interesting to note that
when Component’s handleEvent( ) calls any of these
methods (the typical case), the extra arguments are always redundant as they are
contained within the Event object. In fact, if you look at the source
code for Component.handleEvent( ) you can see that it explicitly
plucks the additional arguments out of the Event object. (This might be
considered inefficient coding in some languages, but remember that Java’s
focus is on safety, not necessarily speed.)
To prove to yourself that these
events are in fact being called and as an interesting experiment, it’s
worth creating an applet that overrides each of the methods above (except for
action( ), which is overridden in many other places in this chapter)
and displays data about each of the events as they happen.
This example also shows you how to
make your own button object because that’s what is used as the target of
all the events of interest. You might first (naturally) assume that to make a
new button, you’d inherit from
Button. But this
doesn’t work. Instead, you inherit from
Canvas (a much more
generic component) and paint your button on that canvas by overriding the
paint( ) method. As
you’ll see, it’s really too bad that overriding Button
doesn’t work, since there’s a bit of code involved to paint the
button. (If you don’t believe me, try exchanging Button for
Canvas in this example, and remember to call the base-class constructor
super(label). You’ll see that the button doesn’t get painted
and the events don’t get handled.)
The myButton class is
specific: it works only with an AutoEvent “parent window”
(not a base class, but the window in which this button is created and lives).
With this knowledge, myButton can reach into the parent window and
manipulate its text fields, which is what’s necessary to be able to write
the status information into the fields of the parent. Of course this is a much
more limited solution, since myButton can be used only in conjunction
with AutoEvent. This kind of code is sometimes called “highly
coupled.” However, to make myButton more generic requires a lot
more effort that isn’t warranted for this example (and possibly for many
of the applets that you will write). Again, keep in mind that the following code
uses APIs that are deprecated in Java
1.1.
//: AutoEvent.java
// Alternatives to action()
import java.awt.*;
import java.applet.*;
import java.util.*;
class MyButton extends Canvas {
AutoEvent parent;
Color color;
String label;
MyButton(AutoEvent parent,
Color color, String label) {
this.label = label;
this.parent = parent;
this.color = color;
}
public void paint(Graphics g) {
g.setColor(color);
int rnd = 30;
g.fillRoundRect(0, 0, size().width,
size().height, rnd, rnd);
g.setColor(Color.black);
g.drawRoundRect(0, 0, size().width,
size().height, rnd, rnd);
FontMetrics fm = g.getFontMetrics();
int width = fm.stringWidth(label);
int height = fm.getHeight();
int ascent = fm.getAscent();
int leading = fm.getLeading();
int horizMargin = (size().width - width)/2;
int verMargin = (size().height - height)/2;
g.setColor(Color.white);
g.drawString(label, horizMargin,
verMargin + ascent + leading);
}
public boolean keyDown(Event evt, int key) {
TextField t =
(TextField)parent.h.get("keyDown");
t.setText(evt.toString());
return true;
}
public boolean keyUp(Event evt, int key) {
TextField t =
(TextField)parent.h.get("keyUp");
t.setText(evt.toString());
return true;
}
public boolean lostFocus(Event evt, Object w) {
TextField t =
(TextField)parent.h.get("lostFocus");
t.setText(evt.toString());
return true;
}
public boolean gotFocus(Event evt, Object w) {
TextField t =
(TextField)parent.h.get("gotFocus");
t.setText(evt.toString());
return true;
}
public boolean
mouseDown(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseDown");
t.setText(evt.toString());
return true;
}
public boolean
mouseDrag(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseDrag");
t.setText(evt.toString());
return true;
}
public boolean
mouseEnter(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseEnter");
t.setText(evt.toString());
return true;
}
public boolean
mouseExit(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseExit");
t.setText(evt.toString());
return true;
}
public boolean
mouseMove(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseMove");
t.setText(evt.toString());
return true;
}
public boolean mouseUp(Event evt,int x,int y) {
TextField t =
(TextField)parent.h.get("mouseUp");
t.setText(evt.toString());
return true;
}
}
public class AutoEvent extends Applet {
Hashtable h = new Hashtable();
String[] event = {
"keyDown", "keyUp", "lostFocus",
"gotFocus", "mouseDown", "mouseUp",
"mouseMove", "mouseDrag", "mouseEnter",
"mouseExit"
};
MyButton
b1 = new MyButton(this, Color.blue, "test1"),
b2 = new MyButton(this, Color.red, "test2");
public void init() {
setLayout(new GridLayout(event.length+1,2));
for(int i = 0; i < event.length; i++) {
TextField t = new TextField();
t.setEditable(false);
add(new Label(event[i], Label.CENTER));
add(t);
h.put(event[i], t);
}
add(b1);
add(b2);
}
} ///:~
You can see the constructor uses
the technique of using the same name for the argument as what it’s
assigned to, and differentiating between the two using
this:
this.label = label;
The paint( ) method
starts out simple: it fills a “round rectangle” with the
button’s color, and then draws a black line around it. Notice the use of
size( ) to determine the width and height of the component (in
pixels, of course). After this, paint( ) seems quite complicated
because there’s a lot of calculation going on to figure out how to center
the button’s label inside the button using the “font metrics.”
You can get a pretty good idea of what’s going on by looking at the method
call, and it turns out that this is pretty stock code, so you can just cut and
paste it when you want to center a label inside any component.
You can’t understand exactly
how the keyDown( ), keyUp( ), etc. methods work until
you look down at the AutoEvent class. This contains a
Hashtable to hold the strings representing the
type of event and the TextField where information about that event is
held. Of course, these could have been created statically rather than putting
them in a Hashtable, but I think you’ll agree that it’s a lot
easier to use and change. In particular, if you need to add or remove a new type
of event in AutoEvent, you simply add or remove a string in the
event array – everything else happens
automatically.
The place where you look up the
strings is in the keyDown( ), keyUp( ), etc. methods
back in MyButton. Each of these methods uses the parent handle to
reach back to the parent window. Since that parent is an AutoEvent it
contains the Hashtable h, and the get( ) method, when
provided with the appropriate String, will produce a handle to an
Object that we happen to know is a TextField – so it is cast
to that. Then the Event object is converted to its String
representation, which is displayed in the TextField.
It turns out this example is rather
fun to play with since you can really see what’s going on with the events
in your
program.
For safety’s sake, applets
are quite restricted and there are many things you can’t do. You can
generally answer the question of what an applet is able to do by looking at what
it is supposed to do: extend the functionality of a Web page in a
browser. Since, as a net surfer, you never really know if a Web page is from a
friendly place or not, you want any code that it runs to be safe. So the biggest
restrictions you’ll notice are probably:
1) An applet can’t touch
the local disk. This means writing or reading, since you
wouldn’t want an applet to read and transmit important information about
you across the Web. Writing is prevented, of course, since that would be an open
invitation to a virus. These restrictions can be relaxed when digital signing is
fully implemented.
2) An applet can’t have
menus. (Note: this is fixed in Swing) This is probably less oriented toward
safety and more toward reducing confusion. You might have noticed that an applet
looks like it blends right in as part of a Web page; you often don’t see
the boundaries of the applet. There’s no frame or title bar to hang the
menu from, other than the one belonging to the Web browser. Perhaps the design
could be changed to allow you to merge your applet menu with the browser menu
– that would be complicated and would also get a bit too close to the edge
of safety by allowing the applet to affect its environment.
3) Dialog boxes are
“untrusted.” In Java, dialog boxes present a bit of a quandary.
First of all, they’re not exactly disallowed in applets but they’re
heavily discouraged. If you pop up a dialog box from within an applet
you’ll get an “untrusted applet” message attached to that
dialog. This is because, in theory, it would be possible to fool the user into
thinking that they’re dealing with a regular native application and to get
them to type in their credit card number, which then goes across the Web. After
seeing the kinds of GUIs that the AWT produces you might have a hard time
believing anybody could be fooled that way. But an applet is always
attached to a Web page and visible within your Web browser, while a dialog box
is detached so in theory it could be possible. As a result it will be rare to
see an applet that uses a dialog box.
Many applet restrictions are
relaxed for trusted applets (those signed by a trusted source) in newer
browsers.
If you can live within the
restrictions, applets have definite advantages, especially when building
client/server or other networked
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 c