The Java Programming Tutorial: Vol. 2
Classes, Threads, and Applets
by Mark C. Reynolds
Reprinted from Web Developer? magazine, Vol. 2 No. 2 May/June 1996 ?
1996
The Java language provides everything one would eXPect in a modern object-orie
nted programming language. It has all the standard control constrUCts, a reaso
nably comprehensible object model, and tools for creating standalone applicati
ons or smaller applets that are designed to run inside Web pages.
The power of Java does not reside primarily in the language itself, however; i
t resides in the libraries that accompany it: the Java Class Hierarchy. The fi
rst part of this tutorial, in the Spring 1996 issue of Web Developer?, dea
lt with the Java language and the construction of simple applets. This part wi
ll focus on the set of Java classes associated with graphics, known as the Adv
anced Windowing Toolkit, or AWT. Applet construction will also be revisited, w
ith an eye toward event handling and multitaSKINg.
Programming low-level graphics is often like constructing a sand castle one gr
ain of sand at a time. Higher-level toolkits allow more rapid construction of
user interfaces, but they lack fine control and involve certain design comprom
ises and limitations. Very high-level GUI builders can be extremely powerful,
but they can also be the source of enormous frustration when asked to do somet
hing outside their repertoire. Where does Java s AWT fit in?
In many ways, the Advanced Windowing Toolkit will seem very familiar to anyone
who has ever used a toolkit to write a graphics program under almost any wind
owing system. The design issues are the same: the suite of components (buttons
, labels, scrollbars, cuboctohedra) that are available, how these components a
re arranged visually, and the mechanisms involved in communicating between the
components and the application program itself. You want a button labeled Canc
el, you want it in the lower right-hand corner of your graphic, and you want t
o tie it to a function named CancelAction(), for example. The AWT gives you se
veral ways to accomplish these goals. It also provides a set of very powerful
classes and methods that give Java programmers some distinctly different highe
r-level tools (as well as some distinctly different programming challenges).
There are four ASPects to programming with the AWT: establishing the layout, i
mplementing the components, manipulating global resources such as colors and f
onts, and handling events. Each of these aspects interacts with the others in
well-defined ways.
One of the strengths of the AWT is that it was designed to be platform-indepen
dent. This does not guarantee that any piece of AWT code will look exactly the
same on a Unix machine, a Windows 95 box, or a Macintosh, but only that the b
ehavior of the AWT will be the same on each. We will consider each of the four
aspects of AWT programming in turn.
Unlike many toolkits, the AWT does not really enforce a specific layout policy
. Instead, it provides five standard layout classes that correspond to commonl
y encountered types of layouts. These are the BorderLayout, CardLayout, FlowLa
yout, GridLayout, and GridBagLayout classes. It is also possible to create one
s own layout class (although this is not for the faint of heart) or to eschew
any formalized layout altogether and place one s components at absolute (x,y)
coordinates. Most AWT applets will end up using FlowLayout, GridLayout, or Bo
rderLayout, which are conceptually the simplest.
In order to understand the idea behind the layout classes, we will start with
a simple example using BorderLayout. In a BorderLayout, components may be plac
ed at the North, South, East, or West locations, or they may be placed in the
Center. It is called a "border" layout because each component that s added sti
cks to its corresponding border (with the exception of the Center component, w
hich occupies the unused space in the middle). Figure 1 shows a code fragment
that creates a border layout with four buttons, one at each cardinal point.
This code first creates a new instance of the BorderLayout class using the new
operator, and then calls the setLayout method with that instance as its sole
argument. What is the purpose of this statement? We can guess that this establ
ishes the BorderLayout as the default layout for the applet. The real reason t
hat it works is that a Java applet is actually an instance of the Applet class
, and the Applet class is actually a subclass of a graphical container class k
nown as a Panel. Just as any applet must extend the Applet class, so Applet ex
tends Panel. The call to setLayout is actually manipulating the panel in which
the applet resides.
The next four statements create four buttons named for their intended location
s. The text of each button is taken from the instance variables NORTH, SOUTH,
EAST, and WEST, which have been declared with the keyWords static and final.
This is the standard approach to declaring constants in Java. Java has no cons
t keyword, like C++. Instead it uses the keywords final, which indicates that
the instance variable may not be changed, and static, which says that this ins
tance variable is shared among all instances of its class. It is very importan
t to realize that while the static keyword has the same meaning of permanence
it has in C and C++, it does not affect the scope of the instance variable, as
it would in C and C++. Other keywords (private and protected) are used for th
at purpose.
After the buttons have been created, they are entered into the layout using th
e add method. A final call is then made to the show method to ensure that all
the components are drawn. Each of the five layout classes has its own peculiar
form of the add method. In the case of BorderLayout, it wants add to have two
arguments: The first is the placement location, and the second is the compone
nt to be added. Note that in this example, the button labels are identical to
their placement locations. This is by no means necessary; the buttons could ju
st as easily have been named Moe, Larry, Curly, and Shemp. The add method, how
ever, must be given one of the cardinal directions exactly as shown or it will
fail to understand. You cannot add(Norte, el_amador), even if el_amador is a
valid button instance.
This example is, of course, extremely boring. The buttons are not connected to
anything, so that pressing them will not have any meaningful results. It is s
traightforward to modify this code into a complete Applet that will do somethi
ng meaningful. In particular, we can change it so that it will illustrate some
thing interesting about applet events. A text-area component will be added at
the Center location. We will also add an event handler, which will write infor
mation into that text area about the events received by the applet. The comple
te code for this simple Evlab (Event Lab) applet is shown in Figure 2.
This code uses a little bit of everything. It declares and uses the BorderLayo
ut class; it incorporates three types of graphical components (Button, TextFie
ld and TextArea); it makes use of one of the global graphical elements, namely
Font; and it incorporates some rudimentary event handling. It also represents
a new type of applet, one that is not threaded. This last point will be discu
ssed in more detail below.
The init() method of the Evlab applet is similar to the BLex.java code shown p
reviously. This code make more extensive use of other graphical components. It
still places buttons at the North, East, and West locations. This version of
init() creates a TextField and puts that at South, and also creates a TextArea
and puts that in the Center position of the BorderLayout. These two text comp
onents are almost identical to the Html elements of the same names. A TextFiel
d is used to display a single line of text. It may be read/write or read-only.
In our example it has been created with no initial text (the first argument t
o the constructor is "") and has width 70. By default, TextFields are read/wri
te so that it will be possible to type into this particular component.
The construction of the centrally placed TextArea is a little more elaborate.
A TextArea is a multiline text entity, which can also be read/write or read-on
ly. In our case it has been created with an initial size of 10 x 70. The call
to the setEditable() method with the false argument declares that the user wil
l not be able to modify the contexts of this text area. A specific font to be
used is also set by calling the setFont() method. This is done for both the ed
itable text field down South and the text area in the Center.
The font was oBTained by calling the Font() constructor with three arguments:
the name of the font, the font style, and the font size. Times Roman is one of
a small number of fonts that Java guarantees to provide (the others include H
elvetica and Courier). The style argument indicates whether we want the plain
version (Font.PLAIN), bold version (Font.BOLD), italic version (Font.ITALIC),
or some other style. Styles may be combined, so that if the second argument ha
d been Font.BOLD + Font.ITALIC, Java would have tried to find a Times Roman 14
-point font that was both bold and italic. The init() method concludes by succ
essively adding each of the five components to the BorderLayout and then calli
ng show() to insure that they are shown.
In addition to the init() method, which initializes the graphic environment, t
his applet also has an action() method (and two placeholder methods, start() a
nd stop(), discussed below).
As one might guess, the action method is used to capture events. The action()
method is passed two arguments: an Event instance ev and an Object arg. The Ev
ent instance ev describes the event that just happened. The second argument, a
rg, represents component-specific information that the AWT gives you to help h
andle the event. When a button is pressed, for example, the action method will
be called, with arg set equal to the label of the button that was just presse
d.
Figure 3
In our Evlab example, we do not attempt to do anything to the incoming events
and their arguments except to display information about them. The first statem
ent of the action() method uses the setText method of the text area tfe to dis
play the event id of the incoming event. Using setText ensures that any previo
us text in this text area is cleared. The event id is a somewhat mystical numb
er describing the type of event. The next four statements of the action() meth
od append additional information about the event ev and the argument arg to th
e text area. Since each string used ends with a newline "
", each will be on
its own line. The x and y coordinates of the site of the event are printed, as
well as the target of the event (which component was affected), and the arg f
ield of the Event ev. It is often the case that ev.arg and the argument arg ar
e identical. Figure 3 shows the appearance of the Evlab applet after the West
button has been pressed.
The final statement of the action() method is return(false). This is necessary
because our action() method overrides a built-in Applet method that returns a
Boolean value. The return code is used to indicate if the event has been hand
led. Like many windowing systems, the AWT uses a hierarchical event-handling m
odel. Just as graphics may be built in a layered fashion, so events may be gen
erated and handled at any of the various levels. Our action() method returns f
alse to indicate that the event has not been handled.
An Excellent way to use the event lab applet to demonstrate the event hierarch
y is to type some text into the text field in the South position, and then hit
Return. This will generate an event that will be displayed in the text area i
n the center of the applet. The third line of text, the one describing the eve
nt target, will be too long, and it will be necessary to scroll the text area
using the horizontal scrollbar on the bottom of the text area. When you hit th
is scrollbar, it will scroll the central text area, and it will not generate a
nother event. In reality, when the scrollbar is activated it does generate ano
ther event, but that event is intercepted and processed by the text area itsel
f. The result is the scroll action that we see.
Since that event is completely processed by the text area, it never reaches th
e action() event handler of the Evlab applet--it has been consumed by the text
area s own built-in event handler. The reader is strongly encouraged to exper
iment with the Evlab applet by placing other types of graphical components (as
described below) in the South position and determining what types of events a
re generated.
What of the other layout methods? FlowLayout is the simplest of the five types
. When this form of layout is used, components are added from left to right. W
hen all the available space in a given row has been used, a new row will be cr
eated. Rows can be CENTER justified (the default) or LEFT or RIGHT justified.
FlowLayout is typically used for rows of similar items, such as buttons. An ex
ample of this layout approach will be given below.
GridLayout is a structured layout format in which graphical components are pla
ced on a grid with a fixed size. If a GridLayout is created with a grid of 3 r
ows by 5 columns, for example, then we can add a component at location (2,3),
which will correspond to the third column of the second row. GridBagLayout is
an extensible form of grid layout, in which the grid is permitted to expand as
new elements are added. GridBagLayout is the most general layout strategy, an
d also the most complex. Finally, CardLayout implements a HyperCard-style layo
ut. Graphical components are added to individual cards, which are then display
ed sequentially, rather than simultaneously.
Figure 4 lists the graphical components, such as Button, and the global graphi
cal elements, such as Font, that are provided by the AWT. If you have ever don
e any sort of graphics programming, many of these items will be very familiar
to you. A List is actually a scrolling listbox, rather than a Lisp-like List.
A CheckboxGroup is called a radio button by most other graphics systems.
Note that the list of graphical components includes items that are containers,
such as Panel. This makes the hierarchical organization described above possi
ble. We could modify the event lab applet to put a Panel in the central positi
on, give that panel its own layout, and then proceed to add components to that
panel. This panel might have a GridLayout, even though the applet s panel has
a BorderLayout.
The Java API provides all the details on using the various components and glob
al elements. Rather than attempt an exhaustive description of all of them, thi
s article will conclude by focusing on a small set of classes related to anima
tion. We have already seen a very simple demonstration of image loading in the
first part of this tutorial. A Java VCR will now be developed that will allow
us to load a set of images and then smoothly display them under user control.
To do this, however, we must make a short detour and discuss the topic of thr
eads in Java.
Threads
Most modern operating systems support the concept of multitasking, in which mu
ltiple independent processes timeshare the same physical CPU. Soon after the f
irst multitasking OS was created, it was realized that it would be delightful
to support the same sort of multiple-task model inside application programs, r
ather than just at the command-line level. The first implementations of this i
dea were cumbersome, and involved copying the entire code and data space of th
e parent task to all newly created child tasks. Eventually, the concept of thr
eads was born. A thread is a lightweight task, without much context. Good thre
ad implementations should allow a top-level application to create multiple thr
eads, assign each a piece of work, coordinate them, and ultimately clean up wh
en all of them are done. Java supports just such a threaded programming model.
The concept of threads not only applies to stand-alone Java applications, but
also to Java applets. The Evlab.java applet in Figure 2 does not make explicit
use of threads. It has an init() method, which is called when the applet is l
oaded; a start() method, which is called after loading is complete (and when a
previously loaded Java page is revisited); and a stop() method, which is call
ed when a page containing the Java applet is unloaded, or when another page is
visited. Both the tiny applets shown in the first part of the tutorial, as we
ll as the Vcr.java applet shown in Figure 5, are threaded applets. We know thi
s because they implement the Runnable interface: The phrase "implements Runnab
le" is part of the applet s class declaration. A runnable applet will also hav
e a run() method, which will be entered whenever a new thread is created for t
he applet s class. If this explanation seems a bit obscure, it should become m
ore clear as we work through the code of the Java VCR.
The init() method does a number of very familiar things. Its goal is to load a
number of images, specified by the HTML PARAM attribute "nimgs." These images
are to be found at a location specified by the PARAM "imgloc" relative to the
document base. Each image has a numerical suffix, as well as the suffix .gif.
Thus if nimgs is 10 and imgloc is images/myanim, then the applet will look fo
r its images in files named images/myanim1.gif through images/myanim10.gif. Th
e first few lines of the init() method retrieve these parameters from the HTML
environment using getParameter() and do some sanity checking. If everything s
eems reasonable, a new instance of the MediaTracker class is created and store
d in the instance variable tracker. Space is also allocated for an array of ni
mgs images in the instance variable imgs. A for loop is then used to attempt t
o load each of the images into that array. Note that two loop variables are us
ed, because the imgs[] array is 0-indexed, while the image names have suffixes starting from 1.
The for loop also contains the statement tracker.addImage(imgs[i], 0). The Med
iaTracker class is being used to keep track of the progress of the images bein
g loaded. This is necessary because getImage() does not actually get the image
--it simply arranges to start getting it. The call to the addImage() method of
tracker tells the tracker instance to begin keeping track of that image. The
second argument to addImage() is an arbitrary ID, 0 in this case, which can be
used to subsequently inquire about the progress of all images with that ID. E
ffectively, we are placing all the images in a single pool, associated with ID
0, whose status will be queried later.
The init() method next builds a layout in which the images and a series of con
trol buttons will be displayed. A BorderLayout with two elements is used for t
he applet s panel. The North element will be a Panel that will hold the images
as they are being animated, while the South element will be another Panel tha
t will hold the control buttons. The variable nodim is used to store the apple
t s preferred size (set by the WIDTH and HEIGHT attributes in the APPLET tag).
We make the North panel as wide as the entire applet, but carve 50 pixels out
of its height for the South Panel to occupy.
The South Panel is constructed using a left-justified FlowLayout. This means t
hat the buttons we add to it will be added from left to right. The remainder o
f the init() method constructs these buttons, sets the button font to 14 point
Helvetica, and then adds those buttons to the South panel. Note that it is ne
cessary to explicitly say so.add(). If we had used add(), the buttons would ha
ve been added to the top-level BorderLayout. This code illustrates hierarchica
l organization using the AWT. The top-level applet Panel is a BorderLayout Pan
el with two elements: both Panels themselves. The North Panel contains no sube
lements, while the South Panel contains four: the four buttons labeled FWD, RE
V, STOP, and EJECT.
Before we discuss the run() method, let us first consider the start() and stop
() methods. When init() method completes, the start() method will be invoked.
It will examine the instance variable animthread, discover that it is null, an
d therefore will create a new Thread with the argument this. This statement ac
tually creates a new Java Thread based on the Vcr class. The next statement, a
nimthread.start(), runs that thread. By virtue of this statement, the run() me
thod will be entered in that separate thread of control. The stop() method is
the inverse of the start() method. When the page is unloaded, the stop() metho
d will be called. It will terminate the animthread thread using animthread.sto
p(), and then reset the animthread variable to null. This ensures that if the
page is reloaded or revisited, the start() method will create a new thread yet
again.
The run() method and the action() event handler work together to display the i
mages according to the controls that the user has pressed. The run() method st
arts out by doing some additional sanity checking. It then calls tracker.waitF
orID(0). This call will wait until all the images associated with ID 0 (which
will be all the images in this case) have been fully loaded and prepared for o
n-screen display. This method invocation in encased in a peculiar construction
known as a try block. Try blocks are necessary because some methods can raise
exceptions, which must be caught. If such an exception occurs (because there
is not enough memory for all the images, for example), the run() method return
s via the catch clause.
The next two statements are a little bit of thread magic. The run method disco
vers the current thread and places that in the local variable me. It then lowe
rs its own thread priority to one below the normal thread priority. This ensur
es that the other thread--the one actually doing the drawing--has higher prior
ity, which is what most animation applications would want. After this is done,
the run() method enters a loop. If the instance variable imginc is nonzero, t
hen the index of the image to be displayed will be updated and a call to repai
nt() made to ensure that the new image is shown. The instance variable whichim
g is used to hold the index of the image currently being drawn. Great care is
taken to ensure that whichimg stays within range. It must never be less than 0
(the minimum image number) or greater than nimgs-1 (the maximum image number)
. After the image has been updated, the thread puts itself to sleep for 100 mi
croseconds. This is another thread trick, ensuring that the Java runtime will
actually look for another thread to run. After all, there really is only a
single CPU and a single applet, so Java must somehow decide when to run the
various threads. When Java notices that one thread has put itself tosleep, all
other threads become runnable.
The action() method controls the critical-instance variables imginc and done.
The action() method looks for events that are associated with Buttons. The ev.
target field will always indicate the type of graphical object that was associ
ated with the Event. Java has a very convenient operator, instanceof, to test
if something opaque like ev.target is actually an instance of a particular cla
ss--Button, in this case. If the test passes, then the local variable thelab i
s set to the label of the Button. A four-way test is now done. If the user pre
ssed FWD, then imginc is set to 1 and the images move forward. If REV was pres
sed, imginc becomes -1 and the images move backward. If STOP is pressed, imgin
c is set to 0, and the images move no more; they rest at the current image. Fi
nally, if EJECT is pressed, the instance variable done is set to true.
The paint() method does the work of actually drawing the images. If done is fa
lse, then it performs a number of sanity checks, and finally uses the drawImag
e() method to draw the current image, represented by the index whichimg. Becau
se of the use of the MediaTracker at the top of the run method, we can be sure
that all images are loaded and ready to be drawn so that the animation will g
o quite smoothly. If done is true, then the paint() method clears the entire d
rawing area, erasing all images but leaving the buttons intact. Note that the
paint() method can be called by the browser environment itself, not just by re
paint(). If you obscure the applet s drawing area with another window and then
reexpose it, the paint() method will be called. This is why it is important t
hat the instance variable whichimg always have a sane value.
Figure 7
Figure 6 shows a simple HTML file that can be used to load images into the Vcr
applet. Figure 7 shows the applet captured in motion, at about image 7. Sun s
Tumbling Duke GIFs were used to create this particular animation, although an
y set of images could have been used. If you download the Java Development Kit
(JDK), these images will be found in the demo/TumblingDuke/images Directory.
This Vcr applet can serve as a template for animation applets, since it contai
ns all the basic elements of a threaded graphics applet. The start() and stop(
) methods can be used as shown. The MediaTracker code and the Thread manipulat
ion portions of the init() and run() methods are also quite generic. More work
needs to be done in order to make this applet robust, however. The applet nev
er checks to see if the image dimensions will actually fit in the applet s pan
el, for example.
Developing Yourself
The Java Development Kit may be obtained at FTP.javasoft. Almost all major pla
tforms, including several flavors of Unix, Windows NT, Windows 95, and the Mac
intosh now support some version of the JDK. Sun also has a Web server at
http://javasoft.com but it is often overloaded. One of the most comprehensive
and Accessible Java sites, the Gamelan repository, has many useful public-
domain classes,applets, and applications. Sun itself is publishing a set of
definitive reference works on Java. The reader may also benefit from Lemay and
Perkins book Teach Yourself Java in 21 Days.
Figure 1
Creating four buttons using the Border Layout Class
public static final String NORTH = "North";
public static final String SOUTH = "South";
public static final String EAST = "East";
public static final String WEST = "West";
public void init() {
setLayout(new BorderLayout());
Button no = new Button(NORTH);
Button ea = new Button(EAST);
Button we = new Button(WEST);
Button so = new Button(SOUTH);
add(NORTH, no);
add(SOUTH, so);
add(EAST, ea);
add(WEST, we);
show();
}
Figure 2
Capturing and Displaying Applet Events
import java.awt.*;
import java.applet.*;
import java.net.*;
public class Evlab extends Applet {
public static final String NORTH = "North";
public static final String SOUTH = "South";
public static final String EAST = "East";
public static final String WEST = "West";
TextArea tfe;
public void init() {
setLayout(new BorderLayout());
Button no = new Button(NORTH);
Button ea = new Button(EAST);
Button we = new Button(WEST);
TextField tff = new TextField("", 70);
Font fo = new Font("TimesRoman", Font.BOLD, 14);
tfe = new TextArea(10, 70);
tfe.setEditable(false);
tfe.setFont(fo);
tff.setFont(fo);
add("Center", tfe);
add(SOUTH, tff);
add(NORTH, no);
add(EAST, ea);
add(WEST, we);
show();
}
public void start() {
}
public void stop() {
}
public boolean action(Event ev, Object arg) {
tfe.setText("Id = " + ev.id + "
");
tfe.appendText("x,y = " + ev.x + "," + ev.y + "
");
tfe.appendText("target = " + ev.target.toString() + "
");
tfe.appendText("arg = " + ev.arg + "
");
tfe.appendText("Object = " + arg.toString());
return(false);
}
}
figure 3:
Figure 4
Graphical components and Global elements of the Java AWT
AWT Components
Button Canvas Checkbox CheckboxGroup
Choice Dialog FileDialog Frame
Label List Menu MenuBar
MenuItem Panel Scrollbar TextArea
TextField Window
AWT Global Elements
Color Dimension Font FontMetrics
Graphics Image Insets MediaTracker
Point Polygon Rectangle
Figure 5
A VCR animation applet in Java
/**
A VCR in Java
@author Mark C. Reynolds
@version 1.0, 23 Feb, 1996
*/
import java.applet.*;
import java.awt.*;
import java.net.*;
public class Vcr extends Applet implements Runnable {
MediaTracker tracker;
Dimension nodim;
Thread animthread = null;
Image imgs[];
static final String FWD = "FWD";
static final String REV = "REV";
static final String STOP = "STOP";
static final String EJECT = "EJECT";
int whichimg = 0;
int imginc = 0; // +1 = forward, -1 = reverse, 0 = stop
int nimgs = 0;
boolean done = false;
public void init() {
BorderLayout nl;
FlowLayout fl;
Button bu[];
Font fo;
Panel so;
Panel no;
String tmp;
String imgloc;
imgloc = getParameter("imgloc");
if ( imgloc != null ) {
tmp = getParameter("nimgs");
if ( tmp != null ) {
nimgs = Integer.parseInt(tmp);
if ( nimgs > 0 ) {
imgs = new Image[nimgs];
tracker = new MediaTracker(this);
for(int i = 0, j = 1; i < nimgs; i++, j++) {
imgs[i] = getImage(getDocumentBase(), imgloc + j + ".g
if");
tracker.addImage(imgs[i], 0);
}
nl = new BorderLayout();
setLayout(nl);
no = new Panel();
nodim = preferredSize();
no.resize(nodim.width, nodim.height-50);
add("North", no);
so = new Panel();
fl = new FlowLayout(FlowLayout.LEFT);
so.setLayout(fl);
add("South", so);
bu = new Button[4];
bu[0] = new Button(Vcr.FWD);
bu[1] = new Button(Vcr.REV);
bu[2] = new Button(Vcr.STOP);
bu[3] = new Button(Vcr.EJECT);
fo = new Font("Helvetica", Font.PLAIN, 14);
for(int i = 0; i < 4; i++)
bu[i].setFont(fo);
so.add(bu[0]);
so.add(bu[1]);
so.add(bu[2]);
so.add(bu[3]);
}
}
}
}
public void run() {
Thread me;
if ( nimgs < 1 imgs == null ) return;
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
return;
}
me = Thread.currentThread();
me.setPriority(Thread.NORM_PRIORITY-1);
while ( animthread != null ) {
whichimg += imginc;
if ( whichimg < 0 )
{
whichimg = 0;
imginc = 0;
}
if ( whichimg >= nimgs )
{
whichimg = (nimgs - 1);
imginc = 0;
}
if ( imginc != 0 ) repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
public void paint(Graphics g) {
if ( done == false ) {
if ( ( imgs != null ) && ( 0 <= whichimg ) && ( whichimg < nimgs ) &
&
( imgs[whichimg] != null ) ) {
g.drawImage(imgs[whichimg], 0, 0, this);
}
} else {
g.clearRect(0, 0, nodim.width, nodim.height);
}
}
public void start() {
if ( animthread == null ) {
animthread = new Thread(this);
animthread.start();
}
}
public void stop() {
if ( animthread != null ) {
animthread.stop();
animthread = null;
repaint();
}
}
public boolean action(Event evt, Object arg) {
Button thebut;
String thelab;
boolean handled = false;
if ( evt.target instanceof Button ) {
thebut = (Button)(evt.target);
thelab = thebut.getLabel();
if ( thelab.equals(Vcr.FWD) ) {
imginc = 1;
handled = true;
} else if ( thelab.equals(Vcr.REV) ) {
imginc = (-1);
handled = true;
} else if ( thelab.equals(Vcr.STOP) ) {
imginc = 0;
handled = true;
} else if ( thelab.equals(Vcr.EJECT) ) {
imginc = 0;
done = true;
stop();
handled = true;
}
}
return(handled);
}
}
Figure 5
/**
A VCR in Java
@author Mark C. Reynolds
@version 1.0, 23 Feb, 1996
*/
import java.applet.*;
import java.awt.*;
import java.net.*;
public class Vcr extends Applet implements Runnable {
MediaTracker tracker;
Dimension nodim;
Thread animthread = null;
Image imgs[];
static final String FWD = "FWD";
static final String REV = "REV";
static final String STOP = "STOP";
static final String EJECT = "EJECT";
int whichimg = 0;
int imginc = 0; // +1 = forward, -1 = reverse, 0 = stop
int nimgs = 0;
boolean done = false;
public void init() {
BorderLayout nl;
FlowLayout fl;
Button bu[];
Font fo;
Panel so;
Panel no;
String tmp;
String imgloc;
imgloc = getParameter("imgloc");
if ( imgloc != null ) {
tmp = getParameter("nimgs");
if ( tmp != null ) {
nimgs = Integer.parseInt(tmp);
if ( nimgs > 0 ) {
imgs = new Image[nimgs];
tracker = new MediaTracker(this);
for(int i = 0, j = 1; i < nimgs; i++, j++) {
imgs[i] = getImage(getDocumentBase(), imgloc + j + ".g
if");
tracker.addImage(imgs[i], 0);
}
nl = new BorderLayout();
setLayout(nl);
no = new Panel();
nodim = preferredSize();
no.resize(nodim.width, nodim.height-50);
add("North", no);
so = new Panel();
fl = new FlowLayout(FlowLayout.LEFT);
so.setLayout(fl);
add("South", so);
bu = new Button[4];
bu[0] = new Button(Vcr.FWD);
bu[1] = new Button(Vcr.REV);
bu[2] = new Button(Vcr.STOP);
bu[3] = new Button(Vcr.EJECT);
fo = new Font("Helvetica", Font.PLAIN, 14);
for(int i = 0; i < 4; i++)
bu[i].setFont(fo);
so.add(bu[0]);
so.add(bu[1]);
so.add(bu[2]);
so.add(bu[3]);
}
}
}
}
public void run() {
Thread me;
if ( nimgs < 1 imgs == null ) return;
try {
tracker.waitForID(0);
} catch (InterruptedException e) {
return;
}
me = Thread.currentThread();
me.setPriority(Thread.NORM_PRIORITY-1);
while ( animthread != null ) {
whichimg += imginc;
if ( whichimg < 0 )
{
whichimg = 0;
imginc = 0;
}
if ( whichimg >= nimgs )
{
whichimg = (nimgs - 1);
imginc = 0;
}
if ( imginc != 0 ) repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
public void paint(Graphics g) {
if ( done == false ) {
if ( ( imgs != null ) && ( 0 <= whichimg ) && ( whichimg < nimgs ) &
&
( imgs[whichimg] != null ) ) {
g.drawImage(imgs[whichimg], 0, 0, this);
}
} else {
g.clearRect(0, 0, nodim.width, nodim.height);
}
}
public void start() {
if ( animthread == null ) {
animthread = new Thread(this);
animthread.start();
}
}
public void stop() {
if ( animthread != null ) {
animthread.stop();
animthread = null;
repaint();
}
}
public boolean action(Event evt, Object arg) {
Button thebut;
String thelab;
boolean handled = false;
if ( evt.target instanceof Button ) {
thebut = (Button)(evt.target);
thelab = thebut.getLabel();
if ( thelab.equals(Vcr.FWD) ) {
imginc = 1;
handled = true;
} else if ( thelab.equals(Vcr.REV) ) {
imginc = (-1);
handled = true;
} else if ( thelab.equals(Vcr.STOP) ) {
imginc = 0;
handled = true;
} else if ( thelab.equals(Vcr.EJECT) ) {
imginc = 0;
done = true;
stop();
handled = true;
}
}
return(handled);
}
}
Figure 6
The HTML file which drives the Java VCR
<HTML>
<HEAD>
<title>A Java VCR</title>
</HEAD>
<BODY>
<HR>
<APPLET CODE="Vcr.class" WIDTH=300 HEIGHT=150>
<PARAM NAME=imgloc VALUE="images/T">
<PARAM NAME="nimgs" VALUE="16">
</APPLET>
<HR>
<A HREF="Vcr.java">The source.</A>
</BODY>
</HTML>
figure 7