All Categories :
Java
Chapter 3
Building a Spreadsheet Applet
CONTENTS
This chapter starts with an overview of AWT
(the Abstract Window Toolkit) and its general features and then
details the basic structure of AWT and its most important classes.
A discussion of more advanced aspects of exception handling follows.
AWT and exception handling will be important in developing the
first version of the spreadsheet applet, discussed in detail in
the last section of this chapter. The visual and user-interface
features of the applet will rely on AWT. The underlying spreadsheet
engine will make active use of exception handling, particularly
when validating and calculating formulas.
AWT forms the basis for graphical user-interface programming in
Java. The AWT package offers a large variety of tools for creating
graphic widgets such as buttons, list boxes, and scrollbars. A
graphics class can be used for two-dimensional drawing operations,
such as displaying polygons, painting text, and setting fonts
and colors, and graphical operations, such as clipping and scaling.
Beyond all this, AWT provides an underlying foundation for interfacing
with the user. A series of methods handle events produced by the
user and the system, such as mouse clicks and keystrokes. In short,
AWT gives you a set of tools for writing simple applets and a
basis for developing classes that can be used to create more sophisticated
programs. Chapters 4, "Enhancing the
Spreadsheet Applet,"and 5, "Adding Graphs and Scrollbars
to the Spreadsheet," will have the other parts of the AWT
tutorial.
Figure 3.1 illustrates the hierarchy of the most important classes
in the AWT package; they are used for a variety of services:
Figure 3.1 : Hierarchy of significant AWT classes.
- Component is the foundation of controls such as buttons
and labels. It is also the superclass of more sophisticated controls,
such as dialog boxes, and even the Applet class.
- Container is a class that contains Components or other
Containers.
- Panel is a visual Container that can be used to hold
other components, such as buttons, list boxes, and other Containers.
- Applet is the base class for creating an applet. It
starts the program and ties it to the native browser.
- Window is used for popup-style components, such as
dialog boxes.
- Font can be used to create fonts customized by such
features as point size and style.
- Event encapsulates user- and system-initiated events,
such as mouse clicks, keyboard strokes, and the shutdown of an
applet.
- Graphics is mainly used when a Component needs painting.
This class encapsulates a wide range of functions, including drawing
polygons, text, and images, plus setting the Fonts and Colors
to create what is drawn.
- MenuComponent provides the foundation for creating
drop-down, checkbox, and other menus.
Most of these classes are discussed and illustrated in more detail
throughout this and subsequent chapters.
Component classes are used for coordinating all aspects of a visual
control. A variety of Component methods can be used to process
events, enable or disable a control, set fonts and colors, and
manage the control's visual display. The most widely used Components
will be those simple ones that are part and parcel of creating
a user interface. These include labels, buttons, list boxes, and
choice menus. However, more sophisticated Component subclasses
can be used to manage these primitive controls. The following
list shows the primitive Component classes; these simpler classes
are derived directly from Component.
| Primitive Component Classes |
Button
Canvas
Checkbox
CheckboxGroup
Choice
Label
List
Scrollbar
TextComponent
TextArea (extends TextComponent)
TextField (extends TextComponent)
|
The Component hierarchy complements these primitive controls with
classes based on the Container class. Containers are used to hold
Component classes and other Containers. Panels, Windows, Dialogs,
and Applets are all notable Container subclasses. The Container
classes are presented in more detail shortly.
You can use the primitive controls in Table 3.1 to quickly produce
a functional applet. Figure 3.2 shows such an applet; with five
components, it enables the user to choose what to display and
when to display it. A choice between painting nothing, a rectangle,
or text is offered. The class that does the drawing is a custom
subclass of the Canvas Component called DrawCanvas, located in
the middle of the applet. At the bottom of the screen is a Panel
with Button and Choice objects. The Choice menu object lets the
user decide what is to be painted when the Button object is clicked
(or when the applet repaints). A TextField object at the top of
the screen is used to specify which text will be displayed, if
text is used.
Figure 3.2 : Example with five companents
Listing 3.1 provides the code for this example. Although the applet
is simple, it illustrates a lot of features. The init()
portion of the applet class (called Example1)
shows how easy it is to create and add classes to the applet display.
The handleEvent() method
overrides the default event handler and is used to trap button
clicks. The code checks to see whether the action came from its
button. If so, it forces the DrawCanvas object to repaint. The
user's selection in the Choice box will then be reflected.
The DrawCanvas class inherits the features of Canvas and adds
its own custom functions to it. When it is drawn with the paint()
method, the class queries the applet about the user's selections
and paints accordingly. This paint()
method can be issued either when the user clicks the button or
when a certain region of the canvas needs to be redrawn. The paint()
method is part of all Component classes. It must be overridden,
as in the DrawCanvas class, if any custom drawing needs to be
done.
The paint() method takes
as its sole parameter an instance of the Graphics class. This
class offers different methods that can be used to set the features
of the area being drawn. For example, the DrawCanvas class uses
the drawString() method to
put some text up on the drawing area. The drawRect()
and fillRect() methods paint
a rectangle that draws a border rectangle and its interior, respectively.
These rectangles are painted a color established by the setColor()
method. Since the paint()
method and the Graphics class are at the basis of drawing AWT
images, you will see more examples of these throughout the rest
of the book.
Listing 3.1. An applet with five components.
import java.awt.*;
import java.lang.*;
import java.io.*;
import java.applet.*;
// This program illustrates a simple applet with a
TextField,
// Panel, Button, Choice menu, and Canvas.
public class Example1 extends Applet {
TextField tf;
DrawCanvas c;
Button drawBtn;
Choice ch;
// Add the Components to the screen...
public void init() {
// Set up display area...
resize(300,200);
setLayout(new BorderLayout());
// Add the components...
// Add the text at the top.
tf = new TextField();
add("North",tf);
// Add the custom Canvas
to the center
c = new DrawCanvas(this);
add("Center",c);
// Create the panel
with button and choices at the
bottom...
Panel p = new Panel();
drawBtn = new Button("Draw Choice Item");
p.add(drawBtn);
// Create the choice box and add the options...
ch = new Choice();
ch.addItem("Rectangle");
ch.addItem("Empty");
ch.addItem("Text");
p.add(ch);
add("South",p);
}
// Handle events that have occurred
public boolean handleEvent(Event evt) {
switch(evt.id) {
// This can be handled
case Event.ACTION_EVENT: {
if(evt.target instanceof Button) {
// Repaint canvas to use new choices...
c.repaint();
} // end if
return false;
}
default:
return false;
}
}
// Return the current choice to display...
public String getChoice() {
return ch.getSelectedItem();
}
// Return the text in the list box...
public String getTextString() {
return tf.getText();
}
}
// This is a custom canvas that is used for drawing
// text, a rectangle, or nothing...
class DrawCanvas extends Canvas {
Example1 e1app;
// Constructor - store the applet to get drawing
info...
public DrawCanvas(Example1 a) {
e1app = a;
}
// Draw the image per
the choices in the applet...
public synchronized void paint (Graphics g) {
// Get the current size of the display area...
Dimension dm = size();
// Draw based on choice...
String s = e1app.getChoice();
// Calculate center coordinates...
int x,y,width,height;
x = dm.width/4;
y = dm.height / 4;
width = dm.width / 2;
height = dm.height / 2;
// Paint a rectangle in the center...
if (s.compareTo("Rectangle") == 0) {
// Draw the rectangle in the center with colors!
g.setColor(Color.blue);
g.drawRect(x,y,width,height);
g.setColor(Color.yellow);
g.fillRect(x + 1,y + 1,width - 2,height - 2);
} // end if
// Get the text in the applet and display in the
middle...
if (s.compareTo("Text") == 0) {
String displayText = e1app.getTextString();
g.setColor(Color.red);
g.drawString(displayText,x,y + (height/2));
}
}
}
The other Component in this example is a Panel, which is a subclass
of Container. In the example, the Panel contains the Button and
Choice objects. Containers are good for managing a group of Components
and have a special meaning in AWT regarding how an object is displayed.
Containers function as a broker for how a component within it
is presented. If a component's display coordinates are outside
the region of a container, it is clipped. More importantly, Containers
provide a mechanism for how an object is presented, especially
its size and position. This is closely tied to how Layouts work,
discussed briefly in the following section.
One interesting aspect of AWT is that the Applet class is derived
from Panel. This may seem unusual at first; however, an Applet
really functions as a Container. Objects that do not fit within
its area are clipped in display, and if the Applet is destroyed,
so are the objects within it. Given this, the Applet class can
be easily understood as simply a Panel with additional functions
that tie it to the workings of the native browser.
The Window, Frame, and Dialog classes are also Containers that
figure prominently. These are used to create objects that "pop
up" outside the space of the applet, giving a multidimensional
feel to an otherwise "flat" Web page. These classes
will be discussed in more detail in the next chapter.
For someone who has not seen Java applet code before, one question
that might immediately come to mind from the previous example
is: How does the program know where to position the Component
objects on the screen? After all, there is no coordinate information
in the code. And what do code expressions like add("North",
tf) mean?
The key to understanding the previous code and how AWT displays
components in general is a knowledge of layouts. Containers rely
on layouts to give you a way to determine the size and position
of the components within it. Every container is tied to a single
instance of a layout. The layout that a container uses is either
set by default or through programming. In Listing 3.1, the layout
of the Applet object, Example1, is set to an instance of the BorderLayout
class by the line:
setLayout(new BorderLayout());
Since the Panel class is also a container, it too has a layout.
The default class, FlowLayout, is used in the example.
Layouts are an important part of AWT because they take care of
a major issue in Java: portability. Although the language portion
of Java takes care of software portability concerns, it cannot,
by nature, take care of visual portability issues. Namely, how
can Java guarantee that an applet developed on a specific GUI
(such as Windows 95) and on a specific monitor resolution (such
as VGA) have a proper look and feel on other platforms? The AWT
package was designed to solve this problem. By providing a level
of abstraction above the native GUI, it can hide the specifics
of the underlying environment from the developer. Layouts are
key elements of the visual part of the abstraction mechanism.
By taking control of the sizing and positioning of components,
layouts free the developer from having to worry about how to write
an applet that looks good on the variety of monitor resolutions
in the field. Layouts dynamically calculate how to present a component
by looking at the native display-coordinate system at runtime;
the sizing and positioning of the components is based on this
runtime information.
All layouts are derived from the LayoutManager interface, which
specifies how a layout needs to function. AWT provides five classes
(described in the following sections) that set up the LayoutManager
interface. This range of layouts allows the developer to choose
the appropriate layout manager for the requirements at hand; if
these choices aren't enough, a new class can be written. In fact,
Java developers have already created several custom layout classes.
Various incarnations of a RelativeLayout class have been created
that lets you state where components go in relation to each other.
By navigating the Java home pages on the Internet, you might find
a layout class that better fits your development needs. Some of
the entry points to Java on the Internet are mentioned in this
book's Introduction.
Border Layout
In Listing 3.1, the applet specifies adding new components through
an unusual scheme that uses such terms as "North" and
"South". This scheme reflects how the BorderLayout class
works. In the example, BorderLayout is tied to the Example1 applet
class when the setLayout()
method is invoked. This method indicates that the Example1 object,
which is an instance of Container, is tied to the BorderLayout
object. When a Container uses BorderLayout, a Component is added
through a command of this form:
add(String direction, Component);
Direction is one of the following Strings: "North",
"South", "East",
"West", and "Center".
In short, the BorderLayout class uses a directional scheme to
position a component based on one of the five direction strings.
A component set to the "North"
direction is set to the top of the container, one that is set
to "South" is positioned
at the bottom, and so forth. The size of the components is determined
by other runtime information, such as the size of the container
(usually set by the resize()
method) and the attributes of the displayed component. The default
behavior of BorderLayout gives the component set to the "Center"
direction any space not used by the other components. In Listing
3.1, the Example1 applet dimensions are set to 300¥200
pixels. The TextField and Panel objects are relatively small and
so are set comfortably at the top and bottom of the applet. The
remaining display is then used for the "Center"
component, the DrawCanvas object. This object takes up the bulk
of the applet display area.
That the BorderLayout class can display up to only five components
(corresponding to the five directions) might concern users; however,
this is not really a problem. Recall that since containers can
hold other containers, this limitation does not really exist.
Consequently, an applet can contain Panel objects, which in turn
can contain other containers, and so forth.
In the example, the Panel class is used to align the button and
choice box on the same row. The Panel class can be used as a kind
of "toolbar," displaying components along the top or
bottom of the screen. It exemplifies this behavior because its
default layout is the FlowLayout class (see the following section).
Flow Layout
The FlowLayout class is good for displaying components horizontally
across the container. It is the default layout for Panels, which
can be set to function like a toolbar or to contain related Components,
such as OK and Cancel buttons. In most cases, the FlowLayout class
will present components across a single row. However, if the components
do not fit on one row, a new row is started.
Figure 3.3 illustrates a modification of Listing 3.1, which adds
another Choice menu to the left of the button (whose text has
been modified). The canvas uses this new Choice object to pick
the color used to paint the object chosen in the other Choice
menu. Since the order of when a component is added to a container
is important in determining the layout display, the color Choice
object is added before the button.
Figure 3.3 : Second version, illustrating FlawLayout.
The following code modifies the init()
method in Listing 3.1 to build the Panel object shown in Figure
3.4. It is inserted in the source code after the DrawCanvas object
is created, but before the Button is made.
// Create the panel with button and choices
at
bottom...
Panel p = new Panel();
// Explicitly set to flow layout...
p.setLayout(new FlowLayout());
// Add color choice to left of button...
colorChoice = new Choice();
colorChoice.addItem("Yellow");
colorChoice.addItem("Red");
colorChoice.addItem("Blue");
p.add(colorChoice);
// Add button...
The code explicitly creates a new FlowLayout object for the Panel
to use. This is unnecessary, but it illustrates how a FlowLayout
object could be set up for Container objects in general.
To use the color Choice menu in the DrawCanvas code, add a method
to the Example1 class to return the color selection:
// Get the color to be displayed....
// Convert the String to a Color object
Color getColor() {
String s = colorChoice.getSelectedItem();
if (s.compareTo("Yellow") == 0)
return Color.yellow;
if (s.compareTo("Red") == 0)
return Color.red;
return Color.blue;
}
The DrawCanvas class modifies its color code to get the color
choice from the applet class:
g.setColor(e1app.getColor());
You can specify other features of FlowLayout to customize its
appearance. By default, components within a container using FlowLayout
are aligned along the center. However, alternative FlowLayout
constructors can be used to align the components to the left or
the right. Figure 3.4 shows how the Panel in the example would
look if it were right-aligned. The following code line is all
you need to add to modify how the layout is established:
p.setLayout(new FlowLayout(FlowLayout.RIGHT));
You can specify the number of pixels between the components in
a container using FlowLayout in an alternative constructor. This
difference in the spacing between the components is known as the
gap value. Both the horizontal and vertical gap values
can be set in FlowLayout. Most of the LayoutManager classes support
setting gaps. The discussion of the next layout, GridLayout, will
illustrate how to use gap values.
Figure 3.4 : The FlawLayout panel is right-justified.
GridLayout
The GridLayout class is used to set a matrix of components along
a number of rows and columns. Since the size of each row and column
is the same, each component in the grid has the same size. Each
new component added to the container using GridLayout is positioned
to the next index in the grid. If the row is not full, it is added
to the next column; if it is, a new row is started and the component
is added to the new column.
Figure 3.5 illustrates an applet set to use GridLayout. It is
a 5¥5 matrix of Buttons. Listing
3.2 shows the code used to create this applet and illustrates
the use of gap values. In this case, a horizontal gap of 10 and
a vertical gap of 20 is specified in the constructor.
Listing 3.2. Creating an example using GridLayout.
import java.awt.*;
import java.lang.*;
import java.applet.*;
// Class used for illustrating Grid Layouts...
public class GridLayoutExample extends Applet {
// Set up a matrix of numbers to be displayed in a
grid...
public void init() {
// Set up display area...
resize(300,200);
// Set the layout to a 5 by 5 grid with
// a horizontal gap of 10 and a vertical gap of 20
int rowsAcross = 5;
int rowsDown = 5;
setLayout(new GridLayout(rowsAcross,rowsDown,10,20));
// Fill the grid with buttons filled with numbers...
int matrixSize = rowsAcross *rowsDown;
for (int i = 0; i < matrixSize; ++i) {
// Make a label set to the current number...
// Add it to the grid...
add(new Button(Integer.toString(i)) );
}
}
}
Figure 3.5 : An example using GridLayout.
The code ends with a for
loop that adds Buttons to the grid. As the number increases, each
Button is added across and down the applet display area. The code
that creates the numeric name of the Button is interesting because
it illustrates a static method of the type wrapper class, Integer,
that can be used to convert an integer to a String without creating
a new object.
An alternative constructor enables you to create a GridLayout
without horizontal and vertical gaps.
GridBagLayout
The most complex layout class provided with the Java API is GridBagLayout.
While it is superficially similar to the GridLayout class, it
differs significantly by not requiring the components in the grid
to be the same size. GridBagLayout uses a helper class called
GridBagConstraints to specify how the component is displayed in
relation to the container's other components. GridBagLayout can
guarantee a logical display of components because it replaces
the use of hard-code coordinates with a relative structure of
how the components should visually interrelate.
Figure 3.6 and Listing 3.3 show an example of using GridBagLayout.
As the code illustrates, this class is much more complex than
the other layouts. The key to using GridBagLayout is understanding
its interaction with the GridBagConstraints helper. To describe
it in high-level terms, GridBagLayout and GridBagConstraints use
a system of weighting and relative flags to determine how things
will be positioned and sized. To see how this works, look at some
of the GridBagConstraints variables summarized in Table 3.1. The
variables starting with "grid" specify positioning in
relation to the other components in the row or column. The GridBagConstraints
constant REMAINDER means
that the object should be the last item in the row or column.
A value of 1, on the other
hand, indicates that it should be positioned normally. The weightx
and weighty variables determine
space distribution of the components in relation to each other.
A weight of 1 indicates that
the item should be positioned evenly with other items of weight
1. On the other hand, a weight
of 0 will give a component
a lower priority in sizing. If the weight variables are not set
to a nonzero value, the default distribution will be moved toward
the center of the container.
Figure 3.6 : An example using GridBagLayout.
Listing 3.3. Creating an example using GridBagLayout.
import java.awt.*;
import java.lang.*;
import java.applet.*;
// Class used for illustrating GridBagLayouts...
public class GridBagLayoutExample extends Applet {
// A complex set of
buttons
public void init() {
// Just reuse these over & over...
Button b;
Label l;
// Set up display area...
resize(300,200);
// Create the GridBagLayout and its helper...
GridBagLayout g = new GridBagLayout();
setLayout(g);
GridBagConstraints gbc = new GridBagConstraints();
// *************************************
// Put up a row of three equal size buttons...
// *************************************
// This tells the layout to use the full horizontal
// and vertical height if the display area is not
filled...
gbc.fill = GridBagConstraints.BOTH;
// Distribute horizontal space evenly between buttons
gbc.weightx = 1.0;
// Create and add the three buttons...
b = new Button("Number 1");
g.setConstraints(b,gbc);
add(b);
b = new Button("Number 2");
g.setConstraints(b,gbc);
add(b);
b = new Button("Number 3");
gbc.gridwidth = GridBagConstraints.REMAINDER; // Fill
up the row...
g.setConstraints(b,gbc);
add(b);
// *************************************
// Put up a button, a label, and a button
// that uses the remaining height area...
// *************************************
b = new Button("Number 4");
gbc.gridwidth = 1; // Reset to normal...
gbc.weighty = 1.0; // Force it to use remaining
height...
g.setConstraints(b,gbc);
add(b);
l = new Label("Number 5");
g.setConstraints(l,gbc);
add(l);
b = new Button("Number 6");
gbc.gridwidth = GridBagConstraints.REMAINDER; // Fill
up the row...
g.setConstraints(b,gbc);
add(b);
// *************************************
// Make a normal height button with insets...
// *************************************
gbc.weighty = 0.0; // Normal height;
gbc.gridheight = 1;
gbc.weightx = 0.0; // Use up the row...
gbc.insets.left = 20;
gbc.insets.right = 20;
b = new Button("Number 7");
g.setConstraints(b,gbc);
add(b);
// *************************************
// Finally add a text field across the bottom...
// *************************************
gbc.insets.left = 0; // Reset these...
gbc.insets.right = 0;
TextField t = new TextField("Number 8");
g.setConstraints(t,gbc);
add(t);
}
}
Table 3.1. The GridBagConstraints variables.
| Variables | Description
|
| gridx, gridy
| Specifies the upper-left display of the grid cell. A value of GridBagConstraints.RELATIVE indicates that the component is to be placed just right of or below the component just added before.
|
| Gridwidth,
| Indicates the number of grid cells in its display area. The
|
| gridheight
| GridBagConstraints.REMAINDER value specifies that it is the last cell it is the next to last cell in the row or column.in the row or column. GridBagConstraints.RELATIVE indicates that
|
| Fill |
Indicates what to do if the display area is not filled. If this value is set to GridBagConstraints.BOTH, then it will fill up the display area.
|
| ipadx, ipady
| Used to specify internal padding to add to the component's minimum size.
|
| Insets
| Sets the external padding around the component's display area.
|
| anchor
| A directional scheme to indicate where a component should go if it does not fill up the display area. The GridBagConstraints.CENTER variable is the default value.
|
| weightx,
| Determines space distribution. The default value of zero results in
|
| weighty
| the components clumping together in the middle of the display. Otherwise, the value indicates a weighting in relation to the other row and column components.
|
Look at the Java API documentation for more information about
these variables.
The spreadsheet project throughout this part of the book uses
GridBagLayout. In particular, the frame initialization uses this
class to set how the text field, spreadsheet canvas, and scrollbars
are positioned in relation to each other. Look at these examples
to get more ideas about how GridBagLayout works.
CardLayout
The CardLayout class allows the developer to flip through a series
of displays. The flipping action of CardLayout is similar to HyperCard
or other card-based programs. This card style of presentation
differentiates CardLayout from the other layouts in that only
one card of information is displayed at a time.
The typical way to use CardLayout is to tie it to a container,
like a Panel. A series of cards can then be added to the container.
Every time a component is added to a container that uses CardLayout,
a new "card" is added. A variety of methods can be used
to flip through the cards. For example, the first()
method goes to the first card in the deck, next()
goes to the next card, show()
goes to a card with a certain name, and so on.
Listing 3.4 provides the source code for a program that can be
used to flip through a deck displaying different graphical images.
It is similar to the first example in this chapter.
Listing 3.4. Creating an example using CardLayout.
import java.awt.*;
import java.lang.*;
import java.applet.*;
// This class illustrates card layouts by drawing a
different
// shape for a variety of cards...
public class CardLayoutExample extends
java.applet.Applet {
// Each card consists of a canvas that draws the name
// of the card...
Panel p;
CardLayout panelLayout;
int index = 0;
int lastIndex;
public void init() {
String name;
// Set up display area...
resize(300,200);
setLayout(new BorderLayout());
// Create the panel that uses CardLayout
p = new Panel();
panelLayout = new CardLayout();
p.setLayout(panelLayout);
add("Center",p);
// Add a canvas to each card
// The name variable is the shape to display...
CardLayoutDrawCanvas c; // Reuse these...
// Add the rectangle...
name = "Rectangle";
c = new CardLayoutDrawCanvas(name);
p.add(name,c);
// Add the oval display...
name = "Oval";
c = new CardLayoutDrawCanvas(name);
p.add(name,c);
// Add the round
rectangle display...
name = "RoundRect";
c = new CardLayoutDrawCanvas(name);
p.add(name,c);
// Show the first card...
index = 0;
lastIndex = 2;
panelLayout.first(p);
}
// A mouse click takes you to the next card...
public boolean mouseDown(Event ev, int x, int y) {
// Go to the next card or to beginning
if (index != lastIndex) {
panelLayout.next(p);
++index;
}
else { //
Go to first card...
panelLayout.first(p);
index = 0;
}
return true;
}
}
// This is a custom canvas that is used for drawing
// text, a rectangle, or nothing...
class CardLayoutDrawCanvas extends Canvas {
String name;
// Constructor - store the applet to get drawing
info...
public CardLayoutDrawCanvas(String
s) {
name = s;
}
// Draw the image per the choices in the applet...
public synchronized void paint (Graphics g) {
// Get the current size of the display area...
Dimension dm = size();
// Draw based on choice...
// Calculate center coordinates....
int x,y,width,height;
x = dm.width/4;
y = dm.height / 4;
width = dm.width / 2;
height = dm.height / 2;
// Paint a rectangle in the center...
if (name.compareTo("Rectangle") == 0) {
// Draw the rectangle in the center with colors!
g.setColor(Color.blue);
g.drawRect(x,y,width,height);
g.setColor(Color.yellow);
g.fillRect(x + 1,y + 1,width - 2,height - 2);
} // end if
// Paint an oval in the center...
if (name.compareTo("Oval") == 0) {
// Draw the rectangle in the center with colors!
g.setColor(Color.blue);
g.drawOval(x,y,width,height);
g.setColor(Color.yellow);
g.fillOval(x + 1,y + 1,width - 2,height - 2);
} // end if
if (name.compareTo("RoundRect") == 0) {
// Draw the rectangle in the center with colors!
int rounding = dm.width / 8;
g.setColor(Color.blue);
g.drawRoundRect(x,y,width,height,rounding,rounding);
g.setColor(Color.yellow);
g.fillRoundRect(x + 1,y + 1,width - 2,height
- 2,
rounding,rounding);
} // end if
}
}
Programs written for most GUI environments take actions based
on events initiated by the user or the system. If the user clicks
the mouse, a "mouse click" event is issued. If the program
wants to handle the mouse click, it needs to insert some code
to trap for any mouse click event. The program may pass the event
on to a default handler if it doesn't want to handle the event.
The default handler encapsulates an object's standard behavior.
For example, when you click a button, it should reflect the action
by showing a pressing motion. This default behavior should occur
regardless of whether the program processes the mouse click event.
As stated earlier, the visual controls the user interacts with
in the AWT environment are derived from the Component class. A
critical method of this class is handleEvent(),
which is used to process incoming events and relay them to the
appropriate handler methods. Any component that needs to manage
specific events will need to override this method with its own
handler.
In this chapter's initial example, the canvas object that draws
shapes and text was repainted every time the user clicked the
Draw button. (See Figure 3.3 and Listing 3.1.) The program made
this happen by adding the following code to the applet class,
Example1:
// Handle events that have occurred
public boolean handleEvent(Event evt) {
switch(evt.id) {
// This can be handled
case Event.ACTION_EVENT: {
if(evt.target instanceof Button) {
// Repaint canvas to use new choices...
c.repaint();
} // end if
return false;
}
default:
return false;
}
}
This code overrides the default handler of the Applet class (which
is a subclass of Component). The sample applet would do very little
if this code had not been added. Even though the default handler
was overridden, the code can still allow default behavior to occur.
The return code from the method tells the parent of the component
what should happen next. If the method returns true, then the
event has been completely handled and should not be passed to
the parent. On the other hand, a false return value allows the
event to be passed on. The event handler of the component's superclass
can also be called through this expression:
return super.handleEvent(evt);
The handleEvent() method
takes as its sole parameter an instance of the Event class. This
class encapsulates information about the event that occurred.
The id integer variable of
the class represents the type of event that occurred. The most
widely captured event is the one with the id
ACTION_EVENT. Each class
of component has a specific action tied to it. For example, the
action for a Button object is its selection, such as a mouse click.
For TextField objects, the action is the entry of the Return key
in the text field.
Other types of frequently caught events are those prefixed by
KEY_ and MOUSE_,
which represent keyboard and mouse events, respectively. Other
events include scrollbar actions and window events, such as Minimize
or Destroy.
In the preceding code, the handleEvent()
method traps for button selections. When the button is selected,
an event with an ACTION_EVENT
ID is generated. Recall, however, that this example also had a
TextField. To differentiate the button selection from a text field
Return keystroke, the code needs to tell what class of object
issued the event. It does this by looking at the Event target
variable. In the example's code, the program checks to see whether
the target is a button by using the instanceOf
operator.
Other information can be found in Event variables. An optional
argument, the arg variable,
provides information specific to the Event, such as the Object
of an action. For mouse events, the x
and y variables can be used
to get the mouse position. The key variable is used to determine
which keystroke corresponds to a KEY_
event.
The Component class also has helper methods that can be used if
the user wants to manage events in a simpler manner than handleEvent()
provides. These helper methods are actually called by handleEvent().
However, the overriding of handleEvent()
is not required to use the helper methods.
In the CardLayout example, cards were flipped by overriding the
mouseDown() method, which
was declared as follows.
public boolean mouseDown(Event ev, int
x, int y)
This is used to trap mouse clicks, passing the current location
of the mouse in the x and y parameters. This call actually begins
in the handleEvent() method,
which traps for events of ID MOUSE_DOWN.
When such events are issued, handleEvent()
reacts by calling mouseDown().
If this is overridden by the object in question, its version of
the method will be called. Like handleEvent(),
the return value of the helper methods indicate whether the event
has been completely handled.
Table 3.2 lists the available Event helper methods, which are
all part of the definition of the Component class.
Table 3.2. Event helper methods.
| Method | Description
|
| mouseEnter
| Mouse enters the Component's area |
| mouseExit
| Mouse leaves the Component's area |
| mouseMove
| Mouse has moved |
| mouseDown
| Mouse has been pressed down |
| mouseDrag
| Mouse has moved while it is pressed down |
| mouseUp
| Mouse click has been released |
| keyDown
| Keyboard character has been pressed |
| keyUp |
Keyboard character has been released |
| action
| An action has occurred to the Component |
| gotFocus
| The input focus has been placed on the Component
|
| lostFocus
| The Component has lost input focus |
Although the basics of exception handling were discussed in the
first part of the book, there is a lot more to managing exceptions
in Java than just the "try-catch" clause. The class
of exceptions that is thrown and what information can be gleaned
from the thrown object are also important topics. This part of
exception handling is related to Java's exception class hierarchy,
the subject of this section.
All throwable objects in Java are an instance of, or are subclassed
from, the Throwable class, which encapsulates the behaviors found
in all the throwable classes that are part of the Java API. One
widely used method of Throwable is getMethod().
This prints out a detail message attached to the thrown object.
The detail message will give extra information regarding the nature
of the error. If the default constructor of the thrown object
is used, a system-generated detail message will be provided. If
you want a custom message, on the other hand, an alternative constructor
can be used.
A couple of examples will illustrate using detail messages. Suppose
an operation is performed that results in an error. The following
code will catch the thrown object and print out the detail message:
try {
//
do something that causes an exception, such
as
divide by zero
}
catch (Throwable t) {
// Print out the detail message of the error
System.out.println(t.getMessage());
}
In another case, suppose that the same code wants to rethrow the
message with its own custom detail message if there is an error.
The other constructor for Throwable can be used in this situation
to construct the custom message:
try {
//
do something that causes an exception, such
as
divide by zero
}
catch (Throwable t) {
// Throw a new Throwable object with
// Custom detail message
throw new Throwable("This method threw an
exception");
}
When the new Throwable is caught and getMessage()
is invoked, the program will get the String "This
method threw an exception" instead of a system-generated
message.
Other methods in Throwable can be used for getting the state of
the runtime stack when the error occurred. The printStackTrace()
method, for example, prints to standard error the kind of stack
output that you often see when a Java program terminates abnormally.
All the other exception classes in the Java API behave similarly
to the Throwable class. They differ only in how they are located
in the hierarchy of class exceptions. Java divides errors into
two general groupings, indicated by two parent classes derived
from Throwable. The Exception class represents the kind of errors
that occur in a normal programming environment, such as file not
found, array index out of bounds, null pointers, divide by zero,
and so forth. These are "soft" errors, the type of difficulty
that a program should be able to easily recover from. In general,
Exception classes represent errors that are meant to be caught
by the calling method whenever they occur. Classes derived from
the Error class, on the other hand, represent more serious errors
that can occur at unpredictable times and are often fatal in nature.
An extreme case of such an Error is a failure within the Java
virtual machine. If this occurs, often the best you can hope for
is an orderly shutdown of the program. Because of their unpredictable
and catastrophic nature, Error objects do not have to be caught
in exception handlers. In general, programs will be written to
catch only classes derived from the Exception class.
| Note |
From a terminological standpoint, "exception" (lowercase) is used to refer to all classes of thrown objects. The term "error" (lowercase also) refers to the circumstances that cause the object to be thrown.
|
As mentioned in the first part of this book, a method establishes
that it throws an Exception that has to be caught in its declaration.
For example, this is the constructor for the class that opens
a file for output:
public FileOutputStream(String name)
throws IOException
This declaration means that an instance of IOException is thrown
whenever the file specified in the String cannot be opened. The
IOException class is derived from Exception, so any code that
uses this FileOutputStream constructor must catch this exception.
This means that code using this constructor must be generally
structured as follows:
try {
FileOutputStream fo = new FileOutputStream("MyFile");
//
write the file
{
catch (IOException e) {
//
Handle the file open problem
}
With one notable exception, all methods that throw objects of
type Exception must be called in an exception handler that catches
the Exception. However, Java provides a branch of Exceptions of
thrown objects that do not have to be caught. These are derived
from the RuntimeException class. Subclasses of the RuntimeException
class are those problems that would be too cumbersome to trap
every time they may occur. For example, all accesses to arrays
could result in an exception because the index into the array
could be bad. However, it would be unwieldy to put an exception
handler around all array calls. There would also be a performance
hit. Even worse are instances of the NullPointerException class
being thrown, which theoretically could occur anywhere in the
program.
Just because an exception is a subclass of RuntimeException, however,
doesn't mean that a good program should not trap for the thrown
object. For example, the ArithmeticException usually indicates
a divide by zero error. It is good programming to trap for this
error whenever it occurs. In most cases, division doesn't often
occur in a program. Consequently, a clause like the following
is appropriate for many division operations:
try {
result = divider / divisor;
}
catch (ArithmeticException e) {
System.out.println("Divide by zero error!");
result = 0;
}
On the other hand, it is acceptable to have a divide operation
that is not part of an exception handler, since ArithmeticException
is a subclass of RuntimeException. Here is a list of the subclasses
of RuntimeException:
| The Subclasses of RuntimeException |
ArithmeticException
ArrayIndexOutOfBoundsException
ArrayStoreException
ClassCastException
IllegalArgumentException
IllegalMonitorStateException
IllegalThreadStateException
IndexOutOfBoundsException
NegativeArraySizeException
|
The Exception class hierarchy serves a more fundamental purpose
in Java programming than just providing a way to order exception
classes; it plays an important role in determining how an exception
is handled. When an exception is thrown, Java looks for an exception
handler to catch the thrown object. It first looks in the method
where the error occurred, checking to see whether it has an appropriate
exception handler-one that is the class or a superclass of the
exception thrown. Recall that an exception handler can have multiple
catch statements. The catch statements should be ordered in such
a way that a subclass is listed before any of its superclasses.
Here is a possible exception handler for managing a variety of
problems:
try {
//
some bad arithmetic operation
//
or maybe a bad array access
//
or a null errror
}
catch (ArithmeticException e) {
// Handle the exception
}
catch (RuntimeException e) {
// Handle the runtime exception
}
catch (Exception e) {
// Handle the Exception
}
catch (Throwable e) {
// Handle the thrown object
}
Recall that ArithmeticException is a subclass of RuntimeException.
The latter is derived from Exception, which in turn is a subclass
of Throwable. In this example, a divide by zero error throws an
ArithmeticException, which is handled in the first catch statement.
On the other hand, a NullPointerException or a bad array access
will result in a RuntimeException object being thrown, which is
handled in the second catch statement. A serious problem of class
Error will not be handled until it reaches the last catch statement,
which will catch the thrown object since Error is a subclass of
Throwable. This example illustrates that the exception class hierarchy
is a critical part of Java's strategy for resolving exceptions.
If the method that caused an object to be thrown does not have
an appropriate exception handler, the object percolates up the
call stack, and this process of finding an appropriate handler
is repeated. If it reaches the top of the stack and no appropriate
handler is found, the program will terminate abnormally.
It's easy to write your own exception handler. A class is simply
created that extends the class that should function as the superclass.
If a new IOException handler is needed, for example, it could
be written as the following:
public class CustomIOException extends
IOException { }
The hard part, however, is deciding what the superclass of the
handler should be. In general, it should not be a subclass of
Error since these are reserved for "hard" system problems.
Using RuntimeException should also be discouraged because of an
interesting controversy over whether there should even be such
a thing as a RuntimeException class. This is because, in some
ways, the use of RuntimeException classes defeats some of the
goals of exception handling. By definition, a RuntimeException
object does not have to be caught, but this would then increase
the likelihood of an exception not being caught at all, forcing
the program to terminate abnormally. This defeats a key goal of
exception handling, which is to have a graceful resolution of
problems. Thus, the use of RuntimeException is reserved for classes
of errors that would occur too frequently to have an exception
handler every time the pertinent methods are called.
This leaves the subclasses of Exception as the best candidate
for being the superclass of new exception classes. Some good examples
can be found in the organization of the exceptions in the Java
IO package. One of the constructors for the FileInputStream class
tries to open up the input file specified in its String parameter.
If the file cannot be opened, a FileNotFoundException is thrown.
This class is derived from the IOException class, which is based
on Exception. Other file error classes are also derived from IOException.
Therefore, the IOException class marks a hierarchy for problems
related to input/output operations.
Suppose a new set of classes is being created for database operations.
It might be helpful to create a new hierarchy of exceptions that
correspond to database problems. The class at the top of this
hierarchy might be called DatabaseException, which could be derived
from Exception since it marks a new branch of the Exception hierarchy.
For errors related to problems with the database key, you could
create a KeyException class derived from DatabaseException. If
the key could not be found, then you could add a KeyNotFoundException,
whose superclass is KeyException. These new Exceptions could be
declared as follows:
public class DatabaseException extends
Exception { }
public class KeyException extends DatabaseException { }
public class KeyNotFoundException extends KeyException { }
In the following example, a method that tries to find a record
could then be declared as throwing a KeyNotFoundException if the
record is not found:
public findRecord(Key index) throws KeyNotFoundException
The code that invokes this method then can be structured as follows:
try {
db.findRecord(myKey);
}
catch (KeyNotFoundException e) {
// Handle the key not found exception
}
catch (KeyException e) {
// Handle the exception due to a bad key
}
catch (DatabaseException e) {
// Handle any database exception
}
catch (Exception e) {
// Handle the Exception
}
The project in this chapter is a spreadsheet applet that supports
rudimentary formula operations and other basic behavior. This
version of the project has the following behaviors:
- The program produces a spreadsheet of
the size specified in the HTML parameter list. The rows are indicated
by alpha characters ranging from A to Z.
The columns are numbered from 0 to the number of columns minus
one. The individual cells in the spreadsheet are labeled accordingly.
Therefore, the upper-left cell is A0. The spreadsheet cells support
only numeric values of precision double.
- The user can change the value of a cell
by clicking on a valid cell. A numeric value, a formula, or an
empty string can then be entered into the text field at the top
of the screen. When the user hits return, the new value is validated
and entered into the cell, and the spreadsheet cell values are
recalculated.
- The program supports several formulas.
Arithmetic formulas of MULT, ADD, SUB, and DIV operate on two
cells and return the double value resulting from it. For example,
MULT(C3,B3) returns the value in cell C3 times the value in cell
B3. The SUM(cell1,cell2) operation returns the values of all the
sums between cell1 and cell2 when these cells share a common row
or column. The spreadsheet supports cell recursion on formulas,
so a cell with a formula can be included in the formula operation
of another cell.
- The applet has a basic menu with Quit
and New spreadsheet commands.
Figure 3.7 shows how the spreadsheet applet appears in a browser.
More advanced features, such as scrollbars, dialog boxes, and
graphs, will be explored in the upcoming chapters. The version
of the spreadsheet currently presented aims to illustrate many
of the features of AWT and exception handling discussed in the
first section of this chapter.
Figure 3.7 : The first version of the spreadsheet applet.
Table 3.3 explains the classes used in this chapter's version
of the spreadsheet applet.
Table 3.3. Spreadsheet classes.
| Class | Description
|
| Cell | Contains a String and evaluated value corresponding to a single cell.
|
| CellContainer | Contains a matrix of Cells. The String in each Cell is evaluated according to whether it is a formula, a literal numeric value, or an empty cell.
|
| FormulaParser | Used to parse out the individual string fragments that make up a single formula. Converts literal strings to their numeric values.
|
| FormulaParserException | An Exception that is thrown if a formula is ill constructed.
|
| ArgValue | A FormulaParser helper class used to store information about an argument in a formula.
|
| SpreadsheetCell | Provides for the visual presentation of a single Cell.
|
| SpreadsheetContainer | Manages the visual presentation of a matrix of SpreadsheetCells. Provides an interface for changing the value of a cell. Supports the display of a currently highlighted cell.
|
| SpreadsheetFrame | Provides the main presentation of the spreadsheet by displaying the SpreadsheetContainer, managing mouse selections on that spreadsheet, reading a text field for changing cell values, and handling a simple menu.
|
| SpreadsheetApplet | Responsible for creating, showing, and hiding the SpreadsheetFrame that provides the visual display of this applet.
|
This class stores a String and its evaluated value for a single
cell. It performs no validation in terms of the validity of any
formulas or nonnumeric values contained in the String. A variable
is used to mark whether the cell has been evaluated, although
the setting of this variable is of interest only to the classes
that use Cell.
Listing 3.5. The Cell class.
// A cell contains the formula for
an individual cell
// However, it does not know what to do with it and
simply
// returns its contents...
public class Cell {
String s;
double evaluatedValue;
boolean evaluated; // True if the cell has been
evaluated
// Constructor creates empty StringBuffer as
reference...
public Cell() {
s = "";
evaluatedValue = 0.0;
evaluated = true;
}
// Takes a StringBuffer and makes it the cell's
data...
public void setCellString(StringBuffer
s) {
this.s = new String(s);
evaluated = false;
}
// Takes a StringBuffer
and makes it the cell's
data...
public void setCellString(String s) {
this.s = s;
evaluated = false;
}
// Return the current contents of the Cell
public String getCellString() {
return s;
}
// Set the evaluated value of a cell...
public void setEvalValue(double val) {
evaluated = true;
evaluatedValue = val;
}
// Set the evaluated
value of a cell...
public void setEvalValue(int val) {
setEvalValue((double)val);
}
// See if a cell has
been evaluated...
public boolean getEvaluated() {
return evaluated;
}
// Get the evaluated
value of a cell...
public double getEvalValue() {
return evaluatedValue;
}
// Set a cell to unevaluated...
public void setEvaluated(boolean eval) {
evaluated = eval;
}
}
This class consists of a matrix of Cells. Its main constructor
creates the matrix based on the number of rows and columns provided
in its constructor. Public methods allow setting or retrieving
values of individual cells based on a row and column index. If
this index is bad, an IllegalArgumentException is thrown. The
most interesting feature of this class is its ability to evaluate
formulas. The recalculateAll()
method forces a reevaluation of all the Cell values in the matrix.
It works with the FormulaParser class to see how a formula will
be calculated. If the formula relies on a Cell that contains yet
another formula, the program recurses into finding out the evaluated
formula value of that cell. The recursion occurs in the calculateCell()
and parseFormula() methods,
which determine the numeric value of a specific Cell. The recursion
stops when a Cell with a literal (that is, a number) value is
found. When a Cell's formula or literal value has been fully evaluated,
the Cell is updated accordingly. The calculation process provides
a good illustration of exception handling because they are widely
used to handle formula parsing errors and illegal formula operations.
Listing 3.6. The CellContainer class.
// The CellContainer class contains a
matrix of Cell data.
// The class is responsible for making sure
// that the formulas in the cells are evaluated properly...
public class CellContainer {
int numRows;
int numColumns;
Cell matrix[];
// Constructs an empty container...
public CellContainer()
{
numRows = 0;
numColumns =
0;
matrix = null;
}
// Constructs a matrix of cells [rows X columns]
public CellContainer(int
rows,int columns) throws IllegalArgumentException {
numRows = rows;
numColumns =
columns;
// Throw an exception if the row/col values are no
good...
if ((numRows
<= 0) || (numColumns <=0)) {
numRows = 0;
numColumns
= 0;
matrix = null;
throw new
IllegalArgumentException();
}
// Create the Cell matrix...
int numCells
= numRows * numColumns;
matrix = new Cell[numCells];
for (int i =
0; i < numCells; ++i)
matrix[i] = new Cell();
}
// Sets the new value
of a cell...
public void setCellFormula(StringBuffer s,int row,int col)
{
setCellFormula(s.toString(),row,col);
}
// Sets the new value of a cell...
public void setCellFormula(String
s,int row,int col) {
// Get the index into the matrix...
int index;
try {
index =
getMatrixIndex(row,col);
}
catch (IllegalArgumentException
e) {
System.out.println("Invalid CellContainer
index.");
return;
}
// Set the value
of the cell...
matrix[index].setCellString(s);
}
// Get the string contents
of a cell...
public String getCellFormula(int row,int col) throws
IllegalArgumentException {
// Get the index
into the matrix...
int index;
try {
index = getMatrixIndex(row,col);
}
catch (IllegalArgumentException e) {
throw e;
}
// Good index.
Return string...
return matrix[index].getCellString();
}
// Get the cell at
certain index...
public Cell getCell(int row,int col) throws IllegalArgumentException
{
// Get the index
into the matrix...
int index;
try {
index = getMatrixIndex(row,col);
}
catch (IllegalArgumentException e) {
throw e;
}
// Good index.
Return Cell...
return matrix[index];
}
// Calculate the matrix
index given a row and column...
// Throw an exception if it is bad...
int getMatrixIndex(int
row,int col) throws IllegalArgumentException {
// Kick out if there are negative indexes...
if ((row <
0) || (col <0))
throw new IllegalArgumentException();
// Also reject
too large indexes...
if ((row >= numRows) || (col >= numColumns))
throw new
IllegalArgumentException();
// Everything is OK. Calculate index...
return ((numColumns
* row) + col);
}
// Validate a formula by seeing whether it matches the basic
syntax...
public String validateFormula(Cell
c,String newFormula) throws
ÂFormulaParserException
{
// Convert all alphas to Upper Case
String convertedFormula = newFormula.toUpperCase();
// Get old formula
of cell and temporarily set cell value there...
String oldFormula = c.getCellString();
// Set up the
parser to validate the cell...
FormulaParser f = new FormulaParser(convertedFormula);
// Validate the
cell...
try { // Set up the formula parser...
// Get
the type of formula it is...
int typeFormula = f.getType();
// If it's
empty, return Success...
if (typeFormula == f.EMPTY)
return
convertedFormula;
// Check to see whether literal is valid...
if (typeFormula
== f.LITERAL) {
f.getLiteral(); // Ignore the
return value...
return
convertedFormula;
} // end if
// If it's
a formula, you need to parse it...
parseFormula(c,f);
}
catch (Exception e) {
throw new
FormulaParserException();
}
// Return the
converted string...
return convertedFormula;
}
// Recalculate the
values in all the cells...
public void recalculateAll() {
if (matrix ==
null)
return;
// Invalidate
the formulas...
invalidateFormulas();
// Go through
each cell and calculate its value...
// Go row-wise across, as this is how things are probably
set up
int i,j;
for (i = 0; i < numRows; ++i) {
for (j
= 0; j < numColumns; ++j) {
if (matrix[(i * numColumns) + j].getEvaluated()
== false) {
calculateCell(i,j);
}
} // end
column for
} // end row for
}
// Recalculate an individual
cell...
// Update its evaluation when complete...
double calculateCell(int
row,int col) {
// Get the index of the calculation...
int index;
try {
index =
getMatrixIndex(row,col);
}
catch (IllegalArgumentException
e) {
return 0.0; // Bad index...
}
// Set up the
parser to recalculate the cell...
FormulaParser f = new FormulaParser(matrix[index].getCellString());
// First
get the type...
int typeFormula = f.getType();
// If it's
empty, you're done...
if (typeFormula == f.EMPTY) {
matrix[index].setEvalValue(0.0);
return 0.0;
}
// If it's a literal, you can also finish quickly...
if (typeFormula
== f.LITERAL) {
// It better be some kind of number...
try
{
double dbl = f.getLiteral(); // Get the
double value...
matrix[index].setEvalValue(dbl);
return dbl;
}
// Some kind of invalid string...
catch(FormulaParserException
e) {
System.out.println("Invalid literal
at [" + row + "," + col + "]");
matrix[index].setEvalValue(0.0);
return 0.0;
}
}
// Formulas
got to be parsed and maybe recurse, however...
double dbl;
try {
dbl = parseFormula(matrix[index],f);
}
catch (Exception e) {
System.out.println("Invalid
formula at [ " + row + "," + col + "]");
dbl = 0.0;
}
matrix[index].setEvalValue(dbl);
return
dbl;
}
// Parse out a formula...
// Assumes formula
parser is set to a certain formula...
double parseFormula(Cell c, FormulaParser f) throws
FormulaParserException {
// Figure
out what type of formula it is...
try {
int
op = f.getOperation();
// Get the arguments...
ArgValue
arg1 = new ArgValue();
ArgValue arg2 = new ArgValue();
f.getOpArgs(arg1,arg2);
// Sum operation is different from rest...
if
(op != f.SUM) { // SUM is even worse...
double val1,val2;
// Get the values...
// See if you have to recurse...
if (arg1.getType() == arg1.CELL)
val1 = calculateCell(arg1.getRow(),arg1.getColumn());
else
val1 = arg1.getLiteral();
if (arg2.getType() == arg1.CELL)
val2 = calculateCell(arg2.getRow(),arg2.getColumn());
else
val2 = arg2.getLiteral();
// Perform the operation...
switch (op) {
case
f.ADD:
return (val1 + val2);
case
f.MULT:
return (val1 * val2);
case
f.DIV:
try { //
Handle divide by zero errors...
double
ret = val1 / val2;
return ret;
}
catch (ArithmeticException
e) {
//
Divide by zero!
return 0.0;
}
case f.SUB:
return (val1 - val2);
default:
break;
} // end switch...
}
// end if
else { // Sum...
double dbl = 0.0;
int index;
// Validate row-wise or column operation...
if ((arg1.getType() != arg1.CELL) ||
(arg2.getType() != arg2.CELL))
throw
new FormulaParserException();
// Row-wise or column-wise...
if (arg1.getRow() == arg2.getRow()) {
if (arg2.getColumn() <
arg1.getColumn())
throw new FormulaParserException();
for (int i = arg1.getColumn();
i <= arg2.getColumn(); ++i) {
// Skip cases where the cells are the same...
index = getMatrixIndex(arg2.getRow(),i);
if (matrix[index] == c)
continue;
// If OK, then recurse...
dbl += calculateCell(arg2.getRow(),i);
}
// end for
return dbl;
}
else if (arg1.getColumn() == arg2.getColumn())
{
if
(arg2.getRow() < arg1.getRow())
throw new FormulaParserException();
for
(int i = arg1.getRow(); i <= arg2.getRow(); ++i) {
// Skip cases
where the cells are the same...
index
= getMatrixIndex(i,arg2.getColumn;
if (matrix[index]
== c)
continue;
// If OK, then
recurse...
dbl
+= calculateCell(i,arg2.getColumn());
} // end for
return dbl;
}
throw
new FormulaParserException();
}
return
0.0;
}
catch (FormulaParserException
e) {
throw e;
}
}
// Invalidate all cells that are formulas to force recalculation...
void invalidateFormulas()
{
// Set up the parser to get the cell type...
FormulaParser
f = new FormulaParser();
int numCells = numRows * numColumns;
for (int i =
0; i < numCells; ++i) {
f.setFormula(matrix[i].getCellString());
if (f.getType()
== f.FORMULA)
matrix[i].setEvaluated(false);
} // end for
}
// Get the number of rows in the container
int getNumRows() {
return numRows;
}
// Get the number of
columns in the container
int getNumColumns() {
return numColumns;
}
}
This class is used to parse the elements of a single formula.
Its public integer variables are used to indicate what kind of
operation (such as SUM) is being performed or whether the formula
is a literal or empty value. It uses internal hints to keep track
of the current parsing operation. It not only returns the type
of operation the formula performs, but also the contents of its
arguments. For example, the formula SUM(A0,A3) results in the
first argument being parsed into row 0 and column 0 (corresponding
to A0) and row 0 and column 3 (cell A3). These are stored in the
helper ArgValue class. A FormulaParserException object is thrown
if there is anything wrong with the formula or literal.
For the sake of saving space, you are referred to the accompanying
CD-ROM for the source code of this class.
This exception is thrown when a String does not contain a proper
formula, for any of a variety of reasons. It is a subclass of
IllegalArgumentException.
Listing 3.7. The FormulaParserException class.
// This class is an Exception thrown
when a
// formula is an invalid format...
public class FormulaParserException extends IllegalArgumentException
{ }
This class does little more than hold information about an argument.
Public integer variables are used to indicate whether the argument
is a literal or cell value. If it is the former, the class stores
the converted double value. If it is a cell in a spreadsheet,
then its converted row and column values are stored.
In the interest of saving space, see the accompanying CD-ROM for
the source code of this class.
This class is used to draw the contents of an individual Cell.
It is an extension of the Canvas class and has a custom paint()
method that is used to draw the Cell at specific coordinates.
It gets the evaluated value of the Cell to determine what to display.
The text and background color of the Cell can be set through public
methods. It is also possible to indicate that the literal Cell
value should be painted and the evaluated value should be ignored.
Listing 3.8. The SpreadsheetCell class.
import java.awt.*;
import java.lang.*;
// This class ties the contents of an individual
// SpreadsheetCell to a single data Cell.
// The evaluated contents of
// that cell are returned to the SpreadsheetContainer...
public class SpreadsheetCell extends Canvas {
Cell c; //
The cell this is tied to...
boolean literal; // If set to true, automatically
paint what's in
// cell string...
Color fillColor;
Color textColor;
public SpreadsheetCell(Cell c,boolean literal) {
super();
this.c = c;
this.literal
= literal;
// Set the color defaults...
fillColor = Color.white;
textColor = Color.black;
}
// Set the fill color
public void setFillColor(Color clr) {
fillColor = clr;
}
// Set the text color
public void setTextColor(Color
clr) {
textColor = clr;
}
// Return the reference
to the cell...
public Cell getCell() {
return c;
}
// Set the cell string...
public void setString(String
s) {
c.setCellString(s);
}
// Get the current
string value in the cell...
public String getString() {
return c.getCellString();
}
// This will return the text to the current evaluated contents
// of the cell...
public synchronized void paint(Graphics g,int x,int y,int
width,int height) {
String s = c.getCellString();
String textPaint;
// If this is
a literal value, then print what it has...
if (literal == true)
textPaint
= s;
else {
// Otherwise,
display formula only if cell is not empty...
if (s.compareTo("") == 0)
textPaint
= s;
else // Otherwise, show evaluate
value...
textPaint
= String.valueOf(c.getEvalValue()) ;
} // end else
// Set up drawing
rectangle...
g.setColor(Color.blue);
g.drawRect(x,y,width,height);
g.setColor(fillColor);
g.fillRect(x
+ 1,y + 1,width - 2,height - 2);
// Clip the text if necessary...
int textWidth;
int len = textPaint.length();
int effWidth
= width - 4;
// Loop until text is small enough to fit...
while (len >
0) {
textWidth = g.getFontMetrics().stringWidth(textPaint);
if (textWidth
< effWidth)
break;
-len;
textPaint = textPaint.substring(0,len);
} // end while
// Draw the string
g.setColor(textColor);
g.drawString(textPaint,x + 4,y + (height - 2));
}
// Return the literal
value...
public boolean getLiteral() {
return literal;
}
}
This class constructs the spreadsheet to be displayed. It takes
as input an instance of CellContainer that contains the matrix
being operated on. It constructs a matrix of Spreadsheet cells,
each of which is tied to an individual Cell in the CellContainer,
except for the headers. These are represented by SpreadsheetCells
(set to a literal value) created on the boundaries of the spreadsheet
to display the row and column headers.
The SpreadsheetContainer class is derived from the Canvas class.
It overrides the paint()
method to draw the SpreadsheetCells. It goes across and down the
spreadsheet matrix repeatedly calling the SpreadsheetCell's paint()
method, providing the coordinates of where it should be drawn.
Note the update() method
that is called before paint();
this was added to prevent flicker. The default behavior of update()
is to blank out the painting area with a white color; this causes
a "flicker" until the paint()
method is next called. By overriding update()
with a direct call to paint(),
however, you can avoid the flicker. Try removing the update()
method from the SpreadsheetContainer class, and you can see the
flicker that results.
The SpreadsheetContainer class also controls the currently highlighted
cell by setting the background and text color of the highlighted
SpreadsheetCell. It also overrides the handleEvent()
method so it can trap mouse clicks. It checks to see whether the
mouse is over a valid cell; if so, it is given the highlight.
Listing 3.9. The SpreadsheetContainer class.
import java.awt.*;
import java.lang.*;
// This class contains the cells that make up a spreadsheet...
public class SpreadsheetContainer extends Canvas {
CellContainer c; // The actual spreadsheet data...
int numRows;
int numColumns;
SpreadsheetCell matrix[];
int cellWidth; // These are set in the paint routine...
int cellHeight;
SpreadsheetCell newHighlight;
SpreadsheetCell oldHighlight;
// Construct container. Create
internal paint matrix tied to
// the data container...
public SpreadsheetContainer(CellContainer
ctnr) {
super();
// Load the container
and set up the display...
loadContainer(ctnr);
}
// Take a cell container
and load set the spreadsheet
// to use it. Put the highlight in the first cell...
void loadContainer(CellContainer
ctnr) {
c = ctnr; // Store the CellContainer...
// Get size of spreadsheet...
numRows = c.getNumRows()
+ 1;
numColumns = c.getNumColumns() + 1;
// Create the SpreadsheetCell matrix...
matrix = new
SpreadsheetCell[numRows * numColumns];
// Add the cells
to the grid...
int i,j,index;
char ch;
// Add the column
labels across the top...
for (j = 0; j < numColumns; ++j) {
// Create
a literal cell for each column...
matrix[j] = new SpreadsheetCell(new Cell(),true);
// Set
the cell contents and color...
if (j > 0)
matrix[j].setString(String.valueOf((j
- 1)) );
matrix[j].setFillColor(Color.lightGray);
matrix[j].setTextColor(Color.blue);
} // end for
// Create the individual rows...
for (i = 1; i
< numRows; ++i) {
// Set up the row header...
index =
(i * (numColumns));
matrix[index] = new SpreadsheetCell(new Cell(),true);
ch = (char)('A'
+ (i - 1));
matrix[index].setString(String.valueOf(ch) );
matrix[index].setFillColor(Color.lightGray);
matrix[index].setTextColor(Color.blue);
// Now
set the container cells...
for (j = 1; j < numColumns; ++j) {
index
= (i * (numColumns)) + j;
matrix[index] = new SpreadsheetCell(c.getCell(i
- 1,j - 1),false);
//
Set the colors...
matrix[index].setFillColor(Color.white);
matrix[index].setTextColor(Color.black);
} // end inner for...
} // end outer
for
// Highlight
the upper-left cell...
index = getIndex(1,1);
newHighlight
= matrix[index];
oldHighlight = newHighlight;
setCellHighlight(newHighlight,true);
}
// Attach a new container to the spreadsheet...
public void newCellContainer(CellContainer
ctnr) {
// Load the container and set up the display...
loadContainer(ctnr);
repaint();
}
// Return the currently
highlighted row...
public SpreadsheetCell getHighlight() {
return newHighlight;
}
// Get the index into the matrix for a row or column...
int getIndex(int row,
int col) {
return ((row * numColumns) + col);
}
// Handle mouse clicks...
void setMouseHighlight(int x, int y) {
// First figure
out what cell is at those coordinates...
newHighlight = calculatePaint(null,false,x,y);
// Make it the
new highlight if it is not a border element...
if ((newHighlight != null) && (newHighlight.getLiteral()
== false) ) {
// Turn
off old highlight...
if ((oldHighlight != null) && (oldHighlight
!= newHighlight))
setCellHighlight(oldHighlight,false);
// Set new highlight...
setCellHighlight(newHighlight,true);
oldHighlight = newHighlight;
// Notify
parent of change...
notifyParentOfHighlight(newHighlight);
}
}
// Highlight a cell
// If boolean is on
then highlight; else set to normal...
void setCellHighlight(SpreadsheetCell sc,boolean on) {
if (on == true)
{ // Highlight it!
sc.setFillColor(Color.red);
sc.setTextColor(Color.white);
} // end if
else { //
Set to normal...
sc.setFillColor(Color.white);
sc.setTextColor(Color.black);
} // end else...
// Force the
cell to repaint...
repaint();
}
// Update message sent
when repainting is needed...
// Prevent paint from getting cleared out...
public void update(Graphics
g) {
paint(g);
}
// Draw the displayable
spreadsheet contents...
public synchronized void paint (Graphics g) {
// Go through
the calculations of the paint while painting...
calculatePaint(g,true,0,0);
}
// This goes through
the motions of calculating what is on the
// screen and either calculates coordinates or paints...
// If it is not paint,
returns cell that fits in hit region...
SpreadsheetCell calculatePaint(Graphics g,boolean bPaint,int
xHit,int yHit) {
// Get the current
size of the display area...
Dimension dm = size();
// Calculate
the cell width and height
// Cell should be wide enough to show 8 digits...
if (bPaint ==
true) {
cellWidth = g.getFontMetrics().stringWidth("12345.67");
cellHeight
= g.getFontMetrics().getHeight();
} // end if
// Figure out how many rows and cols can be displayed
int nCol = Math.min(numColumns,dm.width
/ cellWidth);
int nRow = Math.min((numRows + 1),dm.height / cellHeight);
// Draw the cells...
int index,i,x,j,y;
-nRow;
// Go across
the rows...
for (i = 0; i < nRow; ++i) {
y = cellHeight
+ (i * cellHeight);
// Go across the colomns...
for (j
= 0; j < nCol; ++j) {
index = (i * numColumns) + j;
//
Paint if told to...
if (bPaint == true) {
matrix[index].paint(g, (j * cellWidth),y,
cellWidth,cellHeight);
}
// end if
else { // Otherwise see whether cell fits...
x = (j * cellWidth);
// See whether it fits in the column...
if ((xHit >= x) && (xHit < (x + cellWidth))) {
// See whether it fits in
the row...
if
((yHit >= y) && (yHit < (y + cellHeight))) {
return matrix[index];
}
// end if
} // end if
}
} // end column for
} // end row
for
return null; // Only used if paint is false...
}
// Notify parent that
there is a new Highlight...
void notifyParentOfHighlight(SpreadsheetCell sc) {
// Create new
event with highlight cell as arg
Event ev = new Event(this,Event.ACTION_EVENT,sc);
// Send it to
the parent...
getParent().deliverEvent(ev);
}
// Handle mouse clicks
to spreadsheet...
public boolean handleEvent(Event evt) {
switch(evt.id)
{
// Mouse clicks. See whether you should highlight
// cell
on spreadsheet...
case Event.MOUSE_DOWN: {
if
(evt.target instanceof SpreadsheetContainer)
setMouseHighlight(evt.x,evt.y);
return
false;
}
default:
return false;
}
}
// Handle to change to a formula...
// Throws an exception
if the formula is invalid...
public void replaceFormula(SpreadsheetCell sc,String newFormula)
throws
ÂIllegalArgumentException
{
String convertedFormula;
// First validate
the formula...
try {
convertedFormula
= c.validateFormula(sc.getCell(),newFormula);
}
// If formula
is invalid, rethrow an exception...
catch (FormulaParserException e) {
throw new
IllegalArgumentException();
}
// Add converted
formula to cell...
sc.setString(convertedFormula);
// Recalc...
c.recalculateAll();
// Repaint...
repaint();
}
}
The SpreadsheetFrame class is responsible for presenting the spreadsheet
applet to the user. Its most important component is the SpreadsheetContainer
class, displayed in the middle of the applet. It creates the CellContainer
class that is passed to the SpreadsheetContainer. It also has
a TextField object used to edit the individual Cell values. The
components of SpreadsheetFrame are displayed using the GridBagLayout
manager.
When the user clicks on a cell in the SpreadsheetContainer, the
SpreadsheetFrame class gets a notification of the current cell
highlighted and sticks the text of the cell into the TextField.
When the user enters the text, the new formula is validated, and
the spreadsheet is redisplayed with the recalculated values.
The SpreadsheetFrame also has a menu attached to it. Currently,
the only menu options are Quit and New for a new spreadsheet.
Frames and menus will be discussed in more detail in the next
chapter.]
Listing 3.10. The SpreadsheetFrame Class.
// THIS CLASS IS NOT PUBLIC! Compile
in same file as SpreadsheetApplet class.
// This is the frame that controls that user interaction
// with the applet. It creates the initial spreadsheet
data
// and visual container, along with the input field for
// changing values of a cell, the scrollbars, and the menus
class SpreadsheetFrame extends Frame {
CellContainer c; //
The actual spreadsheet data...
SpreadsheetContainer s; // The spreadsheet view
Scrollbar hScroll; //
The scrollbars...
Scrollbar vScroll;
GridBagLayout g; //
Layout for Frame
MenuBar mbar; // The frames menu...
TextField t; //
The text field for the spreadsheet...
SpreadsheetCell currHighlight; // The currently
highlighted cell...
Applet appl; // The
applet...
int numRows; // Keep the initial parameters...
int numCols;
// The constructor
for the spreadsheet frame takes the
// values of the size of the Spreadsheet...
public SpreadsheetFrame(Applet
a,int rows, int cols) {
super("Spreadsheet Applet");
// Set the initial size and layouts...
resize(300,200);
g = new GridBagLayout();
setLayout(g);
appl = a; // Store the applet...
numRows = rows;
numCols = cols;
// Create the new container based on the applet parameters...
try {
c = new CellContainer(rows,cols);
}
catch (IllegalArgumentException e) {
System.out.println("Invalid
Spreadsheet parameters");
dispose();
}
// Add some fake
data to see how it works...
addTestData();
// Create display components...
addDisplayComponents();
// Add the menu
choices to the frames
addMenuItems();
// Pack before display...
pack();
resize(300,200); // Then reset to default
value...
show();
}
// Handle system and user events...
public boolean handleEvent(Event
evt) {
switch(evt.id) {
case Event.WINDOW_DESTROY:
{
dispose(); // Kill the
frame...
return
true;
}
// This
can be handled
case Event.ACTION_EVENT: {
String
menu_name = evt.arg.toString();
if (evt.target instanceof MenuItem) {
// Exit...
if(menu_name.equals("Quit"))
dispose(); //
Kill the frame...
// New Spreadsheet...
if(menu_name.equals("New"))
newSpreadsheet();
} //
end if
if (evt.target instanceof TextField) {
validateNewFormula();
return true;
} //
end if
if (evt.target instanceof SpreadsheetContainer)
{
changeInputFormula((SpreadsheetCell)evt.arg);
return true;
}
return false;
}
default:
return
false;
}
}
// Add the menu choices to the frames
void addMenuItems() {
mbar = new MenuBar();
Menu m = new Menu("File");
m.add(new MenuItem("New"));
m.addSeparator();
m.add(new MenuItem("Quit"));
mbar.add(m);
setMenuBar(mbar);
}
// Add the spreadsheet and input field
// to display...
void addDisplayComponents() {
GridBagConstraints
gbc = new GridBagConstraints();
// Create an input field across the top...
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.gridwidth
= GridBagConstraints.REMAINDER;
t = new TextField();
g.setConstraints(t,gbc);
add(t);
// Create the spreadsheet display...
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.weighty =
1.0;
gbc.gridwidth = GridBagConstraints.RELATIVE;
gbc.gridheight
= 10;
s = new SpreadsheetContainer(c);
g.setConstraints(s,gbc);
add(s);
// Set initial formula for text field...
changeInputFormula(s.getHighlight());
}
// Change the formula of the input field to that of
// the Object argument,
which is a spreadsheet cell...
void changeInputFormula(SpreadsheetCell sc) {
// Set the text
box with the formula...
if (sc != null)
t.setText(sc.getString());
else
t.setText("");
// Store the currently highlighted cell...
currHighlight
= sc;
}
// A text field formula has been entered...
// Validate it and
update spreadsheet...
// Update text field where necessary...
void validateNewFormula()
{
// Put up wait icon for calculations...
int oldCursor
= getCursorType();
setCursor(WAIT_CURSOR);
try {
// Replace the formula. If no problem,
then
// spreadsheet
will be recalculated...
s.replaceFormula(currHighlight, t.getText());
appl.getAppletContext().showStatus("Formula
accepted.");
}
catch (Exception
e) { // Handle illegal exception...
// Let browser status bar know about error...
// Get
the status bar from the AppletContext...
appl.getAppletContext().showStatus("Illegal
Formula syntax:
ÂUse
SUM,ADD,SUB,MULT,DIV");
}
// Always place
the converted formula in the text field...
changeInputFormula(s.getHighlight());
setCursor(oldCursor);
}
// Reload spreadsheet with blank data...
void newSpreadsheet()
{
// Create the new container based on the applet parameters...
try {
c = new CellContainer(numRows,numCols);
s.newCellContainer(c);
// Set initial formula for text field...
changeInputFormula(s.getHighlight());
}
catch (IllegalArgumentException
e) {
System.out.println("Invalid Spreadsheet
parameters");
dispose();
}
}
// Just some test data...
void addTestData() {
c.setCellFormula("1",0,0);
c.setCellFormula("2",0,1);
c.setCellFormula("3",0,2);
c.setCellFormula("4",0,3);
c.setCellFormula("SUM(A0,A3)",0,4);
// Should be 10
// c.setCellFormula("ADD(A0,A3)",0,4); //
Should be 5
// c.setCellFormula("ADD(A0,4)",0,4); //
Should be 5
// c.setCellFormula("ADD(1,4)",0,4); //
Should be 5
c.recalculateAll();
}
}
This class takes the applet parameters and determines the size
of the spreadsheet to be constructed. If the parameters are bad,
default values are used. When row and column sizes of the spreadsheet
are determined, an instance of the SpreadsheetFrame class is created
and the visual portion of the program begins. When the user moves
away from the current Web page, the spreadsheet is hidden; it
is redisplayed if the user returns.
Listing 3.11. The SpreadsheetApplet class.
// This file describes the applet class
that manages the
// spreadsheet program.
import java.awt.*;
import java.lang.*;
import java.applet.*;
// This applet kicks off the SpreadsheetFrame
// that manages the spreadsheet program.
public class SpreadsheetApplet extends Applet {
SpreadsheetFrame fr;
public void init() {
int rows = 10; //
Default if params are no good...
int cols = 10;
// Get the HTML
parameters
// and try to convert to a good value...
// Get the row...
try {
String
param = getParameter("rows");
int temp = Integer.parseInt(param);
if ((temp
> 1) && (temp < 26))
rows = temp;
else
throw new IllegalArgumentException();
}
catch (Exception e) { // Display
error to browser...
getAppletContext().showStatus("Invalid
row parameter. Using default...");
}
// Get the column...
try {
String
param = getParameter("columns");
int temp = Integer.parseInt(param);
if ((temp
> 1) && (temp < 40))
cols = temp;
else
throw new IllegalArgumentException();
}
catch (Exception e) {
getAppletContext().showStatus("Invalid
column parameter. Using default...");
}
// Create the spreadsheet frame
fr = new SpreadsheetFrame(this,rows,cols);
}
// If returning to screen, show frame...
public void start()
{
// Redisplay the applet...
try {
fr.show();
}
// Handle any problem...
catch(Exception
e) { }
}
// Hide the frame upon leaving...
public void stop()
{
// Handle where it may have been disposed...
try {
fr.hide();
}
// Handle any problem...
catch(Exception
e) { }
}
}
In this chapter, you have read about many of the basics of AWT
and seen it applied to the creation of a non-trivial spreadsheet.
In Chapter 4, "Enhancing the Spreadsheet
Applet," you will see how dialog boxes, streams, colors,
and fonts can be applied to improve the spreadsheet applet. More
of the fundamentals of AWT will be discussed in the context of
these enhancements. You will also see a practical example of streams
as the ability to save and open a spreadsheet is added to the
applet.

Contact
reference@developer.com with questions or comments.
Copyright 1998
EarthWeb Inc., All rights reserved.
PLEASE READ THE ACCEPTABLE USAGE STATEMENT.
Copyright 1998 Macmillan Computer Publishing. All rights reserved.