Applets and Graphics
So far, we know how to create stand-alone programs that are based on a Frame. We have
learned about several layout managers (FlowLayout, BorderLayout, GridLayout), and we have
seen examples of GUI elements (Button, Label, TextField, TextArea). Now we will start
creating applets, and intrudocue the last commonly used classes in the awt, Graphics and
Canvas. We will also experiment more with mixing various layouts using the Panel class.
Applets
An applet is a small program that executes inside another program. In our case, every
applet must extend the Applet class, which is defined by the awt as follows:
public class Applet extends Panel
{ // Constructors
public Applet();
// selected Methods
public AppletContext getAppletContext();
public String getAppletInfo();
public AudioClip getAudioClip(URL url, String name);
public URL getCodeBase();
public URL getDocumentBase();
public Image getImage(URL url, String name);
public String getParameter(String name);
public void init();
public boolean isActive();
public void play(URL url, String name);
public void showStatus(String msg);
public void start();
public void stop();
}
- This is certainly a lot of info, and many of these methods may sound intruiging.
However, let's first concentrate on the basics:
Applet Basics:
| An applet contains three important methods: init(), start(), and stop() |
| init() is called once, when the applet is loaded into memory |
| start() tells the applet to start execution; it is called every time a user visits the
page containing a loaded applet |
| stop() tells the applet to stop execution; it is called every time a user leaves a page
containing a started applet |
| An applet can not be resized. Applets are embedded into web pages, and they will get a
fixed height and width. Layout managers will still try to arrange components inside an
applet as best as possbible, but sometimes that can be difficult |
| Applets do not need a constructor, and they do not need a main method to execute; the
init and start methods can substitute for them |
| Every class that wants to be an applet must import java.applet.Applet, and extend Applet |
| An applet is initially visible. Therefore, the 'show()' method is not needed. Also, the
'pack()' method is not needed as well. |
With this information in hand, let's redo our last 'simple' adding machine as an applet.
Recall the last code for the adding machine:
import java.awt.*;
public class AddingMachine extends Frame
{ // fields
private Button add = new Button("Add"),
reset = new Button("Reset"),
quit = new Button("Quit");
private TextField output = new TextField(6),
input = new TextField(6);
private Label outLabel = new Label("Sum:"),
inLabel = new Label("Add:");
private int sum = 0;
// constructor
public AddingMachine(String t)
{
super(t);
setup();
pack();
show();
}
// methods
public boolean action(Event e, Object o)
{
if (e.target == quit)
System.exit(0);
else if (e.target == add)
{
sum += Integer.parseInt(input.getText());
output.setText(String.valueOf(sum));
}
else if (e.target == reset)
{
sum = 0;
output.setText("");
}
return false;
}
private void setup()
{
Panel info = new Panel(); // panel for the number and labels
Panel buttons = new Panel(); // panel for the buttons
info.setLayout(new GridLayout(2,2)); // methods of the info panel
info.add(inLabel);
info.add(input);
info.add(outLabel);
info.add(output);
buttons.setLayout(new FlowLayout()); // methods of the buttons panel
buttons.add(add);
buttons.add(reset);
buttons.add(quit);
setLayout(new BorderLayout()); // methods of the frame
add("South",buttons);
add("Center",info);
}
}
Actually, we have also used different layouts for this applet, and the above code
reflects the 'best looking' layout for our purpose. To turn this class into an applet, we
need to do the following:
| import java.applet.* |
| change the class from "extends Frame" to "extends Applet" |
| change the constructor method to "public void init()" |
| remove the constructor of the superclass (only constructors can call other constructors,
but our method has just been renamed to 'init()', and is therefore no longer a
constructor) |
| remove the method calls to 'pack' and 'show' |
Here is the new code for our applet:
import java.awt.*;
import java.applet.*;
public class AddingMachine extends Applet
{ // fields - no change at all
// constructor changed to init method
public void init()
{
setup();
}
// no change in action and setup methods for now
}
That's it, we now have an applet that can be embedded into a Web page. Here's how:
Embedding an Applet into a Web page
To embed and applet into a web page, create an HTML document as usual. Then, at the
place where you want the applet to appear, but in the following tags as a minimum:
<APPLET CODE="ClassName.class"
WIDTH="nnn"
HEIGHT="nnn">
</APPLET>
In other words, a very simple HTML document containing the above applet would look like
this:
<HTML>
<H3>My very first Applet</H3>
<HR>
<CENTER>
<APPLET CODE="AddingMachine.class"
WIDTH="300"
HEIGHT="150">
</APPLET>
</CENTER>
</HTML>
Save this file, using any name, say, "AddTest.html", in the same
directory as the class file for the AddingMachine applet, and load it into your web
browser. You should see the AddingMachine applet inside your web page.
Alternatively, you can also use a small program called 'appletviewer' to view an applet
quickly. The appletviewer comes with the JDK, and it ignores all HTML code except the
<APPLET> tags. It can be used to quickly check whether an applet is working
correctly. To use it, simply type, at the command line:
appletviewer AddTest.html
Applet Details
When you test the above applet, you will notice at least two things:
| the input/output textfields are "too large" |
| the quit button does not "work" |
The first problem is due to the fact that an applet can not automatically resize itself.
It will simply try to fit into the space allocated as best as possible. Therefore, you
sometimes have to adjust the HEIGHT and WIDTH fields of the applet tag for a while before
finding the optimal dimensions.
The second problem is due to "security restrictions" on applet. Applets have
several restrictions - things that applets are not allowed to do. One such restriction is
that an applet is not allowed to call the System.exit() method. In fact, if you
bring up the Java console (under the Options menu), you will see that a security violation
appears every time you press the "Quit" button.
Finally, in every applet you should think about using the start and/or stop methods, if
applicable. In our case, we could use the 'start' method to reset the computations every
time a user visits this page.
Here is the new code, with the "Quit" button removed, and the Start method
added. For convenience, the entire code is listed again.
import java.awt.*;
import java.applet.*;
public class AddingMachine extends Applet
{ // fields
private Button add = new Button("Add"),
reset = new Button("Reset");
private TextField output = new TextField(6),
input = new TextField(6);
private Label outLabel = new Label("Sum:"),
inLabel = new Label("Add:");
private int sum = 0;
// init method
public void init()
{
setup();
}
// public methods
public void start()
{
boolean dummy = handleReset();
}
public boolean action(Event e, Object o)
{
if (e.target == add)
return handleAdd();
else if (e.target == reset)
return handleReset();
else
return true;
}
// private methods
private boolean handleAdd()
{
sum += Integer.parseInt(input.getText());
output.setText(String.valueOf(sum));
return true;
}
private boolean handleReset()
{
sum = 0;
output.setText("0");
input.setText("0");
return true;
}
private void setup()
{
Panel info = new Panel(); // panel for the number and labels
Panel buttons = new Panel(); // panel for the buttons
info.setLayout(new GridLayout(2,2)); // methods of the info panel
info.add(inLabel);
info.add(input);
info.add(outLabel);
info.add(output);
buttons.setLayout(new FlowLayout()); // methods of the buttons panel
buttons.add(add);
buttons.add(reset);
setLayout(new BorderLayout()); // methods of the frame
add("South",buttons);
add("Center",info);
}
}
Note: In addition to removing the "Quit" button and adding the start()
method, we have done several other changes:
| We have removed the "Quit" button as a field, in the action method, and in the
setup method |
| We have added a 'public void start()' method. That method is automatically called every
time the page is revisited. It means that if you look at the page with the applet, do a
few calculations, switch to another page, then return to the page with the applet, the
computations have automatically been reset. |
| We have added two private methods handleReset and handleAdd. Those methods are called by
the action method when the appropriate buttons are called. That's good programming
practice. For one thing, the handleReset method is called when the 'Reset' button is
pressed, and when the 'start' method is executed (automatically every time you re-visit
the page with this applet). We can now change the handleReset methd (which indeed we did) only
once and the new code will apply at both places. In general, it is always good to
create small method for individual tasks. In other words, rather than letting the 'action'
method do a lot of work, it should rather delegate the work to other methods, depending on
what action happens. The actual work should take place in - usually - private methods. |
| We have changed what the action method returns. It now returns true only if it actually
handles the event, false otherwise. That's good programming practice. The action
method should only return true when it actually handles an event. It should return
false otherwise. An even better way to do it is to have the action method return
'super.handleEvent(e, o)' if it does not actually handle the event. That gives the action
method of the superclass a chance to handle the event, if necessary. |
| Note that our private methods 'handleAdd' and 'handleReset' now return the boolean
variable true. That allows us to shorten the code of the action method, but it forces us
to add a dummy variable when calling the 'handleReset' method in the 'start' method.
That's no big loss, and the shortening of the action method more than makes up for the
introduction of one dummy boolean variable in the start method. |
From now on we will concentrate on applets, but it should be clear how to convert an
applet into a standalone program, if necessary. To change an Applet to a Frame, simply
reverse the above steps of turning a frame into an applet, i.e.:
| remove the "import java.applet.*" line |
| make sure your class extends Frame rather than Applet |
| change the 'public void init()' method to a constructor |
| add a call to the superclass constructor, if necessary, and calls to 'pack()' and
'show()', if necessary, to show your applet |
| for Frames, the 'start()' and 'stop()' methods are no longer executed automatically. You
can usually leave them as they are, but they do not serve any purpose any more. They are
only called automatically for applets. |
| Add a way to quit the program, for example a "Quit" button. Make sure that you
call the System.exit(0) method inside the action method if a user clicks on that Quit
button. To do that nicely, you should introduce a private method 'public boolean
handleQuit()', similar to the other private 'handleWhatever' methods. |
Drawing Graphics
Next, we want to discuss how to draw graphics inside a program or applet. Before
drawing graphics, we need to understand what methods are, by default, executed for the
basic applet. We already know about init, start, and stop, but there's a little more:
| When applet initializes: |
| public void init() |
| When leaving page with applet |
| public void stop() |
| When entering page with previously initialized appet |
| public void start() |
| When an action event happens: |
| public boolean action(Event e, Object arg) |
| When another event happens: |
| public boolean handleEvent(Event e) |
| When the applet needs updating: |
| public void repaint() |
| When an applet calls repaint() |
| public void paint(Graphics g) |
We have already seen most of these methods except the repaint() and the paint() method.
What the repaint() method will do is, to quote the Java API:
"Repaints this component. This method causes a call to this component's update
method as soon as possible."
Thus, to understand this, we need to consult the Java API about the update method and we
find:
public void update(Graphics g)
Updates this component. The AWT calls the update method in response to a call to
repaint. The appearance of the component on the screen has not changed since the last call
to update or paint.
The update method of Component:
1.Clears this component by filling it with
the background color.
1.Sets the color of the graphics context to be
the foreground color of this component.
2.Calls this component's paint method to
completely redraw this component.
The coordinate of the
graphics context is the top-left corner of this components.
The clipping region of the graphics
context is the bounding rectangle of this
component
Parameters:
g -
the graphics context to use for updating
So, to finish this, we need to look at the paint method, which is defined as:
public void paint(Graphics g)
Paints this component. Most application components, including applets, override this
method.
The coordinate of the graphics context is the top-left corner of this components.
The clipping region of the graphics context is the bounding rectangle of this component.
What does this mean: it simply means that if we want to do any graphics, we
can simply override the object's paint method and handle all drawing in that method.
That will then automatically ensure that our graphics always appears and is redrawn
whenever necessary. That works, because the 'repaint()' method is called automatically if
an applet (or frame) needs updating. That method in turn automatically calls 'update', and
that method clears the drawing area and automatically calls 'paint'. If we override
'paint', therefore, our drawing happens automatically, at the proper time. Here is, for
example, our applet from the very beginning of class:
import java.applet.Applet;
import java.awt.*;
public class HelloWorld extends Applet
{
public void paint(Graphics g)
{
g.drawString("Hello World", 10, 10);
}
}
This applet will draw the string Hello Word, starting at x-coordinate 10 and
y-coordinate 10 inside the applet, where the top left corner has coordinates (0,0). Of
course we need to create an appropriate HTML file before we can see this applet inside a
web page, or using the appletviewer.
For more details on drawing graphics, here's a look at selected methods of the Java API
on the Graphics class:
public void draw3DRect(int x, int y, int width, int height, boolean raised);
public abstract void drawLine(int x1, int y1, int x2, int y2);
public abstract void drawOval(int x, int y, int width, int height);
public abstract void drawPolygon(int xPoints[], int yPoints[], int nPoints);
public void drawPolygon(Polygon p);
public void drawRect(int x, int y, int width, int height);
public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight);
public abstract void drawString(String str, int x, int y);
public void fill3DRect(int x, int y, int width, int height, boolean raised);
public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle);
public abstract void fillOval(int x, int y, int width, int height);
public abstract void fillPolygon(int xPoints[], int yPoints[], int nPoints);
public void fillPolygon(Polygon p);
public abstract void fillRect(int x, int y, int width, int height);
public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight);
public abstract Color getColor();
public abstract Font getFont();
public FontMetrics getFontMetrics();
public abstract FontMetrics getFontMetrics(Font f);
public abstract void setColor(Color c);
public abstract void setFont(Font font);
Now let's try to design a slightly more interesting applet:
Example:
We want to design an applet that will draw a rectangle in some color on the screen. It
will also have four buttons, up, down, right, left. You can then move the rectangle with
these buttons in the corresponding direction. We'll also add two more buttons to increase
or decrease the size of the rectangle.
- Let's start out easy, i.e. without worrying about these buttons, and moving the
rectangle. Let's just try to draw it at some specific coordinages, at a specific size. We
therefore need, as fields:
| x and y coordinates for the location of the rectangle |
| width and height numbers for the size of the coordinates |
| a field of type Color for the color of the rectangle |
Here's the code:
import java.applet.Applet;
import java.awt.*;
public class MoveBox extends Applet
{
private int x = 20, y = 20;
private int width = 10, height = 10;
private Color myColor = Color.red;
public void paint(Graphics g)
{
g.setColor(myColor);
g.fillRect(x,y,width,height);
}
}
Note that at this point, we do not need the 'init', 'start', or 'stop' method; the
fields are initialized as they are declared, and nothing changes anyway, so no need for
those methods right now. To test this, don't forget to create the appropriate HTML file.
Next, let's add the four buttons to move the rectangle around. That will mean
that we also need to implement the 'init' method, to choose a layout for those buttons.
Finally, we will introduce an extra field to specify how much the retangle should move
with each click on a button.
import java.applet.Applet;
import java.awt.*;
public class MoveBox extends Applet
{
private int x = 20, y = 20;
private int width = 10, height = 10;
private int inc = 5;
private Color myColor = Color.red;
private Button up = new Button("Up");
private Button down = new Button("Down");
private Button left = new Button("Left");
private Button right = new Button("Right");
// init method
public void init()
{
Panel buttons = new Panel();
buttons.setLayout(new FlowLayout());
buttons.add(up);
buttons.add(down);
buttons.add(left);
buttons.add(right);
setLayout(new BorderLayout());
add("South", buttons);
}
// public methods
public boolean action(Event e, Object o)
{
if (e.target == up)
return handleUp();
else if (e.target == down)
return handleDown();
else if (e.target == left)
return handleLeft();
else if (e.target == right)
return handleRight();
else
return super.action(e, o);
}
public void paint(Graphics g)
{
g.setColor(myColor);
g.fillRect(x,y,width,height);
}
// private methods
private boolean handleUp()
{
y -= inc;
repaint();
return true;
}
private boolean handleDown()
{
y += inc;
repaint();
return true;
}
private boolean handleRight()
{
x += inc;
repaint();
return true;
}
private boolean handleLeft()
{
x -= inc;
repaint();
return true;
}
}
Note that we use the private 'handleWhatever' methods, following the Good
Programming Practice hints we mentioned earlier.
Finally, for our finished applet so far, we should add two more buttons to change the
size of the rectangle. But, you may have also noticed a flaw in our applet: the rectangle
can now disappear when moving it passed the borders of the applet. We should modify our
code so that the rectangle appears on the left side if it is moved passed the right side,
and on the right side if it is moved passed the left side. For that, we need to know,
inside the applet, the width and height of the applet. A call to the 'super.height' and
'super().width' fields will reveal those numbers. Here is the new code.
import java.applet.Applet;
import java.awt.*;
public class MoveBox extends Applet
{
private int x = 20, y = 20;
private int width = 10, height = 10;
private int inc = 5;
private Color myColor = Color.red;
private Button up = new Button("Up");
private Button down = new Button("Down");
private Button left = new Button("Left");
private Button right = new Button("Right");
private Button increase = new Button("[+]");
private Button decrease = new Button("[-]");
// init method
public void init()
{
Panel buttons = new Panel();
buttons.setLayout(new FlowLayout());
buttons.add(up);
buttons.add(down);
buttons.add(left);
buttons.add(right);
buttons.add(increase);
buttons.add(decrease);
setLayout(new BorderLayout());
add("South", buttons);
}
// public methods
public boolean action(Event e, Object o)
{
if (e.target == up)
return handleUp();
else if (e.target == down)
return handleDown();
else if (e.target == left)
return handleLeft();
else if (e.target == right)
return handleRight();
else if (e.target == increase)
return handleIncrease();
else if (e.target == decrease)
return handleDecrease();
else
return super.action(e, o);
}
public void paint(Graphics g)
{
g.setColor(myColor);
g.fillRect(x,y,width,height);
}
// private methods
private boolean handleUp()
{
y = y - inc;
repaint();
return true;
}
private boolean handleDown()
{
y = y + inc;
repaint();
return true;
}
private boolean handleRight()
{
if (x < size().width)
x = x + inc;
else
x = 0;
repaint();
return true;
}
private boolean handleLeft()
{
if (x > 0)
x = x - inc;
else
x = size().width;
repaint();
return true;
}
private boolean handleIncrease()
{
width += 5;
height += 5;
repaint();
return true;
}
private boolean handleDecrease()
{
if (width > 5)
{
width -= 5;
height -= 5;
}
repaint();
return true;
}
}
Note that have made sure that the size of the rectangle can not shrink too much. Is it
clear how the entire code works, especially the 'wrapping' around the left and right sides
? Actually, to be picky, the 'wrapping' does not work quit perfectly. On the right side,
it wraps 'too late', and on the 'left side, it 'wraps' too early. Try it to see what I
mean.
Actually, we should also change it so that the rectangle can 'wrap' around the top and
buttom borders as well - we will leave that up to you to try for yourself. While you are
at it, also add two addition buttons, and the necessary code, to make the rectangle move
faster or slower when clicking on the apppropriate buttons. That, again, is left to you.
(bgw)