This chapter covers the various tools Java provides to help you build the user interface for your Java application or applet. Java provides a rich set of tools to make platform-independent, easy-to-use graphical user interfaces. Your Java project can use three groups of interface elements:
If you've ever programmed a graphical user interface (GUI) for the Mac, Windows, or UNIX, you'll find that the basic tools are familiar. Java is catching on because, unlike previous languages, the interface is pretty much platform independent so that you don't have to maintain multiple code bases-one for each target platform.
Although Java is technically platform independent, discrepancies still exist between platforms. These differences result from three different mechanisms:
Because you can't really do much about any of these issues, you
need to test your projects on as many different platforms as you
think they will run on. For applets that are used on the Internet,
that means pretty much every platform, whereas for an application
running on your company's intranet, it might mean only Sun and
SGI. The good news is that for simple interfaces, you'll find
that although everything doesn't look identical across platforms,
the matchup is pretty good. If you don't have access to multiple
platforms, you can be fairly sure that your interface will look
pretty much the same on any platform that supports Java.
Note |
If the content of the WWW site on which you're going to use the applet is machine specific, you probably don't have to worry about the applet not being pretty on other platforms. If you have a Mac software site, you probably won't get a whole lot of visits from Windows users. |
The Abstract Window Toolkit (or Alternative Window Toolkit, depending on who you talk to) is a visual interface kit that comes with all the current implementations of Java. As its name indicates, though, the intent is for other-hopefully, more powerful and easier to use-toolkits to be developed by various vendors in the future. For now, if you want to do any interface work in Java, the AWT package is the way to go.
Figures 17.1 through 17.3 show the inheritance hierarchy of the classes commonly used in building interfaces.
Figure 17.1 : AWT classes that inherit from Java.lang object.
Figure 17.3 : The Applet.class inherits from java.awt.Panel,so you can draw directly to an applet.
These class structures might seem a bit intimidating at first, but if you glance through them, you'll see that most AWT classes just inherit from Object. In addition, all the interactive elements (except menus) inherit from Component. The only other very important thing to note is that because Applet inherits from Panel (which inherits from Container), applets can directly contain other objects such as buttons, canvases, and so on. This section describes how you can build hierarchies of containers in applets.
All the other classes are described as they become relevant in the following sections. Use the diagrams included in these sections as quick references when you want to find out what capabilities a class inherits.
The Graphics class is part of the AWT. It's contained in java.awt.Graphics, and it's the basic class for everything you'll draw on-screen. Applets have associated Graphics instances, as do various components such as buttons. Drawing methods, such as drawLine, work on a Graphics instance, so you'll see many calls in this form in a typical Java applet:
public void paint(Graphics g) {
g.drawLine(10,10,20,20);
}
The Graphics class uses a standard computer coordinate system with the origin in the upper- left corner, as shown in Figure 17.4.
Figure 17.4 : The Java Graphics class coordinate system.
All coordinate measurements in Java are done in pixels. The size of items, therefore, in absolute units such as inches or millimeters, differs on various machines due to differing pixels/inch values.
You'll find that whenever your program has to draw something, you'll be using Graphics class methods. The following sections discuss the most useful methods. Here's a listing of the methods that aren't covered later in this chapter, but that occasionally will be useful.
Changes the clipping region to the intersection of the current clipping rectangle and the one specified in the input arguments. This means that the clipping region can only get smaller.
Copies the rectangular area defined by x, y, width, and height to a rectangle defined by new_x, new_y, width, and height in the same Graphics object.
Makes a clone of the Graphics object.
Makes a clone of the Graphics object, but with a clipping region defined by the intersection of the current clipping rectangle and the one specified by x, y, width, and height. In addition, the origin of the Graphics object returned by this call is set to x,y.
Sets the painting mode to draw over; it is used to undo the effects of setXORMode.
Sets the drawing mode to exclusive OR. The color is bitwise exclusive ORed with the current color and the foreground color to produce the final color.
Translates the origin of the Graphics area to the specified point.
You'll encounter three key methods over and over again as you work with the various user interface elements.
Requests a redraw of an item or an entire interface. It then calls update.
Controls what happens when repaint is called; you can override this method.
Determines what's done when any item is redrawn. It's called whenever something needs to be redrawn-for example, when a window is uncovered. All displayable entities have paint methods that they inherit from Component.
Note |
Interactive elements, such as buttons, are redrawn automatically; you don't have to implement a paint method for them. |
All these methods are defined in the Component class. This means that all the various interactive controls and applets inherit these methods. Although all these methods can be overridden, you'll find that paint and update are the ones you work with most often. Listing 17.1 shows a simple example of how paint and repaint can be used.
Listing 17.1. An example of paint and repaint.
import java.awt.*;
import java.applet.Applet;
public class paint_methods extends Applet{
int y;
public void init() {
y = 1;
}
public void start () {
while(true) {
y += 1;
repaint();
//wait 50 milliseconds and then call repaint again
try {
Thread.sleep(50);
} catch(InterruptedException e) {}
}
}
public void paint(Graphics g)
{
//draw a string to the screen
g.drawString("Hello, World!", 25, y );
}
}
After you run this code, you see the phrase Hello, World! slide down the screen. You're probably wondering why you don't see many copies of Hello, World! on-screen at the same time. The answer is the update method. Listing 17.2 shows the default update method.
Listing 17.2. The default update method.
public void update(Graphics g) {
g.setColor(getBackground()); //set the drawing color to the
//background color
g.fillRect(0,0,width,height); //fill the window with the
//background color
g.setColor(getForeground()); //reset the foreground color
paint(g); //call paint to redraw everything
}
The first three lines erase whatever is in the Graphics object so that anything that was drawn before it is deleted. You can change this behavior by overriding the update method. The version of the update method shown in Listing 17.3, when added to the applet in Listing 17.1, gives you the result shown in Figure 17.5 because the screen isn't erased between calls to paint.
Figure 17.5 : Overriding the update method.
Listing 17.3. A revised update method.
public void update(Graphics g) {
paint(g); //call paint to redraw everything
}
One thing to remember about calling the repaint method is that it doesn't always execute immediately. Java repaints as soon as it can, but sometimes repaints pile up because the computer is busy. Remember that your applet might be running on some fairly slow platforms; in this case, Java throws out some of the repaints, discarding the oldest repaints first. Because of this practice, three other versions of repaint are available with different input parameters.
Tries to call update for the number of milliseconds specified in the input argument. If update can't be called by the end of the specified time, repaint gives up. Note that no error is thrown if the method gives up. You will want to use this version primarily for animations, when you know that repaints will occur frequently.
Repaints only the rectangular part of the screen defined by x, y, width, and height.
Combines the previous two repaint versions so that only a portion of the screen is updated. If it can't be done before the specified time elapses (in milliseconds), it is skipped.
When you invoke repaint on a container, such as an applet, the AWT takes care of invoking update on all the items in the container-even ones inside other containers that are in the top-level container.
The AWT provides very generalized color support. The abstract ColorModel class enables you to define how colors are represented. The AWT comes with two predefined color models: IndexColorModel and DirectColorModel. IndexColorModel is used when images have a lookup table of colors and each pixel's value is an index in the lookup table. The pixel's color is determined by the color value in the lookup table specified by the index. DirectColorModel uses a 32-bit integer to hold the data for the color. Regardless of which color model you use, you still can represent individual colors as 32-bit integers (as described later in this section). In general, you won't have to worry about the ColorModel class.
The AWT's normal color model is RGB; in this model, a color is defined by its red, green, and blue components. Each of these three values is an int with a value between 0 and 255. An RGB value can be stored in an int with bits 31 through 24 being unused, bits 23 through 16 for the red value, bits 15 through 8 storing the green value, and bits 0 through 7 for the blue component of the color. The applet in Listing 17.4 shows how to extract the color components from an int.
Listing 17.4. Extracting color components from an integer.
import java.awt.*;
import java.applet.Applet;
public class unpack_color extends Applet
{
public void init()
{
//getRGB is a static method so you invoke it with the name of the
//class. You don't need a specific instance.
int temp = Color.white.getRGB();
unpack_colors(temp);
}
public void unpack_colors(int the_color)
{
int red, green, blue;
//the highest order byte is unused
//the next highest byte is red
red = the_color & 0x00FF0000;
//shift the value to the right so it's in the range 0-255
red = red >> 16;
//the next-to-last byte is green
green = the_color & 0x0000FF00;
//shift the value to the right so it's in the range 0-255
green = green >> 8;
//the lowest byte is blue
blue = the_color & 0x000000FF;
System.out.println("red = " + red + " green= " + green +
" blue= " + blue);
}
}
A Color class is available that enables you to use a single object rather than three ints to define a color. Most AWT methods that use colors require you to pass them an instance of a Color object.
The Color class also has
several methods for manipulating and creating colors.
Note |
All of the method descriptions in this chapter start with the type of value returned by the method (if no type is shown it's void) followed by the method name and its input arguments. For example, |
Returns a new color that is approximately 1.5 times brighter than the current color.
Returns a color about 70 percent as bright as the original color.
Checks to see whether two colors are the same.
Returns the blue component of a color.
Returns an instance of the Color class given a string containing a decimal or hexadecimal number. For example, 0x0000FF produces a deep blue. This is a static method. This version and the next two versions are useful primarily when reading in parameters from HTML.
Performs the same function as the preceding, except if the string isn't in the right format, it returns the color defined by the second parameter.
Performs the same function as the preceding, except you supply a default packed integer with RGB values, not an instance of color.
Returns an instance of the Color class based on the supplied HSB values (hue, saturation, and brightness). This is a static method.
Returns the green component of the color.
Produces an integer containing the RGB values of a color-with blue in the lowest byte, green in the second byte, and red in the third byte-when given the three HSB values. This is a static method.
Returns the packed-integer version of the RGB values of a color.
Returns the red component of a color.
Returns an array of floats containing the h, s, and b values of the equivalent color to the specified r, g, and b values.
As you probably guessed from the names of some of these methods, the AWT also lets you work with the HSB model. In this model, colors are represented as hue, saturation, and brightness.
There are a number of reasons to use the HSB representation. You can convert a color image to black and white by just setting the saturation of all the pixels to 0. Similarly, if you want to create a continuous color spectrum, it's easy to smoothly change the hue to get a continuum-within the limits of the color support of your monitor and Web browser-of colors. The applet shown in Listing 17.5 creates the rainbow shown in Figure 17.6; trust me-even though you can see it only in black and white, it looks great in color.
Listing 17.5. Creating a rainbow.
import java.awt.*;
import java.applet.Applet;
public class rainbow extends Applet{
int n_steps;
int rainbow_width;
public void init()
{
}
public void paint(Graphics g)
{
float h,s,b;
int x,y,i;
Color temp_color;
int patch_width,patch_height;
float incr;
n_steps = 256;
//define how much the Hue will change between steps
incr = (float)(0.25*Math.PI/n_steps);
rainbow_width = 256;
//figure out how wide each step would be
patch_width = (int)(rainbow_width/n_steps);
patch_height = 50;
//fix the value of Saturation to the maximum value
s = (float)1.0;
//fix the value of Brightness to the maximum value
b = (float)1.0;
y = 40;
x = 0;
//draw a set of rectangles filled with the colors of
//the rainbow
for(i=0;i<n_steps;i++) {
h = incr*i;
//create a new color using the HSB parameters
temp_color = Color.getHSBColor(h,s,b);
//set the current drawing color to the new color
g.setColor(temp_color);
x += patch_width;
//draw a rectangle whose upper lefthand corner is at
//x,y and which is patch_width wide and patch_height
//tall
g.fillRect(x,y,patch_width,patch_height);
}
}
}
Several generally useful classes exist that are used to contain information about locations and shapes. They're used as arguments for many of the drawing-related methods.
Stores a width and a height as attributes. You can access these attributes with this code:
Dimension d;
int w,h;
h = d.height;
w = d.width;
Represents an x and y coordinate. The points x and y are accessible. Point also supports two methods.
Sets the x and y values of the point to the input parameter values.
Adds the input parameter values to the current x and y values of the point-for example, x = x + delta_x.
Represents an arbitrarily ordered list of vertices. You can directly access the three main attributes.
Specifies the array of x values for the vertices.
Specifies the array of y values. The nth y value and the nth x value define a vertex location.
Polygon has several useful methods.
Specifies the number of vertices.
Adds a new vertex to the polygon.
Returns the smallest rectangle that contains all the vertices.
Returns TRUE if the point defined by x and y is inside the polygon.
This has four public instance variables: the x and y coordinates of its upper-left corner (unless the height or width is negative, in which case the origin is in one of the other corners), its width, and its height. Rectangle has a number of methods that make working with rectangular regions easy.
Adds the specified x,y point to a Rectangle by growing the rectangle to the smallest rectangle that contains the original rectangle and the point.
Performs the same function as the preceding method, except the input parameter is a point rather than two integers.
Grows the rectangle to the smallest one that contains the original rectangle and the rectangle supplied as the input parameter.
Grows the rectangle by the specified amount in height and width. Each side of the rectangle moves a distance delta_w so that the overall width of the rectangle increases by 2*delta_w. If the original rectangle width and height are 50,50 and the two input parameters to grow are 50,75, the new width and height would be 150,200.
Returns TRUE if the point x,y is inside the rectangle.
Returns a rectangle that contains the intersection of the two rectangles. If the rectangles don't overlap, the resulting rectangle has 0 height and width.
Returns TRUE if the rectangles overlap, including just a shared edge or vertex.
Returns TRUE if the height or width of a rectangle is 0.
Sets the origin of the rectangle to x,y.
Sets the origin of the rectangle to x,y, its width to width, and its height to height.
Sets the width of the rectangle to width and its height to height.
Moves the rectangle a distance d_x in x and d_y in y.
Returns a new rectangle that is the smallest one that contains both rectangles.
This abstract class serves as a bridge between the platform-specific and the platform-independent parts of Java. It's the interface used to create peers for components such as buttons, and its methods let Java programs find out about platform-specific features such as the available fonts and the characteristics of the desktop. Because this is an abstract class, you don't instantiate it, but it does have a number of useful methods, including the following.
The Toolkit is your primary interface to machine-dependent information and interfaces. It's usable in applications and applets. Methods that are implemented by the Applet class, such as getImage, are available in applications via the Toolkit. You can use the static method getDefaultToolkit() to get the Toolkit, as in this snippet:
try {
Toolkit current_toolkit = Toolkit.getDefaultToolkit();
} catch(AWTError e) {}
Note |
The peer of a component is the platform-specific interface definition of the methods that the component has to support. Implementing a Java component on a new platform (Linux, for example) consists of writing native methods to fill in the peer interface definition. |
Returns the screen size in pixels.
Returns the screen resolution in pixels/inch.
Returns the ColorModel, which defines how color is represented on the platform.
Gets a list of the names of the fonts available on the platform.
Returns the FontMetrics, which provide measurements of a font's screen size, for the specified font on the desktop.
Gets an image from the file specified in the input argument.
Gets an image from a specified URL.
Forces the loading of an image, with a given width and height. You can use the image observer to monitor the progress of loading the image. If width and height aren't the current dimensions of the image, the image is scaled.
Returns an integer that can be tested to determine the status of the image. ImageObserver constants that you can AND with the returned value are WIDTH, HEIGHT, PROPERTIES, SOMEBITS, FRAMEBITS, and ALLBITS. If the returned value ANDed with ALLBITS returns TRUE, the image is fully loaded.
Takes an image source, such as a filter, and creates an image.
The methods and tools described in this section enable you to draw simple graphical items that are non-interactive-although you can use the Canvas component and/or the location of mouse clicks to make these items behave as though they were interactive. If you've ever written code for a modern GUI, or even Windows, you'll find most of the drawing utilities fairly familiar. All these operations are implemented as methods on the Graphics class. Because anything you can draw on has a Graphics class as an attribute, this doesn't limit the utility of these functions.
You've already seen an example of drawing text using the drawString method. The general syntax for that method follows:
Graphics g;
g.drawString(String string_to_draw, int x_position, int y_position)
You also can use the drawChars method if you have an array of type char rather than a string.
Back in the dark ages, before the Mac and the desktop publishing revolution, people were used to having only a few fonts available for use on the computer. Things have changed dramatically since then; now people commonly buy CD-ROMs with more than 1,000 fonts for $50. Unfortunately, Java reintroduces a new generation to the wonders of limited fonts. This probably will change rapidly as Java and the Internet standards mature, but for right now, typographic simplicity is the order of the day.
You can create instances of the Font class using this creator syntax:
Font a_font = new Font(String name_of_font, int font_style, int font_size);
The generally available fonts follow:
Courier
Dialog
Helvetica
Symbol
TimesRoman
Because the set of available fonts may change, you might want to use a variation of the applet shown in Listing 17.6 to check for what fonts are available; this code works in applications as well.
Listing 17.6. Checking for available fonts.
import java.awt.*;
import java.applet.Applet;
public class discover_fonts extends Applet
{
public void paint(Graphics g){
String FontList[];
int i;
Font a_font;
FontList = getToolkit().getFontList();
for(i=0;i<FontList.length;i++) {
a_font = new Font(FontList[i],Font.BOLD,12);
g.setFont(a_font);
g.drawString("This is the " + FontList[i] +
" Font",25, 15*(i + 1));
}
}
}
This applet just gets a list of strings containing the names of the available fonts. Figure 17.7 shows the output of this applet.
Figure 17.7 : The available fonts.
The last line is Symbol font. The style for all the fonts was set to bold by using the font constant Font.BOLD. You also can use Font.ITALIC and Font.PLAIN. You can combine styles by bitwise ORing them. This line produces a font that is both italic and bold, for example:
Font a_font = new Font("Helvetica",(Font.BOLD | Font.ITALIC),12);
Note |
Not all fonts support all styles. Courier prints the same no matter what you set the style to, as you can see in Figure 17.7. |
You'll find that you often need to precisely position text in an applet. The FontMetrics class provides an easy way to find out how much space text drawn with a given instance of Font will be. Just in case you're not a typographic expert, Table 17.1 provides some quick definitions of the key font measurement terms, illustrated in Figure 17.8.
Figure 17.8 : Font terminology.
Height | The height of the tallest character in a font. It's therefore the maximum vertical distance you need to reserve when drawing a string with the font. |
Baseline | The bottom of all characters are positioned on this imaginary line. The descent part of a character, such as the bottom curl on a g, lies below this line. |
Ascent | Measures the height of the character above the baseline. This can include the amount of whitespace recommended by the font designer. |
Descent | Measures the height (or, more appropriately, depth) of the character below the baseline. This can include the amount of whitespace recommended by the font designer. |
The applet shown in Listing 17.7 shows you how to access the FontMetrics information. The result is shown in Figure 17.9.
Figure 17.9 : Viewing FontMetrics information.
Listing 17.7. Accessing FontMetrics information.
import java.awt.*;
import java.applet.Applet;
public class font_metrics extends Applet
{
public void paint(Graphics g){
Font a_font;
FontMetrics a_font_metric;
String the_message;
int string_width, font_height,font_ascent, font_descent;
int font_ascent_no_white, font_descent_no_white;
int y;
the_message = "Alien Space Monsters! ";
//Make a new font
a_font = new Font("Helvetica",Font.BOLD,16);
g.setFont(a_font);
//get the metrics for the font
a_font_metric = g.getFontMetrics();
//get the width of the message
string_width = a_font_metric.stringWidth(the_message);
//get the height of the font
font_height = a_font_metric.getHeight();
//get the ascent of the font; this includes whitespace
//recommended by the font designer
font_ascent = a_font_metric.getAscent();
//get the descent of the font; this includes whitespace
//recommended by the font designer
font_descent = a_font_metric.getDescent();
//get the ascent without whitespace
font_ascent_no_white = a_font_metric.getMaxAscent();
//get the descent without whitespace
font_descent_no_white = a_font_metric.getMaxDescent();
//now show these values to the user
y = 10;
g.drawString(the_message, 10,y);
y += font_height + 1;
g.drawString("Message width is " + string_width, 10,y);
y += font_height + 1;
g.drawString("Font height is " + font_height, 10,y);
y += font_height + 1;
g.drawString("Font ascent is " + font_ascent +
" without white space it's " + font_ascent_no_white , 10,y);
y += font_height + 1;
g.drawString("Font descent is " + font_descent +
" without white space it's " + font_descent_no_white , 10,y);
}
}
This information is useful in a number of ways. First, notice how the tops of the letters in the first line in Figure 17.9 are cut off. That's because when you specify the y coordinate for a string, you're specifying the location of the baseline-not the upper-left corner of the text that is being drawn. To figure out where to put the baseline, you just need to look at the value of the ascent. Instead of defining the first value of y as 10, just change it to this:
y = font_ascent + 2; //the extra 2 provides some whitespace
//that the font otherwise lacks
The first line now is completely visible, as Figure 17.10 shows.
Figure 17.10 : Keeping your text in sight.
Another common use for FontMetrics data is to center text in an area. The code in Listing 17.8 centers text in an area.
Listing 17.8. Centering text.
public void draw_centered_text(Graphics g, String the_msg,
int width, int height) {
int x,y;
FontMetrics fm;
fm = g.getFontMetrics();
//find out how much free space there is on either side of the string by
//subtracting the width of the string from the width of the window and
//dividing by 2
x = (width - fm.stringWidth(the_msg))/2;
//find out how much free space there is above
//and below the string's baseline
y = fm.getAscent() + (height - fm.getHeight())/2;
//draw the string so that the baseline is centered
g.drawString(the_msg,x,y);
}
Note |
You can get the width and height of an applet's area by using the getSize method, which returns a Dimension value. The height() and width() methods of the Dimension class enable you to get the height and width values. |
Figure 17.11 shows the result of centering text in a window.
Figure 17.11 : Centering text.
Three methods for drawing lines currently are available in Java.
Listing 17.9 shows a small applet that demonstrates how to draw
straight lines, arcs, and a point.
Note |
No special command for drawing individual pixels is available. Just use drawLine as shown in Listing 17.9; alternatively, you can use drawRect or fillRect, which are discussed later in this section. |
Listing 17.9. Drawing straight lines, arcs, and a point.
import java.awt.*;
import java.applet.Applet;
public class drawpoint extends Applet{
public void paint(Graphics g)
{
int x_final, y_final;
int i;
//this draws a line
g.drawLine(10,10,100,100);
//this draws a point at 10,30
g.drawLine(10,30,10,30);
//this draws an arc
g.drawArc(50,50,30,30,0,180);
//this draws a filled arc
g.fillArc(50,100,20,40,90,90);
}
}
This applet generates the picture shown in Figure 17.12.
Figure 17.12 : A point, a line, an arc and a filled arc.
Here are the full descriptions for the three line-drawing methods.
This draws a line between two points.
x_start: Starting x position
y_start: Starting y position
x_end: Final x position
y_end: Final y position
This routine draws an arc, a segment of an ellipse, or a circle, as Figure 17.13 shows.
Figure 17.13 : Parameters for drawArc and fillArc method.
x: Upper-left corner of the rectangle that contains the ellipse the arc is from.
y: Upper-left corner of the rectangle that contains the ellipse the arc is from.
width: The width of the rectangle that contains the ellipse the arc is from.
height: The height of the rectangle that contains the ellipse the arc is from.
start_angle: The angle at which the arc starts; 0 degrees is at the 3 o'clock position, and the value increases in the counterclockwise direction.
This is the same as drawArc, except that the arc is filled with the current color.
One of the key shortcomings with current Java drawing routines is that you can't specify a line width. All the drawing commands draw unit-width lines. If you want to draw thicker lines, you can follow an approach similar to the one shown in Listing 17.10.
Listing 17.10. Drawing thicker lines.
public void draw_thick_line(Graphics g, int x_start,
int y_start, int x_final,
int y_final, int width) {
int i;
for (i=0;i<width;i++) {
g.drawLine(x_start+i,y_start + i,x_final+i,y_final+i);
}
}
This method just draws several lines to make a thicker line. You can use the same approach for the shapes you'll see in the next section.
The final way to draw very complex lines is to use the drawPolygon method, which is described in the next section. With it, you can draw arbitrarily complex paths.
Java provides a standard set of methods for drawing typical geometric shapes, such as rectangles, ovals, and polygons. It also has a method for drawing 3D rectangles (rectangles with shading to make them look 3D), but because the line widths are so narrow, it's almost impossible to see the 3D effect under normal circumstances. The syntax for the various shape-drawing methods follow.
This draws a traditional rectangle.
x: Upper-left corner
y: Upper-left corner
width: Width of the rectangle
height: Height of the rectangle
This draws a rectangle with rounded corners. You define the amount of curvature by setting the width and height of the rectangle that contains the curved corner (see Figure 17.14).
Figure 17.14 : Defining the degree of roundness for rounded rectangles.
x: Upper-left corner
y: Upper-left corner
width: Width of the rectangle
height: Height of the rectangle
r_width: Width of the rectangle that defines the roundness of the corner
rectangle: Defines the roundness of the corner
r_height: Height of the rectangle that defines the roundness of the corner
This draws a rectangle with a 3D shadow. Because the lines are so thin, you can't really see the shadow.
x: Upper-left corner
y: Upper-left corner
width: Width of rectangle
height: Height of rectangle
flag: If TRUE, the rectangle is raised; if FALSE, the rectangle is indented
This takes two lists-one with x and one with y coordinates-and draws a line between successive points. The polygon will not close automatically. If you want a closed polygon, you must make sure that the first and last points have the same coordinates.
x: An array of integers with the x coordinates of the polygon's vertices
y: An array of integers with the y coordinates of the polygon's vertices
n_points: Number of vertices in the polygon
This draws an oval. If width and height have the same value, you get a circle.
x: Upper-left corner of the rectangle that contains the oval (see Figure 17.15)
y: Upper-left corner of the rectangle that contains the oval
width: Width of the rectangle that contains the oval
height: Height of the rectangle that contains the oval
Figure 17.15 : Defining the size of an oval with the width and height parameters.
For each method starting with "draw," there is another method that starts with "fill." The "fill" methods have the same parameters and behave in the same way, except that they draw versions of the shapes filled with the current color.
The applet in Listing 17.11 shows how the draw and fill versions of the various commands work. It produces the screen shown in Figure 17.16.
Figure 17.16 : The various drawing functions in action.
Listing 17.11. Using the draw and fill commands.
import java.awt.*;
import java.applet.Applet;
public class drawshapes extends Applet
{
public void paint(Graphics g)
{
int i;
int x,y,x_o,width,height;
//set the x coordinates for the polygon vertices
int x_list[] = {80,90,120,83,80};
//set the y coordinates for the two polygons
int y_list[] ={5,35,60,40,10};
int y_list_1[] = {70,95,120,100,70};
g.drawRect(10,5,20,20);
g.fillRect(10,27,20,20);
g.drawOval(50,5,20,20);
g.fillOval(50,27,20,20);
g.drawPolygon(x_list, y_list, 5);
g.fillPolygon(x_list,y_list_1,5);
g.drawRoundRect(130,5,20,20,5,5);
g.fillRoundRect(130,27,20,20,10,15);
g.draw3DRect(160,5,20,20,true);
g.draw3DRect(160,27,20,20,false);
width = 2;
height = 2;
x = 10;
x_o = 30;
y = 50;
//draw a set of ten regular and filled rectangles and ovals
for(i=0;i<10;i++) {
y += 25;
width += 1;
height += 2;
g.drawRect(x,y,width,height);
g.drawOval(x_o,y,width,height);
}
}
}
The 3D rectangles don't look all that three-dimensional. The effect is subtle, and you should experiment with it on different platforms and with different color combinations to find one that looks good. Figure 17.17 shows the result of an applet that draws two standard 3D rectangles-one raised and the other sunken-with white lines on a black background. It then draws two thick-sided 3D rectangles by drawing several 3D rectangles together. You can see-assuming that the print quality of the book is good enough-in the standard 3D rectangles that in both cases two sides of the rectangle are slightly darker than the other two sides. Just in case you had trouble seeing that (and I know that I did), this effect is fairly clear on the thick-sided 3D rectangles.
Figure 17.17 : The secret of 3D rectangles.
One of the keys to the success of Java is its capability to add animation and action to Web pages without requiring you to download huge video files. Even though Java is an interpreted language (assuming that your users' browsers don't have a JIT-Just In Time compiler-installed), it's more than fast enough for most animation tasks. Listing 17.12 shows a simple animation that moves a yellow square over a red and blue checkerboard pattern. Figure 17.18 shows a screen shot-unmoving, unfortunately.
Figure 17.18 : Animation in action-well use your imagination.
Listing 17.12. A simple animation applet.
import java.awt.*;
import java.applet.Applet;
public class simplest_animation extends Applet implements Runnable
{
int x_pos,y_pos;
int n_squares;
int dh,dw;
Color color_1,color_2;
Color square_color;
int square_size;
int square_step;
Thread the_thread;
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
while (true) {
repaint();
try {
the_thread.sleep(50);
} catch(InterruptedException e) {};
}
}
public void init()
{
int w_height, w_width;
//set the starting point of the moving square
x_pos = 0;
y_pos = 0;
//set the size of the moving square
square_size = 40;
//set how much the square moves between screen redraws
square_step = 2;
//specifies the number of squares on each side of the checkerboard
n_squares = 10;
//get the size of the applet's drawing area
w_height = size().height;
w_width = size().width;
//determine the size of the squares in the checkerboard
dh = w_height/n_squares;
dw = w_width/n_squares;
//set the colors for the checkerboard.
//Could have used Color.blue and
//Color.red instead of creating new color instances
color_1 = new Color(0,0,255);
color_2 = new Color(255,0,0);
//set the color for the moving square
square_color = new Color(255,255,0);
}
public void draw_check(Graphics g) {
int i,j,offset;
int x,y;
//this draws a checkerboard
offset = 0;
for(i=0;i<n_squares;i++) {
y = i * dh;
offset++;
for(j=0;j<n_squares;j++) {
x = j * dw;
if (((j + offset)% 2) > 0) {
g.setColor(color_1);
} else {
g.setColor(color_2);
}
g.fillRect(x,y,dw,dh);
}
}
}
public void paint(Graphics g)
{
//draw the blue and red checkerboard background
draw_check(g);
//increment the position of the moving square
x_pos += square_step;
y_pos += square_step;
//set the drawing color to the square color
g.setColor(square_color);
//draw a filled rectangle that moves
g.fillRect(x_pos,y_pos,square_size, square_size);
}
}
What you can't see unless you run the applet is that the whole image is flickering; you can see the background through the yellow square. All in all, the word that comes to mind is ugly-hardly the sort of thing that will help sell a Web page. You can reduce this flicker in several ways. A large part of the flicker occurs when repaint invokes the update method. You saw earlier in this chapter that the update method erases the applet by drawing a background colored rectangle. This erasing, followed by the drawing of the background rectangle, causes a lot of the flicker. You can avoid this by overriding the update method with this version:
public void update(Graphics g) {
paint(g);
}
This eliminates the flicker in most of the image, but you'll still see flicker in the region of the rectangle. That's because the background is drawn and then the square is drawn over it. Your eye sees the background and then the yellow square every time the screen is redrawn. One way to reduce the amount of drawing that has to be done is to tell the AWT to redraw only the part of the screen that has changed.
The version of the paint
method shown in Listing 17.13 does just that. It creates two rectangles:
one for where the square is and one for where it will be after
it's moved in this call to paint.
The union method on Rectangle
then is used to get the smallest rectangle that contains both
those rectangles. That rectangle is the one that contains all
the changes between the two frames. Next, clipRect
is used to tell the AWT to repaint only this rectangle.
Note |
Instead of creating two new rectangles each time paint is called, you could save the second rectangle in an instance variable and then use it as the old rectangle in the next call to paint. |
Listing 17.13. Repainting the minimal area.
public void paint(Graphics g)
{
Rectangle old_r, new_r,to_repaint;
draw_check(getGraphics());
//Figure out where the square is now
old_r = new Rectangle(x_pos,y_pos,square_size,square_size);
//Figure out where the square will be after this method executes
new_r = new Rectangle((x_pos + square_step),(y_pos +
square_step),square_size, square_size);
//Find the smallest rectangle that contains the old and new positions
to_repaint = new_r.union(old_r);
//Tell Java to only repaint the areas that have been
// affected by the moving square
g.clipRect(to_repaint.x,to_repaint.y,
to_repaint.width,to_repaint.height);
x_pos += square_step;
y_pos += square_step;
g.setColor(square_color);
g.fillRect(x_pos,y_pos,square_size, square_size);
}
Although using clipRect reduces the amount of work the AWT has to do, it's still not fast enough to fool your eye. To do that, you need to use double buffering.
Double buffering involves doing all your drawing to an invisible, off-screen bitmap-an image, actually-and then copying that off-screen image to the applet. This is called bitblitting on the Mac and results in much faster drawing. Listing 17.14 shows the commented changes you need to make in the animation applet to do double buffering.
Listing 17.14. Using double buffering.
public class simple_animation extends Applet implements Runnable
{
....
//Define an image to use for offscreen drawing
Image offscreen;
Graphics offscreen_graphics;
....
public void init()
{
....
//create the offscreen image and get its Graphics instance
offscreen = createImage(size().width,size().height);
offscreen_graphics = offscreen.getGraphics();
//draw the background checkerboard
draw_check();
}
....
public void draw_check() {
int i,j,offset;
int x,y;
offset = 0;
for(i=0;i<n_squares;i++) {
y = i * dh;
offset++;
for(j=0;j<n_squares;j++) {
x = j * dw;
if (((j + offset)% 2) > 0) {
offscreen_graphics.setColor(color_1);
} else {
offscreen_graphics.setColor(color_2);
}
offscreen_graphics.fillRect(x,y,dw,dh);
}
}
}
public void paint(Graphics g)
{
Rectangle old_r, new_r,to_repaint;
old_r = new Rectangle(x_pos,y_pos,square_size,square_size);
new_r = new Rectangle((x_pos + square_step),(y_pos +
square_step),square_size, square_size);
to_repaint = new_r.union(old_r);
draw_check();
//just draw what's needed except for the first time
if (x_pos < 1) {
g.clipRect(to_repaint.x,to_repaint.y,
to_repaint.width,to_repaint.height);
}
x_pos += square_step;
y_pos += square_step;
//same as before but now the square is drawn to the offscreen image
offscreen_graphics.setColor(square_color);
offscreen_graphics.fillRect(x_pos,y_pos,square_size,
square_size);
//now that the offscreen image is all done draw the whole thing
//to the screen
g.drawImage(offscreen,0,0,this);
}
}
These items are the ones that enable the user to dynamically interact with your program. They range from buttons to text display areas. Although different operating systems tend to use slightly different interaction elements, the AWT provides a rich enough set that users on all platforms will feel pretty much at home. This is especially true because the visual representation of each item actually is generated by the host operating system on the machine on which the application or applet is running. In addition, freeware, shareware, and commercial widget kits already exist that extend the basic elements provided by the AWT (see the section "Extending the AWT," later in this chapter, for more details).
As you saw in Figure 17.3, all the active components (other than menus), such as Button, inherit from the Component class. The Component methods provide a wide selection of functionality applicable to any interactive graphical element. Although you can't create an instance of Component, you'll use its methods fairly often. Component has a lot of methods, but this section lists some of the ones you'll use fairly often. These methods are invoked in response to various types of events. In all cases, if the method returns TRUE, it means that the method has handled the event. If FALSE is returned, the event is passed up the event chain. You can use these methods on multiple objects to deal with the same event.
This method usually is overridden. It's called whenever an ACTION_EVENT occurs on a component. Events are discussed in Chapter 21, "Event Handling."
This is called when a KEY_PRESS or KEY_ACTION event reaches a component. The key parameter specifies which key was involved. You can use this to have components respond to key clicks.
This method is invoked when the component receives a KEY_RELEASE event.
This is called when the object receives a LOST_FOCUS event.
This is invoked when the component receives a MOUSE_DOWN event, caused by the user clicking the mouse inside the component. The x and y coordinates are in the coordinate system of the component, where 0,0 is in the upper-left corner.
This is invoked when the user drags the mouse with the mouse button down over the component, generating a MOUSE_DRAG event.
This is invoked each time the mouse goes over the component, generating a MOUSE_ENTER event.
This is called when the component receives a MOUSE_EXIT event. The x and y values-which are expressed in the component's coordinates-represent the first point outside the component's bounding rectangle that the mouse goes over.
Although Component has a large selection of methods, the following are the ones you'll use most often.
Returns the bounding rectangle that contains the component.
Monitors the status of an image as it's being composed. You can use this to wait to display a component, such as a Canvas, that uses an image until the image is ready.
Creates a new Image of the specified size.
Disables the component so that the user can't interact with it. (This is a synchronized method.) The AWT draws a disabled component differently than an enabled one.
Enables a disabled component. This is a synchronized method.
Returns the color of the background for the component.
Returns the current font for the component.
Gets the FontMetrics, which contains information about the size of text on the current platform, for the component.
Returns the foreground color-the one that will be used to draw lines, fill shapes, and so on.
Gets the Graphics object associated with the component. You can then use drawing methods, such as fillRect, that are associated with the Graphics object to draw on the component.
Makes the component invisible. This is a synchronized method.
Returns TRUE if x,y lies inside the component's bounding rectangle. x and y should be specified in the coordinate system of the container that holds the component. The container's coordinate system origin is in the upper-left corner of the container. This is a synchronized method.
Sets a flag indicating that the component has been changed in a way that requires the Layout Manager to be called to lay out the screen again. A button's name might be made longer, for example, so the button will need to be resized.
Returns TRUE if the component is enabled to respond to user actions.
Returns TRUE if the component is visible in its parent's window. It can be visible but not showing if its height or width is 0 or if its location is outside the parent's window; for example, it might have been scrolled off-screen.
Returns TRUE if the component currently is visible. You can make a component invisible by invoking its hide method.
Returns a point that contains the coordinates of the component's origin.
Moves the component to the specified position in the parent container's coordinate system.
Redraws the component when it needs to be redrawn. Unless you want some custom behavior, the default method ensures that the component is drawn properly.
Enables you to get an image ready for display prior to displaying it on the component. Another version enables you to specify a size for the image so that it can be scaled.
Repaints this component by a specified time or cancels the request.
Repaints the specified part of the component.
Tries to repaint the specified region. If it can't do so before the specified time, it quits.
Enables you to specify the position and size of the component. This is a synchronized method.
Scales the component to fit in the defined bounding rectangle maintaining the same origin. This is the same as the version below except you specify the width and height separately rather than with a Dimension object.
Scales the component to fit in the defined bounding rectangle maintaining the same origin.
Sets the background color for a component. This is a synchronized method.
Specifies the font that will be used for any text drawn in the component. This is a synchronized method.
Sets the color used for drawing lines and filling in shapes. This is a synchronized method.
Makes the component visible if it had been hidden.
Returns the height and width of the component.
Erases the contents of the component's graphic area every time it's called.
Causes the component to see whether it or any of the components it contains is invalid. If any are invalid, the Layout Manager is called to bring things up-to-date. See the section "Buttons," later in this chapter, for an example of how to use invalidate/validate.
Remember that all the interactive interface elements, including containers such as applets and windows, inherit from Component.
The AWT containers contain classes that can contain other elements. Windows, panels, dialog boxes, frames, and applets are all containers. Whenever you want to display a component such as a button or pop-up menu, you'll use a container to hold it. The base class for all containers is-surprise! surprise!-the Container class.
The Container class has a number of methods that make it easy to add and remove components as well as to control the relative positioning and layout of those components. Containers can contain other containers, for example, so a window can contain several panels.
Container is an abstract class, and the methods you'll use most often follow.
Adds a component to the container.
Adds a component at the specified z position. This is a synchronized method. Be warned that the order of clipping based on relative z position may vary between machines. This problem should be fixed eventually, though.
Returns the number of top-level components of a container. It doesn't count components inside components; for example, a panel with three buttons inside a window is counted only as one component for the window.
Returns a reference to the index component. The index value is determined when the component is added. This is a synchronized method. This throws ArrayIndexOutOfBoundsException.
Returns an array of references to all the components in the container. This is a synchronized method.
Returns the insets object for the container. Insets define the empty space the Layout Manager reserves around the edge of the container-the minimum distance from the edge of a component to the edge of the container.
Removes the component from the container. This is a synchronized method.
Sets the Layout Manager the container will use. If you supply NULL as the argument, no Layout Manager is used; you can use absolute positioning.
Applet inherits from this class, so this section examines Panel in detail so that you can understand how the various demonstration applets work. The other container classes are discussed later in this section.
Panel inherits from Container.
It doesn't create its own window because it's used to group components
inside other containers. Panels enable you to group items in a
display in a way that might not be allowed by the available Layout
Managers. If you have a number of entries in your interface, for
example, that have a label and a text field, you can define a
panel that contains a label and a text field and add the panel
so that the label and the text field always stay together on the
same line (which wouldn't be the case if you added the two items
separately). Without the panel, the Layout Manager could put the
label and the text field on different lines. Panels also are useful
in Layout Managers in which only one item is allowed in an area,
such as the BorderLayout
Manager. By using a panel, you can put several components in a
single BorderLayout area,
such as North.
Note |
A Layout Manager autopositions and sizes the various interface components, taking into account the screen resolution and the window size. |
An inset object defines the amount of empty space around the edge of a panel. The creator method for insets follows:
Insets, new Insets(int top, int left, int bottom, int right)
This defines a new Insets instance, which defines the boundaries specified by the input arguments.
You can change the amount of empty space, which is set to 0 by default, by overriding the Insets method of the container. The applet in Listing 17.15 defines its own panel class that does that. It defines a plain panel and a custom version that overrides the Insets method. Each of the items has four buttons added. Both items have white backgrounds so that you can see the size of the item, not just where the buttons are.
Listing 17.15. Defining a panel class.
import java.awt.*;
import java.applet.Applet;
public class HelloWorld extends Applet
{
public void init()
{
Panel a;
my_panel b;
GridLayout gl;
Button buttons[];
int i;
//create a new GridLayout to force the 4 buttons to arrange themselves
// in a 2 by 2 grid
gl = new GridLayout(2,2);
//create two panels to contain the 8 buttons
a = new Panel();
b = new my_panel();
//tell the panels to use the GridLayout manager rather than the default
//FlowLayout manager
a.setLayout(gl);
b.setLayout(gl);
//Make the backgrounds of the panels white so you
//can see them in the picture
a.setBackground(Color.white);
b.setBackground(Color.white);
//add the panels to the applet
add(a);
add(b);
//make the buttons and add them to the panels
buttons = new Button[8];
for(i=0; i< 8;i++) {
buttons[i] = new Button("Button " + i);
if (i <4) {
a.add(buttons[i]);
} else {
b.add(buttons[i]);
}
}
}
}
class my_panel extends Panel {
//This class exists so we can override the insets method
public Insets insets() {
return new Insets(5,10,15,20);
}
}
The applet generates the applet interface shown in Figure 17.19; look closely to see the white background for the first panel. Because the insets for the top panel default to 0, the panel background is the same size as the space required by the buttons. The custom panel is larger than the buttons because of the inset's value; the space on each side is different because the four values assigned to the inset are all different.
Figure 17.19 : Panels with the default insets and with custom insets.
A frame is a full-fledged, top-level, resizable window with a menu bar. You can specify the title, an icon, and a cursor. See the "Frames" section for examples.
This class isn't used very often, but it's a top-level window without borders and a menu bar.
Labels are text items that don't really do much. By using a label instead of drawString, you can use the Layout Managers to control text placement in a platform- and monitor-independent manner. The code in Listing 17.16 shows how to use labels. About the only significant flexibility you have, other than the alignment of the text, is the capability to change the font used (see Figure 17.20).
Figure 17.20 : Aligning text and modifying the fonts for labels.
Listing 17.16. Using labels.
import java.awt.*;
import java.applet.Applet;
public class label_example extends Applet{
Label the_labels[];
public void init(){
int i;
//set up an array of labels
the_labels = new Label[3];
//Create a label with the default format
the_labels[0] = new Label("on the left");
//these two commands show how to set the color of a Label
//the text itself is drawn with the Foreground color
the_labels[0].setBackground(Color.red);
the_labels[0].setForeground(Color.white);
//Make a new Font and then assign it to the label
Font a_font = new Font("TimesRoman",Font.PLAIN,24);
the_labels[0].setFont(a_font);
//Create a centered label
the_labels[1] = new Label("middle", Label.CENTER);
//Make a new Font and then assign it to the label
a_font = new Font("TimesRoman",Font.BOLD,24);
the_labels[1].setFont(a_font);
//Create a label aligned to the right
the_labels[2] = new Label("on the right", Label.RIGHT);
//Make a new Font and then assign it to the label
a_font = new Font("Helvetica",Font.ITALIC,18);
the_labels[2].setFont(a_font);
//these two commands show how to set the color of a Label
//the text itself is drawn with the Foreground color
the_labels[2].setBackground(Color.white);
the_labels[2].setForeground(Color.blue);
//add the three labels to the applet
for(i=0;i<3;i++) {
add(the_labels[i]);
}
}
}
The label creators and the most useful methods for the Label class follow:
Produces a label with the specified string.
Produces a label with the string aligned according to the second value, which should be one of the three constants Label.CENTER, Label.LEFT, or Label.RIGHT.
Returns the label string.
Changes the label text.
Java buttons are just like the buttons in every other GUI. They are text surrounded by a shape, and they generate an ACTION_EVENT event-the argument is a button's label-after the user clicks them. Java uses the native operating system-Mac, Windows, UNIX, and so on-to actually draw the buttons, so the look and feel of the buttons will be what is expected by users on each platform. Listing 17.17 shows a simple example of using buttons; see Chapter 21 for more information on handling events.
Listing 17.17. Using buttons.
import java.awt.*;
import java.applet.Applet;
public class buttons extends Applet{
Button a_button;
public void init()
{
//make a new button called "Howdy"
a_button = new Button("Howdy!");
//add the button to the Applet, which extends Panel
add(a_button);
}
public boolean action (Event e, Object o) {
Dimension d_should;
if (e.target instanceof Button) {
//check the button label and toggle between the two values
if (((String)o).equals("Howdy!")) {
a_button.setLabel("Alien Space Monster!");
} else {
a_button.setLabel("Howdy!");
}
//mark the button as having changed
a_button.invalidate();
//tell the applet to validate the layout
validate();
}
return true;
}
}
This applet starts out with a single button and then toggles the name of the button between two values every time the button is clicked. Figure 17.21 shows the applet before the button is clicked, and Figure 17.22 shows it afterward.
Figure 17.21 : A simple button.
Figure 17.22 : A button in action
Notice how the invalidate method, inherited from Component, is used to mark the button as having changed in a way that would affect the layout. That isn't sufficient to force the applet to automatically lay out the screen again, although it ensures that the next time the screen is redrawn, things will look okay, so you have to invoke the validate method, inherited from Component, on the applet.
The available button creator calls and the key button methods follow.
Creates a button with no label (see Figure 17.23).
Figure 17.23 : A button without a label.
Creates a button with the specified label.
Sets the button label to the specified string.
Returns the current button label as a string.
Checkboxes are text items with a checkable icon next to
them. They're generally used when you want the user to be able
to set several options prior to making a decision. You usually
don't do anything when a checkbox is checked or unchecked, you
usually just read the values of the checkboxes when some other
control, such as a button or menu item, is activated. Just in
case you do want the code to do something when a box's state changes,
checkboxes generate an ACTION_EVENT
with the new Checkbox state
as the argument after the user clicks on them.
Note |
Radio buttons look just like checkboxes, but they are grouped and only one radio button in a group can be checked at any given time. The next section discusses how to implement radio buttons. |
The code in Listing 17.18 produces the applet interface shown in Figure 17.24.
Figure 17.24 : Checkboxes in action.
Listing 17.18. Checkboxes without a bank.
import java.awt.*;
import java.applet.Applet;
public class checkboxes extends Applet
{
public void init()
{
Checkbox box_1, box_2, box_3;
box_1 = new Checkbox();
box_2 = new Checkbox("this is a labeled checkbox");
box_3 = new Checkbox("Labeled and checked", null, true);
add(box_1);
add(box_2);
add(box_3);
}
}
The creator's methods for Checkbox and the key checkbox methods follow.
Creates a new checkbox with no label.
Creates a new checkbox with a label.
Creates a new checkbox that is labeled and checked. The middle argument is used with radio buttons.
Changes the label of a checkbox.
Returns the current label as a string.
Gets the current checkbox state (checked = TRUE).
Sets the checkbox state.
Checkboxes and radio buttons look different. Even though radio buttons are made up of checkboxes, they're called radio buttons because that's what they're called in most current GUIs. The only functional difference is that only one of the items in a radio button group can be selected at one time, like the buttons on your car radio. This is useful when you want your user to select one of a set of options. The AWT creates a radio button group by associating a CheckboxGroup instance with all the checkboxes in the group, as shown in Listing 17.19.
Listing 17.19. Creating a radio button group.
import java.awt.*;
import java.applet.Applet;
public class radio_buttons extends Applet
{
public void init()
{
CheckboxGroup group;
Checkbox box_1,box_2,box_3;
CheckboxGroup group_1;
Checkbox box_1_1,box_2_1,box_3_1;
//set up the first radio button group
group = new CheckboxGroup();
box_1 = new Checkbox("Yes", group, true);
box_2 = new Checkbox("No", group, false);
box_3 = new Checkbox("Maybe", group, false);
//set up the second group
group_1 = new CheckboxGroup();
box_1_1 = new Checkbox("Yes", group_1, false);
box_2_1 = new Checkbox("No", group_1, false);
box_3_1 = new Checkbox("Maybe", group_1, false);
//add the components to the applet panel
add(box_1);
add(box_2);
add(box_3);
add(box_1_1);
add(box_2_1);
add(box_3_1);
}
}
Figure 17.25 shows the interface to this applet. The first thing to note is that the second group doesn't have any button selected. That's because none of them were created with a checked state. As soon as the user clicks one of them, though, it won't be possible to return all of them to the unchecked state. Clicking a selected item, such as Yes in the first group, does not change its state to unchecked. Selecting one of the other buttons in the first group deselects Yes and selects the button you clicked.
Figure 17.25 : Radio buttons (nearly the same as checkboxes).
Radio buttons have only one creator method:
new Checkbox(String the_label, CheckboxGroup a_group, boolean checked?)
This creates a new Checkbox that is labeled and checked. The middle argument defines which radio button group the checkbox belongs to.
In order to use radio buttons, you also need to create a new checkbox group. Use this code:
new CheckboxGroup()
Because radio buttons are implemented as checkboxes, the methods described in the "Checkboxes" section are the ones you'll use to get and set information.
Choice menus-often called pop-up menus-are designed to allow the user to select an option from a menu and see the value chosen at all times. Figures 17.26 and 27 show how this works. The label of the Choice menu is the currently selected menu item. Choice menus can be constructed as shown in Listing 17.20.
Figure 17.26 : Selecting from a Choice menu.
Figure 17.27 : Showing the currently selected menu item.
Listing 17.20. Creating a Choice menu.
import java.awt.*;
import java.applet.Applet;
public class popup_menus extends Applet
{
public void init()
{
Choice a_menu;
//create the new choice item
a_menu = new Choice();
//add the menu items
a_menu.addItem("Red");
a_menu.addItem("Green");
a_menu.addItem("Blue");
//add the menu to the applet panel
add(a_menu);
}
}
You can add any number of items to a Choice menu, but you can't add hierarchical menus as you can with regular menus. The creator method and other useful methods follow.
Creates a new Choice item.
Adds an item to the Choice menu. It can throw a NullPointerException. This is a synchronized method.
Returns the number of items currently in the menu.
Returns the text of the specified menu item (item 0 is the first item in the menu).
Returns the index of the currently selected item (item 0 is the first item in the menu).
Returns the text of currently selected menu items.
Changes the selection to the specified item. This is a synchronized method, and it can throw IllegallArgumentException.
Selects the menu item for which the name is the specified string.
Scrolling lists display multiple lines of text, and each line corresponds to a selection item. Scroll bars are displayed if the text is larger than the available space. The user can select one or more of the lines. Your program can read the user's selections. Lists generate three event types:
The applet shown in Listing 17.21 shows how to create single- and multiple-choice scrolling lists. Figure 17.28 shows the resulting interface elements. Note that the scrollbar for the left list is disabled because all the items in the list are visible.
Figure 17.28 : Single- and Multiple-choice lists.
Listing 17.21. Creating single- and multiple-choice scrolling lists.
import java.awt.*;
import java.applet.Applet;
public class list extends Applet
{
public void init()
{
List mult_choice, single_choice;
//define a scrolling list that shows 6 elements at a time and that
//allows the selection of multiple items at the same time
mult_choice = new List(6,true);
single_choice = new List();
//define the list entries
mult_choice.addItem("Red");
mult_choice.addItem("Green");
mult_choice.addItem("Blue");
mult_choice.addItem("Yellow");
mult_choice.addItem("Black");
mult_choice.addItem("Azure");
single_choice.addItem("Chicago");
single_choice.addItem("Detroit");
single_choice.addItem("Los Angeles");
single_choice.addItem("Atlanta");
single_choice.addItem("Washington");
single_choice.addItem("Lincoln");
single_choice.addItem("LaGrange");
//add the lists to the applet panel
add(mult_choice);
add(single_choice);
}
}
The key methods you'll most commonly use with lists follow.
Adds the specified item to the end of the current list of items in the list. This is a synchronized method.
Adds the specified item to the list at the specified location. This is a synchronized method. Remember that the first item in the list is numbered 0. For example, addItem("a test", 3) puts "a test" into the fourth position in the list and slides the previous fourth entry and all entries after it down one.
Removes all the entries in the list. This is a synchronized method.
Returns the number of items currently in the list.
Deletes the list item at the specified location. This is a synchronized method.
Deletes all the items between the first and last location, inclusive. This is a synchronized method. For example, delItems(1,3)-note the s at the end of Item-deletes the second, third, and fourth entries in the list; remember that the first location is 0.
Deselects the item at the specified location. This is a synchronized method.
Returns the label of the list item at the specified location.
Returns the number of rows currently visible to the user.
Throws an ArrayIndexOutofBoundsException if it's invoked on a list where more than one item is selected. The method returns -1 if no items are selected. This is a synchronized method.
Returns an array of the locations of the selected items. This is a synchronized method. It works with a single selection and with single-selection lists. It returns -1 if no items are selected.
Returns the location of the currently selected item. This is a synchronized method. A runtime Exception is thrown if this method is called on a multiple-selection list. For that reason, and the fact that getSelectedItems will work with a single item, it's best to avoid this method. If no item is selected, it returns NULL.
Returns an array of Strings containing the names of the currently selected list items. This is a synchronized method. It returns an empty array if no items are selected. In that case, trying to access the 0th array item, as in
String [] picked = list.getSelectedItems()
String x = picked[0];
raises an ArrayIndexOutofBoundsException.
Forces the item at the specified location to be visible. This is a synchronized method. It comes in handy because not all choices are visible to the user if the number of visible rows is less than the number of list items. The specified location is moved to the top of the visible area.
Changes the name of the label specified by the value of location. This is a synchronized method.
Selects the specified item. This is a synchronized method.
Because the List class has such an extensive set of methods for manipulating the list's contents, it's a good choice for displaying information that is going to change often. It would be a good choice for showing recently visited pages-with event support to rapidly return to those pages-or the list of shopping items the user wants to purchase.
This class is abstract, but it's extended by both TextFields and TextAreas. All the methods covered here are available in both those GUI elements. TextComponent provides the basic tools for finding out what text is in a Text item (getText), setting the text in an item (setText), and selecting pieces of text (setSelect). When using TextFields or TextAreas, you won't have to worry about managing the cursor location, the insertion point (the vertical cursor that tells the user where newly typed text will be inserted), or the marking of the selected text. All these functions are done for you by the AWT. The most useful TextComponent methods follow.
Returns the text currently selected in the text item. The text may have been selected by the user or through the setSelection method.
Returns the index of the last character in the selection +1. Suppose that you pick a single character such as index 3; this method returns 4. If no characters are selected, this method returns the location of the insertion point. In that case, this method and getSelectionStart return the same value.
Returns the index of the first character in the current selection or the location of the insertion point if nothing is selected.
Returns all the text in the text item.
Selects the text specified by input arguments. As with getSelectionEnd, the end value should be the index of the last character you want to select +1. If start and stop are the same value, the insertion point is placed immediately before the character with that index.
Selects all the text in the text item.
Enables you to toggle between whether or not a text item is editable by the user.
Enables you to set the text in the text item. This replaces all the text in the item. If you want to insert or append text, you need to use getText, modify the string, and then use setText to put the modified string back in the text item. Note that TextArea has insert and append methods.
The applet in Listing 17.22 shows you how the various methods work. You'll see more about the unique characteristics of TextFields and TextAreas in the next two sections. Figure 17.29 shows the applet's interface. You can use this little applet to help you understand how the various input parameters work. By selecting text and then clicking the Selection Report button, for example, you can see what input parameters you need to use in the select method to get that same selection.
Figure 17.29 : Exploring the parameters of TextComponent methods.
Listing 17.22. TextFields and TextAreas in action.
import java.awt.*;
import java.applet.Applet;
public class text_stuff extends Applet
{
TextArea text_space;
TextArea selection;
Button show_selection, make_selection;
TextField start, stop;
public void init()
{
int i;
String sample_text;
//This string could have included \n to force returns
sample_text = "This is a very long piece of text which is" +
" designed to show how " +
"a text area can hold a lot of text without you having to do a lot of" +
"work." +
"In general text areas are good for holding large chunks of text or for" +
"getting " +
"long answers back from the user. TextAreas have lots of methods for " +
"manipulating the their contents and for controlling how the text is " +
"scrolled";
//define the TextArea
text_space = new TextArea(sample_text,8,50);
//define the report TextArea
selection = new TextArea("",10,50);
//create the button to show the selection values
show_selection = new Button("Selection Report");
//create the text field to input the start of the selection
start = new TextField(2);
//create the text field to input the end of the selection
stop = new TextField(2);
//make the labels for the two input fields
Label l_start = new Label("Start of selection");
Label l_stop = new Label("End of selection");
//define the button to make the selection
make_selection = new Button("Make selection");
//add everything to the applet's panel
add(text_space);
add(show_selection);
add(selection);
add(l_start);
add(start);
add(l_stop);
add(stop);
add(make_selection);
}
//handle mouse clicks on the two buttons
public boolean action (Event e, Object o) {
int start_location,stop_location;
String report, temp;
if (e.target instanceof Button) {
//check the button label to decide which button was clicked
if (((String)o).equals("Make selection")) {
//read the text from the text field to
//get the start of the selection
temp = start.getText();
//convert the text to an integer
start_location = Integer.parseInt(temp);
//get the text from the text field to
//define the end of the selection
temp = stop.getText();
//convert the text to an integer
stop_location = Integer.parseInt(temp);
//set the selection to the interval defined by
//the values in the text fields
text_space.select(start_location,
stop_location);
} else {
report = "Selection Start = ";
//get the start of the current selection
report = report + text_space.getSelectionStart() + "\n";
//get the end of the current selection
report = report + "Selection End = ";
report = report + text_space.getSelectionEnd() + "\n";
//get the selected text
report = report + "Selected Text is: ";
report = report + text_space.getSelectedText() + "\n";
//put the report in the text area
selection.setText(report);
}
}
return true;
}
}
Text fields are designed to be used to allow the user to input short pieces of text-usually no more than a few words or a single number. You also can use them to display information to the user, such as a phone number or the current sum of the costs of the items the user is going to order. Because TextField extends TextComponent, you can define whether the user can edit the contents of a TextField. The example in Listing 17.23 shows the various types of text fields you can create. Figures 17.30 and 17.31 show how changing the size of the window causes the text fields' sizes to change.
Figure 17.30 : Text fields with limited space.
Figure 17.31 : With FlowLayout, the size of TextFields change as the window size changes.
Listing 17.23. Creating TextFields.
import java.awt.*;
import java.applet.Applet;
public class text_field extends Applet
{
TextField t[];
Label l[];
int n_fields;
GridLayout gl;
public void init(){
int i;
//This defines a new layout manager with 4 rows and 2 columns
//You'll learn about layout managers in a few pages
gl = new GridLayout(4,2);
//tell the applet to use this new layout manager
//rather than the default one
setLayout(gl);
n_fields = 4;
t = new TextField[n_fields];
l = new Label[n_fields];
//set up the labels and text fields
t[0] = new TextField();
l[0] = new Label("TextField no input params");
t[1] = new TextField(35);
l[1] = new Label("35 character wide TextField");
t[2] = new TextField("Merry Christmas!");
l[2] = new Label("TextField with initial text");
t[3] = new TextField("Enter your Swiss bank account #",20);
l[3] = new Label("TextField with initial text and fixed width");
//add all the components to the applet panel
for(i=0;i<n_fields;i++){
add(l[i]);
add(t[i]);
}
}
}
Text areas are designed to hold large chunks of text, where large is more than one line. TextArea extends TextComponent by adding a number of additional methods as well as automatic scrolling of the text. Listing 17.24 shows examples of the different TextArea creator methods displayed in Figure 17.32.
Figure 17.32 : Different ways to create text areas.
Listing 17.24. TextArea creator methods.
import java.awt.*;
import java.applet.Applet;
public class text_areas extends Applet
{
TextArea text_space[];
Label labels[];
int n_fields;
public void init()
{
int i;
String sample_text;
n_fields = 4;
labels = new Label[n_fields];
text_space = new TextArea[n_fields];
//This string could have included \n to force returns
sample_text = "This is a very long piece of text which is" +
" designed to show how " +
"a text area can hold a lot of text without you having to do a lot of" +
"work." +
"In general text areas are good for holding large chunks of text or for" +
"getting " +
"long answers back from the user. TextAreas have lots of methods for " +
"manipulating the their contents and for controlling how the text is " +
"scrolled";
//define the TextAreas and their labels.
labels[0] = new Label("default text area");
text_space[0] = new TextArea();
labels[1] = new Label("5 by 30 text area");
text_space[1] = new TextArea(5,30);
labels[2] = new Label("Filled with a sample string");
text_space[2] = new TextArea(sample_text);
labels[3] = new Label("8 by 50 text area with a sample string");
text_space[3] = new TextArea(sample_text,8,50);
//add everything to the applet's panel
for (i=0;i<n_fields;i++){
add(labels[i]);
add(text_space[i]);
}
}
}
TextArea extends TextComponent in several ways, but one of the most useful ones is its support of more powerful text-manipulation methods, such as insert and append. The most useful TextArea methods follow.
Defines a default empty TextArea.
Defines an empty TextArea with the specified number of rows and columns.
Defines a TextArea that contains the specified string.
Defines a TextArea containing the specified string and with a set number of rows and columns.
Appends the specified string to the current contents of the TextArea.
Returns the current width of the TextArea in columns.
Returns the current number of rows in a TextArea.
Inserts the specified string at the specified location.
Takes the text between start and stop, inclusive, and replaces it with the specified string.
A canvas is an empty space-a starting point for complex interface elements such as a picture button. A canvas is a place to draw. You use it instead of just drawing to a panel, as in an applet, so you can take advantage of the Layout Manager's capability to keep your interface machine- independent. The applet in Listing 17.25 draws to the screen and to a canvas, as shown in Figure 17.33.
Figure 17.33 : Drawing on a canvas without being an artist.
Listing 17.25. Drawing to the screen and to a canvas.
import java.awt.*;
import java.applet.Applet;
public class canvas_example extends Applet
{
drawing_area drawing;
Graphics drawing_graphics;
public void init()
{
//Don't need to do this but we don't want the Canvas
//being resized by the
//Layout Manager. Layout Managers are discussed later in this chapter.
setLayout(null);
drawing = new drawing_area();
add(drawing);
}
public void paint(Graphics g)
{
//This is just here to show that you can combine direct drawing to
//the applet panel with Canvases
g.drawString("Hello, World!", 25, 125 );
}
}
//Can't have more than one public class in a source file but
//you can have several
//non-public ones
class drawing_area extends Canvas {
Font my_font;
drawing_area() {
//set up a font for this Canvas to use
my_font = new Font("TimesRoman", Font.BOLD,16);
//set the size of the Canvas
resize(100,100);
}
public void paint(Graphics g) {
//By overriding the paint method you can control what is
//drawn in a canvas. If you want to avoid flicker you might
//also want to override the update method as discussed in the
//animation section.
//Fill a rectangle with the default color, black
g.fillRect(0,0,100,100);
//set the foreground color to white so we can read the text
g.setColor(Color.white);
g.setFont(my_font);
g.drawString("this is a test", 10,50);
//Be careful and reset the color or the next time paint is called the
//rectangle will be filled with white and the text will be invisible
g.setColor(Color.black);
}
}
The traditional method for building a GUI has been to position various interface elements, such as buttons, at specific locations inside a window and then to allow the user to move the windows around. Java has had to explore new approaches to defining the layout of components because of the diversity of standards that it has to support. Although the AWT does let you specify the absolute location of components, it also gives you Layout Managers that let you define the relative placement of components that will look the same on a wide spectrum of display devices.
Although you can build your own Layout Manager, it's easiest to use one of the Managers that come with the AWT. In addition, freeware Layout Managers currently are available; these are discussed along with how to build your own Layout Manager, and more will be arriving in the future.
Although most containers come with a preset FlowLayout Manager, you can tell the AWT to use absolute positions with the following line of code:
setLayout(null);
This eliminates the default Layout Manager, or any other one that the container had been using, enabling you to position components by using absolute coordinates. Keep in mind, though, that absolute coordinates won't look the same on all platforms. Listing 17.26 shows a little applet that uses absolute coordinates and the resize and reshape methods from the Component
class to position and size a label and a text field. Figure 17.34 shows the resulting applet. If you don't use resize or reshape, you won't see the components. This approach to layouts can run into problems when the size of a font varies between platforms. You can use the FontMetrics class to figure out the size of a font relative to some standard size and then scale your absolute coordinates, but it's probably easier to just implement a custom Layout Manager.
Figure 17.34 : A simple applet with absolute positioning.
Listing 17.26. Positioning and sizing a label and text field.
import java.awt.*;
import java.applet.Applet;
public class no_layout extends Applet
{
public void init()
{
Label a_label;
TextField a_textfield;
setLayout(null);
a_label = new Label("A label");
add(a_label);
//change the size of the Label
a_label.resize(40,50);
a_textfield = new TextField(20);
add(a_textfield);
//set the size and position of the TextField
a_textfield.reshape(20,40,140,10);
}
}
This is the default Layout Manager that every panel uses unless
you use the setLayout method
to change it. It keeps adding components to the right of the preceding
one until it runs out of space; then it starts with the next row.
The code in Listing 17.27 shows how to place 30 buttons in an
applet using FlowLayout.
Note |
The creation of a new layout and the use of the setLayout method are superfluous in this case because the panel comes with a FlowLayout as a default. They're included here to show you how you create and define the Layout Manager for a panel. |
Listing 17.27. Placing 30 buttons in an applet using FlowLayout.
import java.awt.*;
import java.applet.Applet;
public class flowlayout extends Applet{
Button the_buttons[];
public void init()
{ int i;
int n_buttons;
FlowLayout fl;
String name;
//make a new FlowLayout manager
fl = new FlowLayout();
//tell the applet to use the FlowLayout Manager
setLayout(fl);
n_buttons = 30;
the_buttons = new Button[n_buttons];
//make all the buttons and add them to the applet
for(i=0;i<n_buttons;i++) {
name = "Button " + i;
the_buttons[i] = new Button(name);
add(the_buttons[i]);
}
}
}
This sample applet generates the interfaces shown in Figures 17.35 and 17.36. You might be wondering how this is possible. The answer is that the FlowLayout Manager follows its rule; it places elements in a row until there isn't room for more and then goes to the next, regardless of the size of the container.
Figure 17.35 : FlowLayout of 30 buttons with one window size.
Figure 17.36 : Same applets, same Layout Manager, same buttons, different window size.
GridLayout's simple rule is to allow the user to define the number of rows and columns in the layout. GridLayout then sticks one item in each grid cell. The cells are all the same size. The size of the cells is determined by the number of cells and the size of the container. Figures 17.37 and 17.38 show how GridLayout positions the items for two window sizes in the applet shown in Listing 17.28.
Figure 17.37 : GridLayout with a small window.
Figure 17.38 : The same applet with a larger window.
Listing 17.28. Using the GridLayout Manager.
import java.awt.*;
import java.applet.Applet;
public class gridlayout extends Applet{
Button the_buttons[];
public void init()
{ int i;
int n_buttons;
GridLayout gl;
String name;
//set up a grid that is 3 rows and 10 columns. There is a 10 pixel space
//between rows and 15 pixels between columns
gl = new GridLayout(3,10,10,15);
setLayout(gl);
n_buttons = 30;
the_buttons = new Button[n_buttons];
for(i=0;i<n_buttons;i++) {
name = "Button " + i;
the_buttons[i] = new Button(name);
add(the_buttons[i]);
}
}
}
As you see in this listing, the GridLayout creator method enables you to define gaps between components.
There are two GridLayout creator methods:
Makes a GridLayout with the specified number of rows and columns.
Makes a GridLayout with the specified rows and columns and with the specified empty space around each component.
This is the most powerful, complex, and hard-to-use Layout Manager that comes with the AWT. Although it gives you the most flexibility, you should plan to spend some time experimenting with its parameters before you get a layout that you like. The basic principle of GridBagLayout is that you associate a constraint object, an instance of GridBagConstraints, with each component in the layout. The GridBagLayout Manager uses those constraints to determine how to lay out the components on an invisible grid, where each component can occupy one or more grid cells. The creator methods for GridBagConstraints take no input parameters; you customize the instance by changing the following instance variables.
Specifies how a component is to be aligned if a component is smaller than the allocated space. The available constants follow:
CENTER: Puts the component in the middle of the area.
EAST: Aligns it with the right-middle side.
NORTH: Aligns it with the top-middle.
NORTHEAST: Puts it in the upper-right corner.
NORTHWEST: Puts it in the upper-left corner.
SOUTH: Aligns it with the bottom-middle.
SOUTHEAST: Puts it in the lower-right corner.
SOUTHWEST: Puts it in the lower-left corner.
WEST: Aligns it with the left-middle side.
Determines what happens if the space allotted to a component is larger than its default size. The allowable values follow:BOTH: Tells the component to fill the space in both directions.
HORIZONTAL: Tells the component to fill the space in the horizontal direction.
NONE: Leaves the component at its default size.
VERTICAL: Tells the component to fill the space in the vertical direction.
Specifies the height of the component in grid cells. The constant REMAINDER specifies that the component is the last one in the column and therefore should get all the remaining cells.
Specifies the width of the component in grid cells. The constant REMAINDER specifies that the component is the last one in the row and therefore should get all the cells remaining in the row.
Specifies the grid position of the left side of a component in the horizontal direction. The constant RELATIVE specifies the position to the right of the previous component.
Specifies the grid position of the top of a component in the vertical direction. The constant RELATIVE specifies the position below the previous component.
Enables you to set an instance of the Insets class that specifies the whitespace reserved around an object. It provides more flexibility than ipadx and ipady because it allows different whitespace on the left than on the right and different whitespace on the top than on the bottom of the component.
Specifies the amount of padding (empty space) to put on either side of a component. This increases the effective size of the component.
Specifies the amount of padding to put above and below the component.
Specifies how extra horizontal space (space not needed for the default component sizes) is allocated between components. This is a relative value, normally chosen to be between 0 and 1, and the values of the components are compared when allocating space. If one component has a weight of .7 and another has a weight of .2, for example, the one with weight .7 gets more of the extra space than the one with .2.
Same as weightx but for the vertical direction.
As you play around with this Layout Manager, you'll discover that it's very powerful but not entirely intuitive. Listing 17.29 shows an example that gives you a feeling for what GridBagLayout can do.
Listing 17.29. Using GridBagLayout.
import java.awt.*;
import java.applet.Applet;
public class gridbaglayout extends Applet{
Button the_buttons[];
public void init()
{ int i;
int n_buttons;
GridBagLayout gbl;
GridBagConstraints gbc;
String name;
int j;
//define a new GridBagLayout
gbl = new GridBagLayout();
//define a new GridBagConstraints. this will be used
//for all the components
gbc = new GridBagConstraints();
//if a component gets more space than it needs don't grow
//the component to fit the
//available space
gbc.fill = GridBagConstraints.NONE;
//if a component doesn't fill the space assigned to it
//put it in the top middle of the area
gbc.anchor = GridBagConstraints.NORTH;
//pad the size of the component
gbc.ipadx = 5;
gbc.ipady = 5;
//if there is more width available than needed for
//the components give this
//component a weight of .3 when allocating the extra horizontal space
gbc.weightx = .3;
gbc.weighty = .1;
setLayout(gbl);
n_buttons = 15;
the_buttons = new Button[n_buttons];
j = 0;
for(i=0;i<9;i++) {
j++;
//start a new row after every 3 buttons
if (j == 3) {
j = 0;
//make this component the last one in the row
gbc.gridwidth = GridBagConstraints.REMAINDER;
}
name = "Button " + i;
the_buttons[i] = new Button(name);
//tell GridBagLayout which constraint object to use. you can use
//the same constraint object for many components even if you
//change the instance variables values in the constraints object
//for different components
gbl.setConstraints(the_buttons[i],gbc);
add(the_buttons[i]);
//this sets the gridwidth to its default value. it cleans up the
//REMAINDER value assigned to the last button in a row
gbc.gridwidth = 1;
}
//change the weight for allocating extra space to subsequent
gbc.weightx = .4;
gbc.weighty = .2;
name = "Button 9";
the_buttons[9] = new Button(name);
//if there's extra space put the component in the upper right corner
gbc.anchor = GridBagConstraints.NORTHEAST;
//make it the last component in the row
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(the_buttons[9],gbc);
add(the_buttons[9]);
name = "Button 10";
the_buttons[10] = new Button(name);
//if the component has extra space assigned grow the component to
//fill it in both the x and y direction
gbc.fill = GridBagConstraints.BOTH;
//this line is unnecessary because the value of gridwidth is retained
//from when it was set for the previous button
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(the_buttons[10],gbc);
add(the_buttons[10]);
name = "Button 11";
the_buttons[11] = new Button(name);
//if there's extra space align the component with the right hand side
gbc.anchor = GridBagConstraints.EAST;
//change the weights for allocating extra space
gbc.weightx = .5;
gbc.weighty = 0;
//don't grow the component in either direction if there's extra space
gbc.fill = GridBagConstraints.NONE;
gbl.setConstraints(the_buttons[11],gbc);
add(the_buttons[11]);
name = "Button 12";
the_buttons[12] = new Button(name);
//pad the component--on the Mac the component grows
gbc.ipadx = 20;
//set the allocation of width to the default.
//note that it had been set to
//REMAINDER above
gbc.gridwidth = 1;
//put this component to the right and below the previous one
gbc.gridx = GridBagConstraints.RELATIVE;
gbc.gridy = GridBagConstraints.RELATIVE;
gbl.setConstraints(the_buttons[12],gbc);
add(the_buttons[12]);
name = "Button 13";
the_buttons[13] = new Button(name);
//set the pad space to 0
gbc.ipadx = 0;
//align the component with the left hand side of the available space
gbc.anchor = GridBagConstraints.WEST;
gbl.setConstraints(the_buttons[13],gbc);
add(the_buttons[13]);
}
}
This applet has the interface shown in Figures 17.39 and 17.40. The two figures show how GridBagLayout behaves as the window resizes.
Figure 17.39 : The large window layout.
Figure 17.40 : The small window layout.
The BorderLayout divides the container into five pieces; four form the four borders of the container and the fifth is the center. You can add one component to each of these five areas. Because the component can be a panel, you can add more than one interface element, such as a button, to each of the five areas. BorderLayout makes room for the items in the four border areas (referred to as North, South, East, and West), and then whatever is left over is assigned to the Center area. This layout is nice if you want to place scrollbars around a panel, place the scrollbars in the border regions, use all four scrollbars or just two, and place the panel you want to scroll in the center.
Listing 17.30 shows an example of the BorderLayout in action, which generates the screen shown in Figure 17.41.
Figure 17.41 : The BorderLayout.
Listing 17.30. Using BorderLayout.
import java.awt.*;
import java.applet.Applet;
public class borderlayout extends Applet{
Button the_buttons[];
public void init()
{ int i;
int n_buttons;
BorderLayout bl;
String name;
bl = new BorderLayout(10,15);
setLayout(bl);
n_buttons = 5;
the_buttons = new Button[n_buttons];
//add the buttons to the various "geographic" regions
the_buttons[0] = new Button("North Button");
add("North",the_buttons[0]);
the_buttons[1] = new Button("South Button");
add("South",the_buttons[1]);
the_buttons[2] = new Button("East Button");
add("East",the_buttons[2]);
the_buttons[3] = new Button("West Button");
add("West",the_buttons[3]);
the_buttons[4] = new Button("In the Middle");
add("Center",the_buttons[4]);
}
}
The CardLayout is different
from the others because it enables you to create virtual screen
real estate by defining multiple Cards,
one of which is visible at any time. Each Card
contains a panel that can contain any number of interface elements,
including other panels. If you've ever used HyperCard on the Mac,
you'll be familiar with this Rolodex
type of interface. It's also similar to the tabbed dialog boxes
that are the rage in Microsoft products, but Cards
lack any built-in way to go from Card
to Card; you have to provide
an interface for that.
Note |
Commercial widgets that implement tabbed dialog boxes are available; see the section "Extending the AWT," later in this chapter. |
The example in Listing 17.31 generates a group of five cards. Each card has a button that takes you to the next card. You can tell which card you're on by looking at the button name.
Listing 17.31. Generating a group of five cards.
import java.awt.*;
import java.applet.Applet;
public class cardlayout extends Applet{
Button the_buttons[];
Panel the_panels[];
CardLayout cl;
public void init()
{ int i;
int n_buttons;
String name;
cl = new CardLayout();
setLayout(cl);
n_buttons = 5;
the_panels = new Panel[n_buttons];
the_buttons = new Button[n_buttons];
//this loop creates the 5 panels and adds a button to each
for(i=0;i<n_buttons;i++) {
the_panels[i] = new Panel();
name = "Button " + i;
the_buttons[i] = new Button(name);
the_panels[i].add(the_buttons[i]);
name = "Card " + i;
//give the panel a name to be used to access it
add(name, the_panels[i]);
}
cl.show(this,"Card 2");
}
public boolean action (Event e, Object o) {
Dimension d_should;
//when the button is clicked this takes you to the next card,
//it will cycle around so that when you're
//at card 4 this will take you to card 0
if (e.target instanceof Button) {
cl.next(this);
}
return true;
}
}
Figure 17.42 shows the second card in the stack. Clearly, you could make a tabbed dialog box interface by using a row of buttons across the top of all the cards (one for each card in the group) or with a canvas at the top with a set of tabs drawn on it.
Figure 17.42 : Card two of a CardLayout Manager interface.
You can develop your own Layout Managers by creating a new class that implements LayoutManager and overrides the following five methods.
Adds a component to the layout. If your layout has multiple named areas (such as North in BorderLayout), the name specifies which region the component should go to. If your layout doesn't keep any special information about the component, you can make this method do nothing.
Removes a component from the layout. If you don't keep any special information about a component, you can make this method do nothing.
Computes the preferred size for the container that holds the layout.
Returns the minimum size needed for the layout.
Lays out the components using the reshape method. This is where you put the logic for deciding how to position components.
Although implementing a custom Layout Manager isn't earth-shatteringly complex, the good news is that there are lots of people in the Java community-and some already are making custom Layout Managers. You can get a PackerLayout, for example, which is very similar to the layout approach used in tcl/tk-another mainly UNIX programming language. Before spending the time building your own Layout Manager, do a Web search to make sure that you can't save the time by using someone else's work.
Here are some sample Layout Managers that you can get on the Web. I haven't tried them, so don't view this as a recommendation; instead, it merely should be a starting point in your quest to avoid writing any more code than necessary.
Fractional Layout Manager
http://www.mcs.net/~elunt/Java/FractionalLayoutDescription.html
Layout Manager Launch
(a collection of links to various Layout Managers)
http://www.softbear.com/people/larry/javalm.htm
Packer Layout Manager
http://www.geom.umn.edu/~daeron/apps/ui/pack/gui.html
Relative Layout Manager
http://www-elec.enst.fr/java/RelativeLayout.java
Rule-Based Layout Manager
http://www.sealevelsoftware.com/sealevel/testrule.htm
Tree Layout Manager
http://www.sbktech.org/tree.html
Note |
One problem with custom Layout Mangers is that they have to be downloaded over the network, while the AWT Layout Managers already reside on the local machine. Because a Layout Manager can be large (more than 10 KB), this is an issue if a lot of your users will be working with 14.4-Kbps or 28.8-Kbps modems. If your users have T1 lines, it's not an issue. |
The developers of Java knew that working with images is a critical part of any modern programming language with a goal of implementing user interfaces that meet the criteria of users. Because Java is platform independent, though, it couldn't use any of the platform-specific formats, such as the Mac's PICT standard. Fortunately, there already are two platform-independent formats: GIF and JPEG. These formats are especially nice because they are compressed so that transmitting them takes less of the limited network bandwidth. The AWT supports both these compression formats, but it uses neither of them internally. Although you can read in GIF and JPEG files, they are converted into images, which are just bitmaps. All the work you do in Java with images is based on the Image class. For some strange reason, even though there is a special package for image-manipulation-related classes (java.awt.image), the Image class itself resides in the top-level java.awt package.
Image is an abstract class designed to support bitmapped images in a platform-independent manner. Although this class provides just the basics for working with images, it does have several methods you'll find useful.
Java uses a model of image producers and image consumers. Image producers generate pixels from a file or Image object, and image consumers use and/or display those pixels. Both ImageConsumer and ImageProducer are Java interfaces. AWT comes with ImageProducers for reading from local files and URLs, arrays in memory, and Image objects. It also comes with CropImageFilter, RGBImageFilter, and PixelGrabber, which implement the ImageConsumer interface.
Returns the graphics object for this image. You can use this to draw to the image using the drawing methods discussed in the Graphics object section, "Graphics Class."
Returns the height of the image. The ImageObserver class monitors the construction of the image-as it's loaded over the Internet, for example. The ImageObserver is notified when the image is fully loaded into memory if it isn't at the time this method is called.
Returns the value of one of the image properties. The specific properties depend on the original image format. Three properties are supported by Java:
comments: A string full of information defined by the person who made the image file.
croprect: If a Java croprect filter is used, this holds the boundary of the original image.
filters: If Java image filters are used to make the image, they are listed here.
Returns an instance of ImageProducer, which serves as a source for the pixels in an image.
Returns the width of the image.
Java makes it easy to load images from files located on the server. Security restrictions vary between browsers, but even Netscape, which tends to have the tightest security, allows you to get images from files in the same directory from which the Java classes are loaded. The simple applet shown in Listing 17.32 loads in an image, monitors the loading with an ImageObserver, and then displays the image (see Figure 17.43).
Figure 17.43 : Displaying a GIF file.
Listing 17.32. Showing a GIF image.
import java.awt.*;
import java.applet.Applet;
import java.awt.image.*;
public class imagetest extends Applet implements ImageObserver, Runnable{
Image the_picture;
boolean image_ready;
Thread the_thread;
public void init()
{
//we'll use this flag to see when the image is ready to use
image_ready = false;
//get the image from the same location as the
//HTML document this applet is on
the_picture = getImage(getDocumentBase(),"test.gif");
}
//overriding this method allows you to monitor the
//status of the loading of the image
public boolean imageUpdate(Image img,int status, int x,
int y, int width, int height){
if ((status & ALLBITS) == 0) {
//Monitor the load status
System.out.println("Processing image " + x + " " +
y + " width, height " + width + " " + height);
return true;
} else {
System.out.println("Image fully read in");
return false;
}
}
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
//Give the thread that's loading the image more cycles by minimizing the
//priority of the applet's animation thread
the_thread.setPriority(Thread.MIN_PRIORITY);
//cycle while you're waiting for the image to load. You could put in a
//message telling the user what's going on as well
while (!image_ready) {
repaint();
try {
the_thread.sleep(50);
} catch(InterruptedException e) {};
}
}
public void paint(Graphics g)
{
//Draw the image. The "this" assigns the applet
//as the image observer for
//this drawing.
image_ready = g.drawImage(the_picture,0,0,this);
}
}
Notice that the applet itself implements the ImageObserver interface so that when an ImageObserver is called for, as in the drawImage method call, this is used. The only other change you have to make to your applet is to override the imageUpdate method with one of your own to track the image-loading process. Although the behavior of this seems to vary a bit between platforms (probably due to differences in the way thread priorities are handled), you can use it to see when an image is ready to be displayed. But there is a better way, using the MediaTracker class, which you'll see in the next section.
If you're using Java on the Internet, you'll find that you'll want to crop images more often than you might expect. The reason for this is that there is a fairly significant overhead associated with sending a file from the server to the client. As a result, you can make your applet load faster if you send one file that is 2x pixels on a side instead of four files that are 1x pixels on a side. If you go with one large file, you'll find that the CropImageFilter class will make it easy to cut out pieces of the image. This is a good way to work with multi-frame animations. Put all the animations in a single file and then use CropImageFilter to make a separate image for each cell in the animation.
You won't use any of CropImageFilter's methods other than its creator:
new CropImageFilter(int x, int y, int width, int height)
The input parameters define the rectangular region that will be selected. The coordinates are with respect to the upper-left corner of the image, which is set to 0,0.
The applet in Listing 17.33 reads in a GIF file, creates a new image that is the upper-left corner of the original image, and then displays both images, as shown in Figure 17.44.
Figure 17.44 : The original and the cropped image.
Listing 17.33. Cropping an image.
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
public class crop_image extends Applet implements Runnable{
Image the_picture, cropped_picture;
boolean image_ready;
Thread the_thread;
MediaTracker the_tracker;
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
the_thread.setPriority(Thread.MIN_PRIORITY);
while (true) {
repaint();
try {
the_thread.sleep(3000);
} catch(InterruptedException e) {};
}
}
public void init()
{
CropImageFilter c_i_filter;
//Define a MediaTracker to monitor the completion status of an image
//You need to supply a component as the argument.
//In this case the applet
// is used
the_tracker = new MediaTracker(this);
image_ready = false;
the_picture = getImage(getDocumentBase(),"test.gif");
//tell MediaTracker to monitor the progress of the image and
//assign the image
//the index of 0--the index value is arbitrary
the_tracker.addImage(the_picture,0);
//Define an image cropping filter
c_i_filter = new CropImageFilter(0,0,32,32);
//Apply the filter to the image and make a new image of the cropped area
cropped_picture = createImage(new FilteredImageSource(
the_picture.getSource(),c_i_filter));
//Monitor the cropped image to see when it's done
the_tracker.addImage(cropped_picture,1);
}
public void paint(Graphics g)
{
//Wait till both images are ready
if(the_tracker.checkID(0,true) &
the_tracker.checkID(1,true)) {
image_ready = true;
g.drawImage(the_picture,0,0,this);
g.drawImage(cropped_picture,70,70,this);
} else {
System.out.println("Waiting for image to fully load");
}
}
}
The applet shown in Figure 17.44 also demonstrates how to use the MediaTracker class to monitor images that are being loaded or built. All you have to do is create a new instance of the MediaTracker class.
To create a MediaTracker instance to monitor the loading of images associated with the specified component, use this code:
new MediaTracker(Component a_component)
To track images, you'll use these MediaTracker methods:
Tells MediaTracker to monitor the specified image. The ID is used in other method calls to identify the image. You can use any value for the ID, and you can assign the same ID to more than one image.
Functions like the preceding method, except that it registers a scaled image where width and height are the new dimensions of the image. This is a synchronized method.
Returns TRUE when the images have loaded or stopped loading due to an error. Use the isErrorAny and/or isErrorID methods to check for problems.
Returns TRUE if all images have finished loading or encountered an error. This is a synchronized method. It forces the images to load if the input parameter is TRUE.
Returns TRUE if the specified image (or group of images, if more than one image has the same ID) has finished loading or stopped due to an error.
Returns TRUE if the images with the specified ID are loaded or have encountered an error. If load_flag is TRUE, this synchronized method starts to load the image if it isn't already loading.
Returns an array of media objects for those images that encountered errors. This is a synchronized method. You can print these objects by using the .toString method on them.
Returns an array of media objects for the images with the specified ID that encountered an error while loading. This is a synchronized method.
Returns TRUE if any image currently being tracked has a problem. This is a synchronized method.
Returns TRUE if any image with the specified ID has received an error while loading. This is a synchronized method.
Returns the status of the loading of all the images. ANDing the returned value with MediaTracker.ERRORED returns TRUE if there is an error. If the input parameter is TRUE, this method starts loading the images if they haven't already been loaded.
Functions similarly to statusAll, except for a single ID.
Waits until all images are loaded or encounter an error. If this method is interrupted, it throws the InterruptedException error.
Functions similarly to waitForAll, except that it will timeout after the specified time. This is a synchronized method.
Functions similarly to waitForAll, except for a single ID.
Functions similarly to waitForAll, except for a single ID. This is a synchronized method.
As you can see from the applet in Listing 17.33, though, you only need to use a couple of methods to monitor image loading in most circumstances.
Note |
Java will not be able to garbage-collect images that have been monitored by MediaTracker, because MediaTracker retains references to them unless the MediaTracker instance itself can be garbage-collected. If you load and then delete images, make sure to set all references to the MediaTracker you used to NULL if you want Java to free up the space used by the images. |
Image filters provide an easy way to transform images. The RGBImageFilter makes it easy to execute transformations that only require information about the pixel being modified. Although that excludes a wide range of common transformations, it does support a large number, such as converting to grayscale, image fade out, and color separation. The applet in Listing 17.34 takes an image, inverts all the colors, and then displays the before and after pictures (see Figure 17.45).
Figure 17.45 : Inverting colors with RGBImageFilter.
Listing 17.34. Using an RGBImageFilter.
import java.applet.Applet;
import java.awt.*;
import java.awt.image.*;
public class rgbimagefilter extends Applet implements Runnable{
Image the_picture, filtered_image;
boolean image_ready;
Thread the_thread;
MediaTracker the_tracker;
//Sep is the new class that extends RGBImageFilter
Sep image_filter;
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
the_thread.setPriority(Thread.MIN_PRIORITY);
while (true) {
repaint();
try {
the_thread.sleep(3000);
} catch(InterruptedException e) {};
}
}
public void init()
{
the_tracker = new MediaTracker(this);
image_ready = false;
the_picture = getImage(getDocumentBase(),"test.gif");
the_tracker.addImage(the_picture,0);
//modify the image and track the process
filter_image();
the_tracker.addImage(filtered_image,1);
}
public void filter_image() {
image_filter = new Sep();
//create the new filtered image using the getSource method on the input
//picture as a pixel producer
filtered_image = createImage(new FilteredImageSource
(the_picture.getSource(),image_filter));
}
public void paint(Graphics g)
{
//wait for both images to be loaded before drawing them
if(the_tracker.checkID(0,true) &
the_tracker.checkID(1,true)) {
image_ready = true;
g.drawImage(the_picture,0,0,this);
g.drawImage(filtered_image,60,60,this);
} else {
System.out.println("Waiting for image to fully load");
}
}
}
class Sep extends RGBImageFilter{
public Sep() {
//This specifies that this class can work with IndexColorModel where
//each pixel contains a pointer to a color in a lookup table rather than
//a 32 bit color
canFilterIndexColorModel = true;
}
public int filterRGB(int x, int y, int rgb) {
int blue,red,green,new_color;
//get the three color components from the rgb int
blue = rgb & 0x000000FF;
green = (rgb & 0x0000FF00) >> 8;
red = (rgb & 0x00FF0000) >> 16;
//These three lines invert the color
blue = 255 - blue;
green = 255 - green;
red = 255 - red;
//create the new color and then get the int version of the color
new_color = (new Color(red,blue,green)).getRGB();
return new_color;
}
}
All the work is done in the filterRGB method, which is called for each pixel or every color in the lookup table for indexed color models. You can put any algorithm in that method that only requires information about that one color and/or pixel. If your transformation is position dependent (it's a function of x and/or y), you can't work with an indexed color model because the filterRGB method, in this case, is called only for the colors in the lookup table-not for every pixel.
The AWT provides a class, MemoryImageSource, that makes it easy to convert an array of data into an image. This is useful if you're building images on-the-fly-drawing the Mandelbrot set, for example. In Listing 17.35, the image is a gradient fill that's built as an array of integers and then converted to an image (see Figure 17.46).
Figure 17.46 : A gradient fill generated as an array of integers.
Listing 17.35. Working with pixel values.
import java.awt.image.*;
import java.awt.*;
import java.applet.Applet;
public class memory_image extends Applet implements Runnable
{
Image working;
Thread the_thread;
MediaTracker the_tracker;
int pixels[];
int max_x, max_y;
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
while (true) {
repaint();
try {
the_thread.sleep(3000);
} catch(InterruptedException e) {};
}
}
public void init()
{
max_x = 256;
max_y = 256;
the_tracker = new MediaTracker(this);
//set up the pixel array that we're going to convert to an image
pixels = new int[max_x * max_y];
//set up the new image
init_pixels();
}
public void init_pixels() {
int x,y,i, j;
double step;
//this method does a gradient fill starting with black at the top
//and going to blue at the bottom
//step is used to determine how much the color changes between rows
step = 255.0/max_y;
i = 0;
Color c = new Color(0,0,0);
int ci = c.getRGB();
j= 0;
for (y=0;y<max_y;y++) {
//get a new color for each row. notice that the fastest cycling
// in the array is x. variable
c = new Color(0,0,Math.round((float)(j*step)));
ci = c.getRGB();
j++;
for (x=0;x<max_x;x++) {
pixels[i++]= ci;
}
}
//make the image and use MediaTracker to see when it's
//done being processed
working = createImage(new MemoryImageSource
(max_x,max_y,pixels,0,max_y));
the_tracker.addImage(working,0);
}
public void paint(Graphics g)
{
//if the image is done display it, otherwise let the
//user know what's going on
if(the_tracker.checkID(0,true)) {
g.drawImage(working,0,0,this);
} else {
g.drawString("please wait",50,50);
}
}
}
After you have an image from the int array, you can use all the other image-processing tools with them.
If you're going to process images, you often want to work with the pixel data as an array. The AWT provides a class, java.awt.image.PixelGrabber, to do just that. This class has two creator methods, plus some other useful methods.
Takes the region of the image defined by x, y, height, and width and puts it into the pixel's array with the specified offset and line width.
Takes the region of the image produced by the image producer defined by x, y, height, and width and puts it into the pixel's array with the specified offset and line width.
Throws InterruptedException if it's interrupted. It takes the image data and puts it into the pixel array. This is a synchronized method.
Returns the status of getting the pixels from an image. The return value uses the same flag constants as the ImageObserver class. This is a synchronized method.
The applet in Listing 17.36 reads in a GIF file, fills in an array with the pixel values, applies an averaging filter to the data (which uses information about each pixel's neighbors), and then converts the pixel array back to an image. This results in the screen shown in Figure 17.47.
Figure 17.47 : An average filter applied to a grayscale image.
Listing 17.36. A filter that uses data from a pixel's local area.
import java.awt.image.*;
import java.awt.*;
import java.applet.Applet;
public class pixel_grabber extends Applet implements Runnable
{
Image working;
Thread the_thread;
MediaTracker the_tracker;
int pixels[];
int max_x, max_y;
Image the_picture;
Image the_modified_picture;
boolean done_flag;
public void start() {
if (the_thread == null) {
the_thread = new Thread(this);
the_thread.start();
}
}
public void stop() {
the_thread.stop();
}
public void run() {
while (true) {
repaint();
try {
the_thread.sleep(3000);
} catch(InterruptedException e) {};
//when the original image is loaded transform it.
//The use of the flag
//makes sure you only transform the image once
if(the_tracker.checkID(0,true) & !(done_flag)) {
transform_image();
done_flag = true;
}
}
}
public void init()
{
done_flag = false;
max_x = 64;
max_y = 64;
the_tracker = new MediaTracker(this);
//set up the pixel array that we're going to
//fill with data from the image
pixels = new int[max_x * max_y];
the_picture = getImage(getDocumentBase(),"test.gif");
the_tracker.addImage(the_picture,0);
}
public void transform_image() {
PixelGrabber pg;
int x,y;
double sum, temp;
//set up the PixelGrabber to capture the full image, unscaled
pg = new PixelGrabber(the_picture, 0,0,max_x,max_y,pixels,0,max_y);
//fill the pixel's array with the image data
try {
pg.grabPixels();
} catch(InterruptedException e) {};
//just to show something happened apply an averaging filter.
//This is designed to
//work with grayscale images because it assumes all of the
//color components are
//equal. It takes the Blue component of a pixel and its
//four neighbors, averages them,
//and then sets the pixel color to that average
for (x=3;x<(max_x - 3);x++) {
for (y=3;y<(max_y - 3);y++) {
temp = pixels[x + y * max_y] & 0x000000FF;
sum = 0.2 * temp;
temp = pixels[(x - 1) + y * max_y] & 0x000000FF;
sum = sum + 0.2 * temp;
temp = pixels[(x+1) + y * max_y] & 0x000000FF;
sum = sum + 0.2 * temp;
temp = pixels[x + (y - 1) * max_y] & 0x000000FF;
sum = sum + 0.2 * temp;
temp = pixels[x + (y + 1) * max_y] & 0x000000FF;
sum = sum + 0.2 * temp;
int gray = (int)Math.round(sum);
Color c = new Color(gray,gray,gray);
pixels[x + y * max_y] = c.getRGB() ;
}
}
//here we use MemoryImageSource to convert the pixel array into an Image
the_modified_picture = createImage(new
MemoryImageSource(max_x,max_y,pixels,0,max_y));
the_tracker.addImage(the_modified_picture,1);
}
public void paint(Graphics g)
{
//if the image is done display it, otherwise let
//the user know what's going on
if(the_tracker.checkID(0,true) ) {
g.drawImage(the_picture,0,0,this);
if( the_tracker.checkID(1,true)) {
g.drawImage(the_modified_picture, 64,64,this);
}
} else {
g.drawString("please wait",50,50);
}
}
}
The Window class implements a window with no borders and no menu bar. This generally isn't a useful class on its own, but because Frame and Dialog-which are useful-extend it, it's useful to take a quick look at Window's methods.
This gets rid of the window's peer. When the window is destroyed, you need to call this method. This is a synchronized method.
Returns the Toolkit associated with the window.
Displays the window, making it visible and moving it to the front. This is a synchronized method.
Moves the window behind all other windows in the application.
Moves the window in front of all other windows.
User interactions with a window can cause it to generate the WINDOW_DESTROY, WINDOW_ICONIFY, WINDOW_DEICONIFY, and WINDOW_MOVED events.
Note |
Here's another example of how the AWT conforms to the expectations of platform users. On the Mac, the window doesn't reduce to an icon, because that is not the sort of behavior a Mac user would expect. |
A Frame implements a resizable
window that supports a menu bar, cursor, icon, and title.
Caution |
An AWT frame has nothing to do with an HTML frame, which is a concept for defining panes inside a window and was developed by Netscape. A Java frame only applies within an applet window or a Java application. You can't use a Java frame to set up Frames on an HTML page. |
This is your basic application-type window, which looks like the windows you'll see in most programs. You can use frames with an applet; in fact, it's the only way to use dialog boxes and menu bars.
Frames generate the same events as windows, which they extend: WINDOW_DESTROY, WINDOW_ICONIFY, WINDOW_DEICONIFY, and WINDOW_MOVED.
The only parameter you can pass to the Frame constructor is a String, which will be the window title. You generally will create your own class that extends Frame and contains event-handling methods that override Component methods such as action, mouseDown, and keyDown. When you extend the class, you can make creator methods with more input parameters. One useful technique when you're using Frames with applets is to pass the applet, using this-the Java construct that refers to the object in whose scope the program line is in-to the Frame so that the Frame methods can invoke applet methods and read/write applet instance variables.
The most useful Frame methods follow.
Enables you to free up windowing resources when you're done with a Frame. This is a synchronized method.
Returns the integer constant that defines which cursor currently is displayed. The available Frame final static (constant) values follow:
CROSSHAIR_CURSOR
DEFAULT_CURSOR
E_RESIZE_CURSOR
HAND_CURSOR
MOVE_CURSOR
N_RESIZE_CURSOR
NE_RESIZE_CURSOR
NW_RESIZE_CURSOR
S_RESIZE_CURSOR
SE_RESIZE_CURSOR
SW_RESIZE_CURSOR
TEXT_CURSOR
W_RESIZE_CURSOR
WAIT_CURSOR
Returns the image being used when the window is reduced to an icon.
Returns the frame's menu bar.
Returns the frame's title.
Returns TRUE if the frame can be resized. This attribute can be toggled using the setResizable method.
Removes the menu bar associated with the frame. This is a synchronized method.
Sets the current cursor to the one specified by the input argument.
Sets the icon to be used when the frame is reduced to an icon to the input image.
Sets the menu bar for the frame. This is a synchronized method.
Changes the frame size if the input parameter is TRUE. If the input is FALSE, the frame is a fixed size.
Sets the window title.
The applet in Listing 17.37 shows you how to work with a frame. A button on the applet shows the frame. The user then can enter text in a TextArea on the frame. When the user closes the frame (either with the button in the frame or the one in the applet), the frame is hidden and the text in the frame's TextArea is put into the TextArea in the applet. The applet and the frame are shown in Figures 17.48 and 17.49.
Figure 17.48 : The applet window.
Listing 17.37. Working with frames.
import java.awt.*;
import java.applet.Applet;
public class frame_test extends Applet
{
input_frame a_frame;
Button b_open, b_close;
TextArea the_message;
boolean frame_visible;
public void init()
{
b_open = new Button("Open frame");
b_close = new Button("Close frame");
add(b_open);
add(b_close);
the_message = new TextArea(5,20);
add(the_message);
frame_visible = false;
a_frame = create_dialog("Enter a message");
// Uncomment these next two lines if you want the frame showing
//when the applet starts
//a_frame.show();
//frame_visible = true;
}
public input_frame create_dialog(String the_title) {
//create a new frame. pass this so that the frame can
//access the applet's
//instance variables in order to pass information back.
a_frame = new input_frame(the_title,this);
//set its size to 200 by 200
a_frame.resize(200,200);
return a_frame;
}
public boolean action(Event the_event, Object the_arg) {
String item_name;
//handle mouse clicks on the two buttons
if(the_event.target instanceof Button) {
item_name = (String)the_arg;
if(item_name.equals("Open frame")) {
if(!frame_visible) {
a_frame.show();
frame_visible = true;
}
}
if(item_name.equals("Close frame")) {
if(frame_visible) {
a_frame.hide();
frame_visible = false;
}
}
}
return true;
}
}
class input_frame extends Frame {
Button close_frame;
TextArea text_space;
//this has to be of type frame_test not Applet. If you use Applet
//instead of frame_test it
//will still compile but you won't be able to use my_
//parent to access the instance
//variables of the frame_test applet such as frame_visible.
frame_test my_parent;
input_frame(String the_title, frame_test host) {
super(the_title);
FlowLayout fl;
my_parent = host;
//have to define a layout manager for the frame
fl = new FlowLayout();
setLayout(fl);
close_frame = new Button("Close frame");
add(close_frame);
text_space = new TextArea("Enter your input",5,10);
add(text_space);
}
//By overriding the hide method we ensure that the text
//from the dialog is sent back
//to the applet if the frame is closed by clicking on the
//close frame button in the
//applet.
public synchronized void hide() {
String the_input;
the_input = text_space.getText();
//use the reference to the applet to pass data back by accessing the
//TextArea component
my_parent.the_message.insertText(the_input,0);
my_parent.frame_visible = false;
super.hide();
}
public boolean action(Event an_event, Object arg) {
String button_name;
String the_input;
if (an_event.target instanceof Button) {
button_name = (String)arg;
if(button_name.equals("Close frame")) {
//we don't have to do anything to copy the text to
//the applet here because we overrode the
//hide method
hide();
}
}
return true;
}
}
Notice how the applet extends the Frame class and passes the applet object, using this, to the frame so the Frame methods can work with applet data. In general, creating frames is straightforward because the AWT does most of the work for you. Frames are the basic element for Java application interfaces.
For some strange reason, the capability to control the cursor is in the Frame class, so when you can control the cursor, the user sees when a frame has focus. The applet in Listing 17.38 enables you to select the cursor from a pop-up menu. Figure 17.50 shows the frame that enables you to select the cursor. The actual cursor images are platform dependent. This is yet another way to make sure that users on every platform feel comfortable with the interface.
Figure 17.50 : A pop-up menu to pick the cursor.
Listing 17.38. Selecting the cursor from a pop-up menu.
import java.awt.*;
import java.applet.Applet;
public class cursor_test extends Applet
{
a_frame a_window;
Button b_open, b_close;
boolean window_visible;
public void init()
{
b_open = new Button("Open Window");
b_close = new Button("Close Window");
add(b_open);
add(b_close);
create_dialog("Pick a cursor");
}
public a_frame create_dialog(String the_title) {
//create a new Frame
a_window = new a_frame(the_title,this);
//set its size to 300 by 80 pixels
a_window.resize(300,80);
return a_window;
}
public boolean action(Event the_event, Object the_arg) {
String item_name;
//handle mouse clicks on the two buttons
if(the_event.target instanceof Button) {
item_name = (String)the_arg;
if(item_name.equals("Open Window")) {
if(!window_visible) {
a_window.show();
window_visible = true;
}
}
if(item_name.equals("Close Window")) {
if(window_visible) {
a_window.hide();
window_visible = false;
}
}
}
return true;
}
}
class a_frame extends Frame {
Button close_window;
Choice a_menu;
//this has to be of type cursor_test not Applet. If you
//use Applet instead of cursor_test it
//will still compile but you won't be able to use my_
//parent to access the instance
//variables of the dialogs applet such as window_visible.
cursor_test my_parent;
a_frame(String the_title, cursor_test host) {
super(the_title);
FlowLayout fl;
my_parent = host;
//have to define a layout manager for the window
fl = new FlowLayout();
setLayout(fl);
close_window = new Button("Close Window");
add(close_window);
a_menu = new Choice();
a_menu.addItem("Crosshair cursor");
a_menu.addItem("Default Arrow cursor");
a_menu.addItem("Resize Window to the Right cursor");
a_menu.addItem("Hand cursor");
a_menu.addItem("Move Window cursor");
a_menu.addItem("Resize Window Upwards cursor");
a_menu.addItem("Resize Window using NorthEast corner cursor");
a_menu.addItem("Resize Window using NorthWest corner cursor");
a_menu.addItem("Resize Window Downwards cursor");
a_menu.addItem("Resize Window using SouthEast corner cursor");
a_menu.addItem("Resize Window using SouthWest corner cursor");
a_menu.addItem("Text Editing cursor");
a_menu.addItem("Resizing Window to the left cursor");
a_menu.addItem("Hourglass cursor");
add(a_menu);
}
public boolean action(Event an_event, Object arg) {
String button_name;
String the_input;
String item_name;
if (an_event.target instanceof Button) {
button_name = (String)arg;
if(button_name.equals("Close Window")) {
my_parent.window_visible = false;
hide();
}
}
if(an_event.target instanceof Choice) {
item_name = (String)arg;
if(item_name.equals("Crosshair cursor")){
setCursor(Frame.CROSSHAIR_CURSOR);
}
if(item_name.equals("Default Arrow cursor")){
setCursor(Frame.DEFAULT_CURSOR);
}
if(item_name.equals("Resize Window to the Right cursor")){
setCursor(Frame.E_RESIZE_CURSOR);
}
if(item_name.equals("Hand cursor")){
setCursor(Frame.HAND_CURSOR);
}
if(item_name.equals("Move Window cursor")){
setCursor(Frame.MOVE_CURSOR);
}
if(item_name.equals("Resize Window Upwards cursor")){
setCursor(Frame.N_RESIZE_CURSOR);
}
if(item_name.equals("Resize Window using NorthEast corner cursor")){
setCursor(Frame.NE_RESIZE_CURSOR);
}
if(item_name.equals("Resize Window using NorthWest corner cursor")){
setCursor(Frame.NW_RESIZE_CURSOR);
}
if(item_name.equals("Resize Window Downwards cursor")){
setCursor(Frame.S_RESIZE_CURSOR);
}
if(item_name.equals("Resize Window using SouthEast corner cursor")){
setCursor(Frame.SE_RESIZE_CURSOR);
}
if(item_name.equals("Resize Window using SouthWest corner cursor")){
setCursor(Frame.SW_RESIZE_CURSOR);
}
if(item_name.equals("Text Editing cursor")){
setCursor(Frame.TEXT_CURSOR);
}
if(item_name.equals("Resizing Window to the left cursor")){
setCursor(Frame.W_RESIZE_CURSOR);
}
if(item_name.equals("Hourglass cursor")){
setCursor(Frame.WAIT_CURSOR);
}
}
return true;
}
}
You can put a menu bar in a frame or window, but not an applet. Refer to the illustration in Figure 17.3 showing the five menu-related classes: MenuComponent, MenuBar, Menu, MenuItem, and CheckboxMenuItem.
All the other menu classes inherit from MenuComponent. MenuComponent is an abstract class, but you'll use these methods fairly often.
Returns the font used for the current item.
Sets the font to be used to display the item on which the menu is invoked.
The MenuBar class is a container for a set of menus displayed with a frame. The key MenuBar methods follow.
Adds a menu to the menu bar. The return value is a handle to the added menu. Menus are added left to right. This is a synchronized method.
Returns the number of menus currently in the menu bar.
Returns the menu that is defined as the Help menu for the menu bar.
Returns the menu item at a given location in the menu bar.
Removes the menu at the specified position. This is a synchronized method.
Removes the specified menu from the menu bar. This is a synchronized method.
Sets the specified menu to be the Help menu, which always is placed on the right side of the menu bar. This is a synchronized method.
The Menu class implements pull-down menus. There are two constructor methods and some useful Menu methods.
Creates a new menu with the specified label.
Creates a new menu with the specified label, which can be torn off from the menu bar.
Adds the specified menu item to the menu. You make hierarchical menus by adding menus to another menu. This is a synchronized method.
Adds a new entry to the menu.
Adds a separating line to the menu.
Returns a count of the number of items in the menu.
Returns the menu item at the specified location.
Returns TRUE if the menu has tear off enabled.
Removes the menu item at the specified location. This is a synchronized method.
Removes the specified menu item. This is a synchronized method.
The MenuItem class implements the functionality of a single entry in a pull-down menu. When you create one, you have to supply its label (as a string) as the input parameter. You use the add method to add MenuItems to menus. The most useful MenuItem methods follow.
Grays out a menu item and prevents the user from selecting it.
Enables a menu item so the user can select it.
Enables a menu item if the logical statement evaluates to TRUE.
Returns the item's label.
Returns TRUE if the user can select the item.
Changes the label of the menu item to the specified string.
An ACTION_EVENT is generated whenever a menu item is selected by the user.
The CheckboxMenuItem class extends MenuItem and implements the functionality of a menu item with an associated checkbox. The two methods of this class that will come in handy follow.
Returns TRUE if the menu item is checked.
Sets the state of the menu item.
The applet in Listing 17.39 shows how to use menus. It creates a new frame to support the menu bar because you can't put a menu bar in an applet itself. The two buttons in the main applet window enable you to open and close the frame. Figure 17.51 shows the applet interface. Menus are a good example of how different your interfaces can look on different platforms. Mac users expect menu bars to show up at the top of the screen as the focus changes between windows. As a result, the peer classes on the Mac put the menu bar at the top of the screen rather than inside the frame, as a UNIX or Windows user would expect. Although the functionality is the same as you would see on any other platform, the details are designed to make Mac users feel comfortable. This is one of the strong points of Java: users on any platform will see an interface that looks familiar. Unfortunately, it does mean that you, the programmer, will have to spend a little extra time thinking about the implications of these types of differences on your user interface.
Listing 17.39. Using a menu bar.
import java.awt.*;
import java.applet.Applet;
public class menu_test extends Applet
{
Frame f;
Button b_open, b_close;
boolean window_visible;
public void init()
{ Menu m_about, m_tools, m_sub;
//these buttons will allow us to control the new window
b_open = new Button("Open Window");
b_close = new Button("Close Window");
add(b_open);
add(b_close);
//make a new frame because the applet can't have a menu bar
f = new Frame("test");
//set the initial size of the frame
f.resize(200,200);
//make a new menu bar and attach it to the frame
MenuBar mb = new MenuBar();
f.setMenuBar(mb);
//set up the various menu items
m_about = new Menu("About");
m_about.add(new MenuItem("Credits"));
m_about.add(new MenuItem("Register"));
m_about.add(new MenuItem("Help"));
m_tools = new Menu("Tools");
m_tools.add(new MenuItem("Line"));
m_tools.add(new MenuItem("Square"));
//add a checkbox menu item
m_tools.add(new CheckboxMenuItem("Dashed Lines"));
//create a hierarchical menu
m_sub = new Menu("Colors");
m_sub.add("Red");
m_sub.add("White");
m_sub.add("Blue");
//Make the menu hierarchical by adding it as though it were a menu
//item
m_tools.add(m_sub);
mb.setHelpMenu(m_about);
mb.add(m_tools);
mb.add(m_about);
//show the new frame
f.show();
window_visible = true;
}
public boolean action(Event the_event, Object the_arg) {
String item_name;
//handle mouse clicks on the two buttons
if(the_event.target instanceof Button) {
item_name = (String)the_arg;
if(item_name.equals("Open Window")) {
if(!window_visible) {
f.show();
window_visible = true;
}
}
if(item_name.equals("Close Window")) {
if(window_visible) {
f.hide();
window_visible = false;
}
}
}
return true;
}
}
You can build your own dialog boxes using Frames, but the Dialog class enables you to create modal dialog boxes. A modal dialog box forces the user to deal with the dialog box before doing anything else. Although this generally isn't a good idea, certain types of actions, such as notifying a user of a problem, require user input before the program can do anything else.
Dialog boxes are containers, so you can add components to them, but their default Layout Manager is BorderLayout rather than FlowLayout. User interaction with dialog boxes can generate the WINDOW_DESTROY, WINDOW_ICONIFY, WINDOW_DEICONIFY, and WINDOW_MOVED events. As with other window-related classes, you should call the dispose method (inherited from Window) when the window is destroyed in order to free up window system resources.
The two dialog creator methods follow.
Creates a new dialog box that is modal if the second argument is TRUE.
Functions like the preceding method, except that you can specify the name of the dialog box.
Unfortunately, Dialogs can be created only as children of Frames, as you can see from the arguments to the creator functions. This might make you think that you can't use Dialogs with your applets but, through the use of a minor subterfuge, you can. The secret, demonstrated in Listing 17.40, is to make a Frame but not show it. The applet extends the Dialog class and makes a simple Dialog that is shown in Figure 17.52.
Figure 17.52 : A dialog box in action.
Listing 17.40. Using a dialog.
import java.awt.*;
import java.applet.Applet;
public class dialogs extends Applet
{
Frame a_window;
user_dialog real_dialog;
Button show_dialog;
public void init()
{
//You need a Frame to make a dialog but you don't have to display it.
a_window = new Frame("Testing Dialogs");
//This is a non-modal dialog, that is the user can
//switch between windows
//while the dialog is active. Change the false to true
//to make it modal and force
//the user to deal with and dismiss it before doing anything else.
real_dialog = new user_dialog(a_window,"The Sky Is Falling!",false);
show_dialog = new Button("Show Dialog");
add(show_dialog);
}
public boolean action(Event the_event, Object the_arg) {
//There's only one button so we just make sure that a button was clicked
if(the_event.target instanceof Button) {
real_dialog.show();
}
return true;
}
}
class user_dialog extends Dialog {
Button b_ok, b_cancel;
user_dialog(Frame a_frame,String the_title, boolean modal) {
super(a_frame,the_title,modal);
FlowLayout fl;
b_ok = new Button("Ok");
fl = new FlowLayout();
setLayout(fl);
add(b_ok);
//resize the dialog so the title, "The Sky Is Falling!"
//shows. You should use
//FontMetrics here to see how many pixels wide that phrase is on a given
//platform
resize(200,40);
}
public boolean action(Event the_event, Object the_arg) {
if(the_event.target instanceof Button) {
//Hide the dialog. If you're not going to use it again you should call
//window.dispose to get rid of it. Hide just makes it invisible
hide();
}
return true;
}
}
This class enables you to access the user's native file opening and saving dialog boxes. It's a modal dialog box that you can create with the following two creator methods.
Creates a file selection modal dialog box with the specified title.
Functions like the preceding method, except that you can specify, using the third parameter, whether this is a file selection or a file saving dialog box. The two constants you should use follow:
FileDialog.LOAD: Open File dialog box
FileDialog.SAVE: Save File dialog box
The general approach to using this class is to create the dialog box and then show it when you want the user to select or save a file. When the user finishes with the modal dialog box, you use the getDirectory and getFile methods to get a path to the file that's to be saved or loaded. The methods you'll find most useful for FileDialog follow.
Returns the directory to the file the user has selected or the directory where the user wants to save a file. The string uses backslashes to separate directories and it doesn't end with a backslash. A file on disk Space inside folder Files, for example, would return /Space/Files.
Returns the name of the file to be opened or the name the file is to be saved as.
Returns the FilenameFilter-an interface specification that enables you to filter which files appear in the dialog box-associated with the File dialog box.
Returns the mode of the dialog box.
Enables you to set the directory the user sees when the dialog box opens. Specify the directory with the same string format returned by the getDirectory method.
Sets the file in the dialog box.
Associates an instance of the FilenameFilter with the dialog box. The one method in the FilenameFilter class is called for every file, and only those that pass the test are displayed.
The applet in Listing 17.41 generates the two File dialog boxes shown in Figures 17.53 and 17.54, depending on the second parameter in the creation function.
Figure 17.53 : The File Open dialog box with the mode set to FileDialog.Load.
Figure 17.54 : The File Save dialog box with the mode set to FileDialog.Save.
Listing 17.41. Generating File dialog boxes.
import java.awt.*;
import java.applet.Applet;
import java.io.*;
public class file_dialog extends Applet
{
Frame a_window;
FileDialog real_dialog;
Button show_dialog;
public void init()
{
//You need a Frame to make a dialog but you don't have to display it.
a_window = new Frame("Testing Dialogs");
//This is a dialog that lets you save files. By changing
//the last parameter to
// FileDialog.LOAD you'll get a dialog that lets you open a file.
real_dialog = new FileDialog(a_window,"Save File to:");
show_dialog = new Button("Show File Dialog");
add(show_dialog);
}
public boolean action(Event the_event, Object the_arg) {
//There's only one button so we just make sure that a button was clicked
if(the_event.target instanceof Button) {
real_dialog.show();
String a_file = real_dialog.getFile();
String a_directory = real_dialog.getDirectory();
System.out.println(a_directory + " " + a_file);
}
return true;
}
}
The FilenameFilter interface is very simple; it has only one method:
boolean, accept(File directory, String file_name)
This is called for each file. The input parameters are the directory the file is in and the filename. You can use this information to decide whether a file should be shown in the dialog box. If the method returns TRUE, the file is shown.
You can implement a FilenameFilter as shown in Listing 17.42.
Listing 17.42. Implementing a FilenameFilter.
public class my_filenameFilter implements FilenameFilter {
my_filenameFilter() {
....
}
public boolean accept(File a_directory, String file_name) {
//some code to decide if the file should be displayed
}
}
FilenameFilters are useful in preventing a user from inadvertently picking the wrong file.
Although all the examples in this chapter have been applets (a
conscious choice because most of the current excitement about
Java is related to its role in the WWW), you can use almost all
these techniques in an application. Unless you're using applet-specific
methods, you can replace Applet
with Panel. So, instead of
extending Applet, you extend
Panel. You also can still
extend the Applet class but
put the Applet into a Frame.
Note |
An example of an applet-specific method is getImage. In an application, you can use the Toolkit's getImage method instead and everything else stays the same. |
Note |
Remember that Applet inherits from Component, so you can place an applet in a container-such as a frame-just as you would put a button in the frame. |
The general approach is to add a main method to your code, create a Frame, add your class that extends Applet or Panel to the Frame, and then call the init and startup methods for the class that extends Applet. Make sure that the event handler for the Frame handles the WINDOW_DESTROY method properly.
Most of what's covered in this chapter is independent of the Applet class's methods. The following are the methods that are of use when building the user interface.
Loads the GIF or JPEG image at the specified URL.
Enables you to specify a base URL, as you would get from getDocumentBase(), and an address relative to that address.
Displays the specified message to the user. In the applet viewer, it's placed in the bottom edge of the applet window. The specific place where the display occurs is browser dependent.
Clearly, you can build pretty much any Java program that doesn't rely on these methods, so switching between applets and applications shouldn't be too difficult as far as your GUI is concerned.
You can add more elements to your interface toolbox in two ways: you can extend existing components yourself or you can use someone else's classes.
One of the nice features of object-oriented programming is that you can extend existing classes and add new functionality with minimal effort. The applet in Listing 17.43 contains the code for a class called image_button_component, which behaves like a button but has an image for its interface rather than the regular button picture.
Listing 17.43. The image_button_component class.
import java.applet.Applet;
import java.awt.*;
public class image_button extends Applet {
MediaTracker the_tracker;
image_button_component the_button;
public void init()
{
Image the_picture, inverse_picture;
//load the two images, one for the regular
//button image and one for when the
//mouse button is held down on the button
the_tracker = new MediaTracker(this);
the_picture = getImage(getDocumentBase(),"test.gif");
the_tracker.addImage(the_picture,0);
inverse_picture = getImage(getDocumentBase(),"invert.gif");
the_tracker.addImage(inverse_picture,0);
//Wait until both images are fully loaded.
try {
the_tracker.waitForID(0);
} catch(InterruptedException e) {}
the_button = new image_button_component(the_picture,inverse_picture);
add(the_button);
}
public boolean mouseDown(Event e, int x, int y) {
if (e.target instanceof image_button_component) {
System.out.println("image_button_component clicked");
}
return true;
}
}
class image_button_component extends Canvas {
Image my_image, inverse_image;
boolean mousedown_flag;
public image_button_component(Image an_image,Image i_image) {
my_image = an_image;
inverse_image = i_image;
//the button starts unclicked
mousedown_flag = false;
//make sure the canvas image is drawn
repaint();
}
//override these methods to tell the layout manager
//the size of the button which is defined by the
//size of the image. This assumes that the two images are the same size.
public Dimension minimumSize() {
return new Dimension(my_image.getWidth(null),my_image.getHeight(null));
}
public Dimension preferredSize() {
return new Dimension(my_image.getWidth(null),my_image.getHeight(null));
}
//make sure the button image is changed when the mouse is clicked
public boolean mouseDown(Event e, int x, int y) {
mousedown_flag = true;
repaint();
//return false so the event is passed on so the mouseDown
//method in the applet can use it
return false;
}
public boolean mouseUp(Event e, int x, int y) {
mousedown_flag = false;
repaint();
//return true because the applet doesn't need this
//event but you might want to
//return false to make this more button like
return true;
}
public void paint(Graphics g) {
//determine which image to draw based on the mouse state
if (!mousedown_flag) {
g.drawImage(my_image,0,0,null);
} else {
g.drawImage(inverse_image,0,0,null);
}
}
}
The applet loads in two images-one for the regular button and one for when it's clicked-and then creates a new instance of the image_button_component class. The button is shown in Figure 17.55, whereas Figure 17.56 shows the button after being clicked with the mouse. Although this class doesn't do much, you can put in whatever behavior you want just as though it were a standard button.
Figure 17.55 : An image button.
Figure 17.56 : The same image button after being clicked with the mouse.
Note |
One technique to note is that you can assign multiple images to the same MediaTracker ID and then just wait for that one ID to finish loading instead of looking for multiple IDs to load. Of course, if speed is a concern, you might want to load the basic image first, draw the interface, and then hope that the user doesn't click the button until the second image is loaded. You can use an even better solution if you're working with a Frame. You can load the first image, draw the interface, and then set the cursor to WAIT_CURSOR until the second image is loaded. Users will be able to start familiarizing themselves with the interface while the second image is loading. |
Although you can improve the AWT by extending its classes, you're
probably better off buying that sort of thing from someone else
if the functionality you require is very complex. The following
list is a very limited one of various commercial Java classes
that bring more sophisticated interfaces to your toolbox. Inclusion
in this list shouldn't be viewed as an endorsement. Java has too
many bugs, and these tools run afoul of them, for anyone to make
definitive statements about the quality of any given package.
In addition, things are changing too rapidly to give complex software
libraries like these an in-depth evaluation and still get this
book out before 1998. View this list as a set of starting points
for your search for work-saving classes to jazz up your Java program's
interface.
Note |
One thing to keep in mind when looking at these classes is that, unlike the AWT, they have to be downloaded from the server if you're using them in an applet. Because some of these files are large, it could be a problem if the people who will be viewing your applet have slow connections. |
Note |
In general, all the components have many more methods and adjustable parameters than I mention here. If you see a component that seems in the ballpark for meeting your requirements, check with the vendor for more details. |
This is a commercial toolkit that comes with a nice printed manual to help you understand how to use it. Table 17.2 lists the primary components.
Component | Description |
Grid | A two-dimensional scrolling array of cells, much like a standard spreadsheet. This type of interface component also can be called a table. |
Misc | Includes methods to draw shadows and word-wrap text. |
Progress | A customizable progress bar. |
TabPanel | A tabbed panel that enables you to switch between window contents by clicking on tabs at the top of the window. |
Tree | A hierarchical view where you can show and hide the items inside containers. If you want to show a file structure, for example, the folders would have arrows next to them. After you click on an arrow, the items in the folder are shown. |
This is a set of three libraries: one with tools, one with financial functions, and one with widgets. Table 17.3 lists the main widgets.
Widget | Description |
Grid control | A spreadsheet-like widget. |
Group box | Enables you to group related sets of controls. |
Image button | Enables you to create an image button. |
Tabbed dialog box | A dialog box with tabs on the top that enable you to switch between screens. |
Toolbar | A toolbar with icons. |
This is a very extensive kit of useful widgets. Although it's expensive, if you use many of the widgets you'll save a lot of time. The lack of printed documentation is a drawback but not too horrible. Table 17.4 lists these widgets.
Widget | Description |
3D label | Same functionality as label, but with a 3D look and text coloring. |
Calendar | Shows a calendar for a specified date. |
Combo box | An editable, searchable text field with a drop-down listbox. There are some platform dependencies in this one. |
Direction button | Makes a button with an arrow pointing in the specified direction. |
Editable, searchable | An enhanced choice control. |
Choice listbox | |
Enhanced text fields | Components that restrict the type of data that can be entered. |
Grid control | An option in the multicolumn listbox. |
Group box panel | A panel with a label and a visual boundary. |
Image buttons | Enables you to associate different images with different button states-Normal, Pressed, and Disabled-and the position of the image with respect to the button text. |
Image canvas | A picture button type of widget. |
Image listbox | A standard listbox, but you can add images to each of the entries. |
Key Press Manager panel | Extends panel and supports tabbing between components and some key accelerators. |
Multicolumn listbox | A listbox with multiple columns. |
Performance Animator | A class that makes an animation from a single image file that saves download time, hence improving performance. |
Pop-up context menu | A better version than the one in the AWT. |
Progress gauge | A 3D color progress indicator. |
Scrolling panel | A panel with built-in scrolling. |
Spin control | Spin control and list spin control widgets. |
Splitter panel | A panel that is split into subareas, each of which can be a splitter panel. Nice for rectangular-style layouts. |
Tab panel | A standard tab dialog box widget. |
This is a table component.
This is another table component.
In addition to widget and utility collections such as these, a number of data display collections exist that have various charting types, such as bar and pie charts.
In this chapter, you learned how to build the user interface for Java applets and applications using the AWT. You saw how to draw pictures using the Graphics class and how to use the various GUI elements, such as buttons and menus, to give your project an easy-to-use interface that uses the graphical look and feel users are used to. Finally, you saw how to extend that interface and what sorts of freeware, shareware, and commercial tools are available to enhance the basic AWT repertoire of interface widgets.