Section 3.7
Introduction to Applets and Graphics
FOR THE PAST TWO CHAPTERS, you've been learning
the sort of programming that is done inside a single subroutine. In the rest
of the text, we'll be more concerned with the larger scale structure of programs,
but the material that you've already learned will be an important foundation for
everything to come.
In this section, before moving on to programming-in-the-large, we'll
take a look at how programming-in-the-small can be used in other contexts
besides text-based, command-line-style programs. We'll do this by taking
a short, introductory look at applets and graphical programming.
An applet is a Java program that runs on
a Web page. An applet is not a stand-alone application, and it does not
have a main() routine. In fact, an applet is an object
rather than a class. When an applet is placed on a Web
page, it is assigned a rectangular area on the page. It is the job of the
applet to draw the contents of that rectangle. When the region needs to
be drawn, the Web page calls a subroutine in the applet to do so. This is
not so different from what happens with stand-alone programs. When a program
needs to be run, the system calls the main() routine of the
program. Similarly, when an applet needs to be drawn, the Web page calls the
paint() routine of the applet. The programmer specifies what
happens when these routines are called by filling in the bodies of
the routines. Programming in the small! Applets can do other things
besides draw themselves, such as responding when the user clicks the mouse
on the applet. Each of the applet's behaviors is defined by a subroutine
in the applet object. The programmer specifies how the applet behaves
by filling in the bodies of the appropriate subroutines.
A very simple applet, which does nothing but draw itself, can be defined by
a class that contains nothing but a paint() routine. The source
code for the class would have the form:
import java.awt.*;
import java.applet.*;
public class name-of-applet extends Applet {
public void paint(Graphics g) {
statements
}
}
where name-of-applet is an identifier
that names the class, and the statements
are the code that actually draws the applet. This looks similar to the
definition of a stand-alone program, but there are a few things here that need
to be explained, starting with the first two lines.
When you write a program, there are certain built-in classes
that are available for you to use. These built-in classes include
System and Math. If you want to use one of these
classes, you don't have to do anything special. You just go ahead
and use it. But Java also has a large number of standard classes that
are there if you want them but that are not automatically available to
your program. (There are just too many of them.)
If you want to use these classes in your program, you have
to ask for them first. The standard classes are grouped into
so-called "packages." Two of these packages are called
"java.awt" and "java.applet". The directive
"import java.awt.*;" makes all the classes from the package
java.awt available for use in your program. The java.awt package contains
classes related to graphical user interface programming, including
a class called Graphics. The Graphics class is referred
to in the paint() routine above. The java.applet package contains
classes specifically related to applets, including the class named Applet.
The first line of the class definition above says that the class
"extends Applet." Applet is a standard class that is defined in
the java.applet package. It defines all the basic properties and behaviors
of applet objects. By extending the Applet class, the new class we
are defining inherits all those properties and behaviors. We only have to define
the ways in which our class differs from the basic Applet class.
In our case, the only difference is that our applet will draw itself differently,
so we only have to define the paint() routine. This is one of the
main advantages of object-oriented programming.
(Actually, most of our applets
will be defined to extend JApplet rather than Applet.
The JApplet class is itself an extension of Applet. The Applet
class has existed since the original version of Java, while JApplet is part
of the newer "Swing" set of graphical user interface components. For the moment, the
distinction is not important.)
One more thing needs to be mentioned -- and this is a point where
Java's syntax gets unfortunately confusing. Applets are objects, not
classes. Instead of being static members of
a class, the subroutines that define the applet's behavior are part of the
applet object. We say that they are "non-static" subroutines.
Of course, objects are related to classes because every object is described
by a class. Now here is the part that can get confusing: Even though a non-static
subroutine is not actually part of a class (in the sense of being part of the
behavior of the class), it is nevertheless defined in a class (in the sense
that the Java code that defines the subroutine is part of the Java code that
defines the class). Many objects can be described by the same class.
Each object has its own non-static subroutine. But the common definition
of those subroutines -- the actual Java source code -- is physically part of
the class that describes all the objects. To put it briefly: static
subroutines in a class definition say what the class does; non-static
subroutines say what all the objects described by the class do.
An applet's paint() routine is a an example non-static subroutine.
A stand-alone program's main() routine is an example of a static
subroutine. The distinction doesn't really matter too much at this point:
When working with stand-alone programs, mark everything with the
reserved word, "static"; leave it out
when working with applets. However, the distinction between static and
non-static will become more important later in the course.
Let's write an applet that draws something.
In order to write an applet that draws something, you need to
know what subroutines are available for drawing, just as in writing
text-oriented programs you need to know what subroutines are available
for reading and writing text. In Java, the built-in drawing subroutines
are found in objects of the class Graphics, one of the
classes in the java.awt package. In an applet's paint()
routine, you can use the Graphics object g for drawing.
(This object is provided as a parameter to the paint() routine
when that routine is called.) Graphics objects contain
many subroutines. I'll mention just three of them here. You'll find
more listed in Section 6.3.
g.setColor(c),
is called to set the color that is used for drawing. The parameter,
c is an object belonging to a class named Color,
another one of the classes in the java.awt package. About a dozen standard
colors are available as static member variables in the Color class.
These standard colors include Color.black, Color.white,
Color.red, Color.green, and Color.blue.
For example, if you want to draw in red, you would say
"g.setColor(Color.red);". The specified color
is used for all drawing operations up until the next time
setColor is called.
g.drawRect(x,y,w,h)
draws the outline of a rectangle. The parameters x, y,
w, and h must be integers.
This draws the outline of the rectangle whose top-left corner is
x pixels from the left
edge of the applet and y pixels down from the top of the applet.
The width of the rectangle is w pixels, and the height is
h pixels.
g.fillRect(x,y,w,h)
is similar to drawRect except that it fills in the inside
of the rectangle instead of just drawing an outline.
This is enough information to write the applet shown here:
This applet first fills its entire rectangular area with red.
Then it changes the drawing color to black and draws a sequence of
rectangles, where each rectangle is nested inside the previous one.
The rectangles can be drawn with a while loop. Each time
through the loop, the rectangle gets smaller and it moves
down and over a bit. We'll need variables to hold the
width and height of the rectangle and a variable to record how far the
top-left corner of the rectangle is inset from the edges of the
applet. The while loop ends when the rectangle
shrinks to nothing. In general outline, the algorithm for drawing
the applet is
Set the drawing color to red (using the g.setColor subroutine)
Fill in the entire applet (using the g.fillRect subroutine)
Set the drawing color to black
Set the top-left corner inset to be 0
Set the rectangle width and height to be as big as the applet
while the width and height are greater than zero:
draw a rectangle (using the g.drawRect subroutine)
increase the inset
decrease the width and the height
In my applet, each rectangle is 15 pixels away from the rectangle that
surrounds it, so the inset is increased by 15 each time through
the while loop. The rectangle shrinks by 15 pixels on the left and
by 15 pixels on the right, so the width of the rectangle shrinks by 30
each time through the loop. The height also shrinks by 30 pixels each
time through the loop.
It is not hard to code this algorithm into Java and use it to define
the paint() method of an applet. I've assumed that the
applet has a height of 160 pixels and a width of 300 pixels. The size
is actually set in the source code of the Web page where the applet appears.
In order for an applet to appear on a page, the source code for the
page must include a command that specifies which applet to run and
how big it should be. (The commands that can be used on a Web page are
discussed in Section 6.2.) It's not a great
idea to assume that we know how big the applet is going to be. On the
other hand, it's also not a great idea to write an applet that does
nothing but draw a static picture. I'll address both these issues
before the end of this section. But for now, here is the source code
for the applet:
import java.awt.*;
import java.applet.Applet;
public class StaticRects extends Applet {
public void paint(Graphics g) {
// Draw a set of nested black rectangles on a red background.
// Each nested rectangle is separated by 15 pixels on
// all sides from the rectangle that encloses it.
int inset; // Gap between borders of applet
// and one of the rectangles.
int rectWidth, rectHeight; // The size of one of the rectangles.
g.setColor(Color.red);
g.fillRect(0,0,300,160); // Fill the entire applet with red.
g.setColor(Color.black); // Draw the rectangles in black.
inset = 0;
rectWidth = 299; // Set size of first rect to size of applet.
rectHeight = 159;
while (rectWidth >= 0 && rectHeight >= 0) {
g.drawRect(inset, inset, rectWidth, rectHeight);
inset += 15; // Rects are 15 pixels apart.
rectWidth -= 30; // Width decreases by 15 pixels
// on left and 15 on right.
rectHeight -= 30; // Height decreases by 15 pixels
// on top and 15 on bottom.
}
} // end paint()
} // end class StaticRects
(You might wonder why the initial rectWidth is set to 299, instead of
to 300, since the width of the applet is 300 pixels. It's because rectangles are
drawn as if with a pen whose nib hangs below and to the right of the point
where the pen is placed. If you run the pen exactly along the right edge of
the applet, the line it draws is actually outside the applet and therefore is
not seen. So instead, we run the pen along a line one pixel to the left
of the edge of the applet.
The same reasoning applies to rectHeight.
Careful graphics programming demands attention to details like these.)
When you write an applet, you get to build on the work of the people who wrote
the Applet class. The Applet class provides a framework on
which you can hang your own work. Any programmer can create additional frameworks
that can be used by other programmers as a basis for writing specific types of
applets or stand-alone programs. One example is the applets in previous sections
that simulate text-based programs. All these applets are
based on a class called ConsoleApplet, which itself is based on the
standard Applet class. You can write your own console applet
by filling in this simple framework (which leaves out just a couple of bells and whistles):
public class name-of-applet extends ConsoleApplet {
public void program() {
statements
}
}
The statements in the program() subroutine are executed when the user of
the applet clicks the applet's "Run Program" button. This "program" can't
use TextIO or System.out to do input and output. However,
the ConsoleApplet framework provides an object named console for
doing text input/output. This object contains exactly the same set of subroutines
as the TextIO class. For example, where you would say TextIO.putln("Hello
World") in a stand-alone program, you could say console.putln("Hello
World") in a console applet. The console object just displays
the output on the applet instead of on standard output. Similarly, you can substitute
x = console.getInt() for x = TextIO.getInt(), and so on. As a
simple example, here's a console applet that gets two numbers from the user and
prints their product:
public class PrintProduct extends ConsoleApplet {
public void program() {
double x,y; // Numbers input by the user.
double prod; // The product, x*y.
console.put("What is your first number? ");
x = console.getlnDouble();
console.put("What is your second number? ");
y = console.getlnDouble();
prod = x * y;
console.putln();
console.put("The product is ");
console.putln(prod);
} // end program()
} // end class PrintProduct
And here's what this applet looks like on a Web page:
Now, any console-style applet that you write depends on the ConsoleApplet
class, which is not a standard part of Java. This means that the compiled class file,
ConsoleApplet.class must be available to your applet when it is run.
As a matter of fact, ConsoleApplet uses two other non-standard classes,
ConsolePanel and ConsoleCanvas, so the compiled class files
ConsolePanel.class and ConsoleCanvas.class must also be available
to your applet. This just means that all four class files -- your own class and
the three classes it depends on -- must be in the same directory with the source code
for the Web page on which your applet appears.
I've written another framework that makes it possible to write applets
that display simple animations. An example is given by the applet at the
bottom of this page, which is an animated version of the nested squares applet
from earlier in this section.
A computer animation is really just a sequence of still images. The computer
displays the images one after the other. Each image differs a bit from the
preceding image in the sequence. If the differences are not too big and if
the sequence is displayed quickly enough, the eye is tricked into perceiving
continuous motion.
In the example, rectangles shrink continually towards the center of the
applet, while new rectangles appear at the edge.
The perpetual motion is, of course, an illusion. If you think about it,
you'll see that the applet loops through the same set of images over and
over. In each image, there is a gap between the borders of the applet and
the outermost rectangle. This gap gets wider and wider until a new
rectangle appears at the border. Only it's not a new rectangle.
What has really happened is that the applet has started over again with
the first image in the sequence.
The problem of creating an animation is really just the problem of drawing
each of the still images that make up the animation. Each still image is
called a frame. In my framework for animation,
which is based on a non-standard class called SimpleAnimationApplet2,
all you have to do is fill in the code that says how to draw one frame.
The basic format is as follows:
import java.awt.*;
public class name-of-class extends SimpleAnimationApplet2 {
public void drawFrame(Graphics g) {
statements // to draw one frame of the animation
}
}
The "import java.awt.*;" is required to get access to graphics-related
classes such as Graphics and Color.
You get to fill in any name you want for the class, and you get to fill in
the statements inside the subroutine. The drawFrame() subroutine
will be called by the system each time a frame needs to be drawn. All you have to do is say what
happens when this subroutine is called. Of course, you have to draw a different
picture for each frame, and to do that you need to know which frame you are drawing.
The class SimpleAnimationApplet2 provides a function named getFrameNumber()
that you can call to find out which frame to draw. This function returns an integer
value that represents the frame number. If the value returned is 0, you are supposed to
draw the first frame; if the value is 1, you are supposed to draw the second frame, and
so on.
In the sample applet, the thing that differs from one frame to another is
the distance between the edges of the applet and the outermost rectangle. Since
the rectangles are 15 pixels apart, this distance increases from 0 to 14 and then
jumps back to 0 when a "new" rectangle appears. The appropriate
value can be computed very simply from the frame number, with the
statement "inset = getFrameNumber() % 15;". The value of the
expression getFrameNumber() % 15 is between 0
and 14. When the frame number reaches 15, the value of getFrameNumber() % 15
jumps back to 0.
Drawing one frame in the sample animated applet is very similar to drawing the single
image of the StaticRects applet, as given above. The paint() method
in the StaticRects applet becomes, with only minor modification, the drawFrame() method
of my MovingRects animation applet. I've chosen to make one improvement:
The StaticRects applet assumes that the applet is 300 by 160 pixels. The
MovingRects applet will work for any applet size. To implement this,
the drawFrame() routine has to know how big the applet is.
There are two functions that can be called to get this information. The
function getWidth() returns an integer value representing the width
of the applet, and the function getHeight() returns the height.
The width and height, together with the frame number, are used to compute the
size of the first rectangle that is drawn. Here is the complete source code:
import java.awt.*;
public class MovingRects extends SimpleAnimationApplet2 {
public void drawFrame(Graphics g) {
// Draw one frame in the animation by filling in the background
// with a solid red and then drawing a set of nested black
// rectangles. The frame number tells how much the first
// rectangle is to be inset from the borders of the applet.
int width; // Width of the applet, in pixels.
int height; // Height of the applet, in pixels.
int inset; // Gap between borders of applet and a rectangle.
// The inset for the outermost rectangle goes
// from 0 to 14 then back to 0, and so on,
// as the frameNumber varies.
int rectWidth, rectHeight; // The size of one of the rectangles.
width = getWidth(); // Find out the size of the drawing area.
height = getHeight();
g.setColor(Color.red); // Fill the frame with red.
g.fillRect(0,0,width,height);
g.setColor(Color.black); // Switch color to black.
inset = getFrameNumber() % 15; // Get the inset for the
// outermost rect.
rectWidth = width - 2*inset - 1; // Set size of outermost rect.
rectHeight = height - 2*inset - 1;
while (rectWidth >= 0 && rectHeight >= 0) {
g.drawRect(inset,inset,rectWidth,rectHeight);
inset += 15; // Rects are 15 pixels apart.
rectWidth -= 30; // Width decreases by 15 pixels
// on left and 15 on right.
rectHeight -= 30; // Height decreases by 15 pixels
// on top and 15 on bottom.
}
} // end drawFrame()
} // end class MovingRects
(The SimpleAnimationApplet2
class uses Swing and requires Java version 1.3 or better. There is an older version
named SimpleAnimationApplet
that provides the same functionality but works with any version of Java.
You could use SimpleAnimationApplet to write animations that will work
on older Web browsers.)
The main point here is that by building on an existing framework, you
can do interesting things using the type of local, inside-a-subroutine
programming that was covered in Chapters 2 and 3. As you learn more about
programming and more about Java, you'll be able to do more on your
own -- but no matter how much you learn, you'll always be dependent
on other people's work to some extent.
End of Chapter 3