by David Hanley
In this chapter you will learn to create graphical user interfaces in the Java programming language. These user interfaces are the means by which Java programs interact with their users. Mastering the use of Java's approach to graphical user interfaces will allow you to write easy-to-use interfaces that are platform independent.
Java comes with a predefined and powerful set of objects for constructing graphical user interfaces (GUIs) for your Java programs. It is called the AWT, which is short for Abstract Windowing Toolkit. The AWT allows Java programs to make user interfaces that are simple, robust, and visually attractive. Moreover, it abstracts the details about a specific platform so that the graphical interface can run on all computers on which Java runs. This flexibility is a huge advantage because graphical programs created on one platform are typically completely incompatible with other systems.
The Java philosophy also stipulates that what a user sees on the screen should be familiar-that is, a Windows user should see Windows controls and buttons, a Macintosh user should see a typical Macintosh interface, and so forth for all platforms. The Java button that you create is mapped to the analogous component on the machine on which that code is actually executing.
These two benefits-portability and familiarity-necessarily impose some restrictions on the kinds of components that Java can utilize. Java cannot utilize components available on only one machine and cannot use graphical tricks available only on one platform. Also, as a result of the stripped-down controls and design limitations, Java controls typically are not quite as nice as the controls that a good designer creates for a specific platform. This situation is being rectified, however, by the introduction of better design toolkits and a more comprehensive AWT slated for the 1.1 release of Java.
Programmers used to other windowing environments will face a steep learning curve when first encountering the Java AWT, as will programmers unused to event-driven or object-oriented programming. However, this learning curve is lessened by the clean, object-oriented design of the graphics package. Although Sun's documentation is somewhat thin, it is easy to read and describes how the various components function together. The Java SDK, downloaded during J++ installation, provides many examples of the AWT in action. The J++ Books On-Line, on the J++ CD-ROM, has a brief explanation of the AWT classes, and provides a cross-reference to each of the methods of each class. This chapter gets you started with an overview of AWT, a brief explanation of the key classes in the AWT, and some coding.
The object classes used for constructing interactive windowing user interfaces contain three basic types of entities:
The easiest way to explain these concepts is by examining some code that creates GUI components using the AWT classes. Enter the code shown in Listing 14.1, set your workspace options to execute it as a standalone application, and then run it.
Listing 14.1. The AWTTest class.
import java.awt.*;
class AWTTest extends Frame
{
Button ok , exit;
AWTTest()
{
setTitle( "The very first frame" );
setLayout( new FlowLayout() );
List list = new List();
list.addItem( "This" );
list.addItem( "that" );
list.addItem( "the other" );
add( list );
ok = new Button( "OK" );
add( ok );
exit = new Button( "Exit" );
add( exit );
show();
}
public boolean action( Event evt , Object o )
{
if ( evt.target == exit )
{
System.exit( 1 );
}
return true;
}
public static void main( String [] args )
{
AWTTest t = new AWTTest();
}
}
Figure 14.1 shows what the program looks like upon execution.
Figure 14.1 : A simple Java program demonstrating a frame.
Let's see how this code works. The first line in the program tells the Java compiler that the program may use all the components available in the java.awt package:
import java.awt.*;
You could import only the specific components you are using, but importing everything is simpler and doesn't add any overhead to you code.
This line declares that your class, AWTtest, extends the Frame class, which means that your class is an child of the Frame class:
class AWTTest extends Frame
{
Therefore, you gain access to all of the public methods that are associated with the Frame class and its ancestors. Just as important, you are allowed to overload the default methods that are supplied with the Frame class. This allows you to intercept resize events, tell when the mouse has entered the frame, and accomplish other important tasks. You will see how this works later in the chapter.
The following line sets the title of the window to the string contained in the parentheses:
setTitle( "Our very first frame" );
If you do not set the title of the windows you create, the system will name the windows "untitled".
This method sets up the layout manager that is used inside this container:
setLayout( new FlowLayout() );
The flow layout manager governs where the components you add to this container will be located. In this case, with the flow layout manager, objects are laid out in order from left to right and from top to bottom, much as text is read in English. The layout manager also centers lines of components if they do not completely fill the line. You will see other layout managers that let you specify where in a region a particular component should reside in relation to the others in the next cash register example later in the chapter.
To create the list box shown in Figure 14.1, you create a new list object:
List list = new List();
The list object can reside in the container, in this case the frame AWTTest, and allows the user to pick one or more lines of text out of a collection. A list can be set to allow either multiple simultaneous selections or only one selection at a time. The program using the list object can be notified when an entry is picked, or it can check on all the selected items later.
List has a method called addItem that you use to populate the list box:
list.addItem( "This" ); list.addItem( "that" ); list.addItem( "the other" );
A list object starts life empty. The addItem method is the key to making the list useful.
Once the list object is created, you need to place it on the AWTTest frame. To do that, use the add method inherited from the Frame class:
add( list );
The flow layout manager will only work on an object that has been added. If you did not add the list, it would not appear on the screen. Try commenting out add( list ). Notice that the list box is not visible. Try putting the add( list ) back in, but move it to the line after the add( ok ) line. Note that the list box will appear between the two buttons. There are two important things to remember here:
To create and add the OK and Exit buttons to AWTTest, you write the following:
ok = new Button( "OK" ); add( ok ); exit = new Button( "Exit" ); add( exit );
Once again, the layout manager specifies how to add each object to the frame. Notice how OK and Exit are declared outside the constructor AWTTest(). In this example OK could have been declared the same way the list is:
Button OK = new Button("OK");
But this would not work for the Exit button because the Exit button is referenced inside the action subroutine. If Exit were declared inside the constructor, AWTTest, it would not be visible in the action subroutine. Try declaring both buttons in the constructor, AWTTest. The compiler will complain that it doesn't know what Exit is.
The visual elements of a Java screen start out hidden. The show() method instructs the frame to show itself:
show(); }
Windows are created in an invisible state and depend on the user to eventually show them. This delay prevents the user from seeing the components being laid out into the window area as well as seeing the window change sizes. Such a display would be both unattractive and slow.
The next method overrides one of the methods that is defined by the Frame class:
public boolean action( Event evt , Object o )
{
This method allows you to access the events that the user generates by interacting with the interface and to take the appropriate actions. In the AWTTest example you want the program to end when the user pushes the Exit button. To do this you use the arguments that come with the action method. The action method contains a field that is a handle to the object that was manipulated, generating the event that triggers the action method. You need to recognize the event and shut down the program:
if ( evt.target == exit )
{
System.exit( 1 );
}
In this case you are testing to see whether the object is the Exit button. If the object is the Exit button, you exit the program.
The main() function, required when a program is run as a standalone, creates an instance of your new class:
public static void main( String [] args )
{
AWTTest t = new AWTTest();
}
Run this program, resize the windows, and so on to get a feel for how Java AWT components look and how you can interact with them. Experiment with the title and the order of the add statements. To prove that an event is generated when the OK button is pressed, add this line:
list.addItem(o.toString());
Place this line in the action subroutine. Remember to declare the list at the top of the class, along with the OK and Exit buttons. Declaring it outside the constructor will make the list visible inside the action method. Pressing the OK button while the program is running will cause OK to be added to the list. This is an example of how an event-driven program works.
How does an event-driven program differ from other types of programs? An event-driven program does not execute a series of instructions and exit when finished. Rather, an event-driven program sets up a GUI interface and exits. The system has the responsibility for calling the appropriate part of your program when the user clicks a button. Instead of your program calling the system to conduct I/O, I/O by the system triggers your program.
The following sections highlight the three principal groups of objects that are available in the AWT class library: components, containers, and layouts.
Common controls that are part of the component class include
These are the primary controls you will use over and over in your programs to enable user interaction.
As a derivative of the Component class, these controls can be added to containers and arranged by layouts. In addition, you can derive new controls from the classes that are available to you. This allows you to customize the behavior of the controls you derive from the basic AWT classes. You can also assign any component object you have allocated to an object array of type component. This type of assignment enables you to handle component objects in a consistent fashion in your code through the common members and methods of the Component class. Therefore, an array of components might have as its members buttons, text fields, or any user-defined class derived from a component. This technique is very useful when processing collections of display objects. Let's look at the controls available to you through the Component class of the AWT library.
Buttons are components that a user can press to activate a certain reaction on the part of your program. Buttons can be labeled or unlabeled; although, clearly, an unlabeled button is not terribly useful. (See Figure 14.2.)
Pressing a button calls the action() method of the container in which it resides. The call can help you determine which button was pressed. One way, which we have already seen, is to compare the target member of the Event object that gets passed to the action() method of the button objects that we have created and added to the container. This technique is very fast and efficient, and the method I prefer, but it may not be practical in all cases. The action() method receives a second parameter, as well as an object. This object, in the case of a button action, is equal to the label of the button. You can use the equal() method of the argument object of the button labels to determine which button was actually pressed. For example, the code inside the action() method in the AWTTest program would be changed to look like this:
if(o.equals("Exit"))
{
System.exit( 1 );
}
This technique enables you to test for the press of a button to which you no longer have the pointer. Try declaring the Exit button inside the constructor in the AWTTest example. If you use the o.equals() method you can still catch and react to the fact that the Exit button was pushed even though the variable Exit is not visible to the action() method. The method you choose in a particular application will depend on the structure and needs of your code.
Some of the methods that buttons inherit from the Component class allow you to do some interesting things with the buttons that you create. For example, you can change the font that is used on a button with setFont() and change the color of the button with setBackground(). These methods allow you to incorporate extra pizzazz and attractiveness in your visual displays.
Canvases serve several very important functions that would otherwise be difficult to achieve in the AWT paradigm. You can place a canvas in the GUI area and draw on it. Using a canvas in this way is much safer then drawing directly onto the window because you cannot accidentally overdraw the area that is reserved for graphics and overwrite the components. Moreover, drawing on the canvas requires less work than drawing on the component area because drawings made to the canvas are already adjusted to the location of the canvas in the GUI area. If the canvas is later moved to another place in the component layout, drawings made to the canvas will be adjusted to the new location. This technique reduces code dependency on exact locations of components and enables you to update your design without breaking your code.
The canvas also serves another important function: receiving messages. Because it receives all the messages that a regular control receives, by subclassing a canvas you can create new controls and widgets. You could, for example, create a graphing component and place it on the screen. You might also create a button that uses an image for its visual display area, allowing you to create attractive graphical buttons. An example of this approach appears later in the chapter.
A check box is similar to a button but differs in some important aspects. Clicking a normal button causes a message to be sent; then the button reverts to its previous state (unpressed). Clicking a check box causes its state to be toggled (see Figure 14.3). Clicking an unchecked check box sets it to the checked state. Conversely, clicking a checked check box causes it to revert to an unchecked state. Toggling is very useful for applications in which a user can pick many options. For example, in an application in which someone is selecting components for a computer, the customer could toggle on or off check boxes to indicate which components he or she wants to include with the system.
Figure 14.3 : Java check boxes.
Furthermore, you can also set the state of a check box explicitly. For example, when a button gets pressed, you can explicitly set the state of check boxes as a result of pressing that button. In the computer-purchasing application, then, a user could select a particular computer system from a list which would in turn set the check boxes representing the defaults of that computer system.
At times you might want to use check boxes, but not want the user to select more than one check box from a particular group. In this instance you can instantiate a check box group (see Figure 14.4). A check box group may contain several check boxes, but only one of these check boxes may be selected at a certain time. The check box group enforces the rule that only one of the member check boxes may be selected at a particular time. If a new check box is selected, the previously selected check box will be unchecked.
Figure 14.4 : Java check box groups.
A check box that is a member of the check box group may appear different from a normal check box. On some platforms-Windows, for example-it may have the "radio button" appearance, clearly indicating to the user that this button is a member of a group of buttons of which only one at a time can be selected. This visual clue is important because it clearly indicates to the user how that set of buttons will behave.
Something you need to bear in mind, however, is that no constraint forces the members of a check box group to be located in the same region of the screen. Scattering buttons around the screen is confusing to the user. You would need to think out such a design decision very carefully.
Sometimes-in a pull-down menu, for example-you might want to include items that can be either checked or unchecked. Although you can't add check boxes to menus directly, you can add check box menu items (see Figure 14.5). This class behaves much like the regular Checkbox class; the check boxes can be toggled, and their state can be tested.
Figure 14.5 : An example of check box menu items.
Check box menu items do not directly subclass check boxes and cannot be used as entries to check box groups. However, if you know with which menu item check boxes are associated, you can trap the events posted to them and coordinate the states of related menu items and check boxes. In the computer-purchasing application discussed earlier, a check box menu item might allow the user to choose among mutually exclusive peripherals.
A choice is a list of options, one of which may be selected at any given time. A choice is implemented as a pull-down list. When inactivated (unclicked), the choice component is a text box that displays the currently selected choice with an arrow on the right. When the component is selected, a drop-down menu is displayed that enumerates the available selections. If another entry is selected from the list, the choice component returns to its previous state with the new selection displayed as the current selection (see Figure 14.6).
Figure 14.6 : An example of choice objects.
An application for this component in the computer-purchasing example might be the user's choice of CPU speeds. The user can select only one of several CPU speeds, but only needs to see the CPU speed selected at any given time. However, if the customer wants to change the selection, he or she can quickly select the desired clock speed from the drop-down list.
The file dialog box is an example of a component that is too time-consuming to create for just one platform (see Figure 14.7). It allows the user to select a location for saving or loading a file and to navigate through folders to find a file to load or a place in which to save a file.
Figure 14.7 : A Java file dialog box.
This component is an example of a Java interface that shows the user something familiar. A Macintosh user will see the familiar Macintosh file dialog box; Windows and UNIX users will see what is familiar to them. The operating system determines what actions users can take with the dialog box; for example, they may be able to create new folders or change how the files are displayed. Java application programmers do not need to worry about these actions; they need only create the object and react to the save/load messages it issues.
The Image class represents what it sounds like: images. They can be either graphical bitmaps that are fetched from the disk or images created by drawing commands issued by the programmer. These images can add a lot of visual impact to the display of a program. You can even create animation by flipping through a set of images.
Labels are critically important for good layout design, especially because some controls are not self-documenting. In other words, the purpose of the control may not be intuitively obvious just from looking at it. Labels are noninteractive text elements that remain as part of the layout (see Figure 14.8).
Figure 14.8 : Java labels in action.
Lists display options to the user. Unlike the choice component, however, more than one of the available options is displayed at a given time and, depending on how you configure the list, more than one of its elements may be selected (see Figure 14.9).
If the list component contains more entries than can be displayed in its area, it will acquire a scrollbar, allowing the user to scroll through the entries to make a choice. This behavior is automatic and requires no special programming when you utilize the List class. The encompassing container object receives notification when a member of the list is selected/unselected and can query the list at any time to see which of the members are currently selected.
Menu bars are a very convenient way to allow users to access a large number of options quickly. Additionally, the options may be grouped logically to assist the users in finding important options. Menu bars must be added to a frame and are therefore unavailable to applets that reside directly in the browser, although they are still available to applets that open independent frames (see Figure 14.10).
Figure 14.10 : A Java menu bar.
To create menu bars, you must create menu items (which are named) and add them to the menu bar, which is then attached to a certain frame. The menus themselves contain menu items. When the user interacts with a menu bar, events that correspond to the menu items are triggered.
A particular menu item might not be available at a given time because of the state of the program. For example, in a word processor, if no file is present in the program, the Save option in the menu should not be available. In this case, you can disable the particular option, and the AWT library will graphically indicate to the user that the option is not available.
Scrollbars can enable the user to view an area larger than the area that is physically present in the component (see Figure 14.11). For example, if you want to display a picture that is larger than the available viewing area, you can display only a section of the picture and then allow the user to move the subregion of the picture that he is viewing over the actual area occupied by the picture.
Figure 14.11 : Java scrollbars.
It is worth noting, however, that no behavior of the scrollbar is automatic. The programmer utilizing the scrollbar object must trap the user interactions with the scrollbar and adjust the other components of the layout. This type of programming is tedious and time-consuming.
Of course, there are other possible uses for scrollbars, such as setting values across a certain range. For example, a screen that allowed the user to pick a color might have three scrollbars that represent the red, green, and blue components of the color desired. The updating of the scrollbar would display the new color selected.
Text areas are components that represent rectangular areas of text on the screen with scrollbars (see Figure 14.12). You do not need to manage interaction with the scrollbars in this case, as it is handled within the text area component. You can use text areas to display long sets of text in an area too small to show the entire set at once.
Figure 14.12 : An example of a text area.
The text area can be set in edit or nonedit mode. In the edit mode the user can select the text area and modify the contents. You can use this technique to allow the user to enter a lot of text in a free-form fashion.
Text fields create one line of text on the display that can be set to a value (a string) and edited by the user (see Figure 14.13). They do not contain scrollbars and have a much smaller screen footprint then the text area widget. Text fields are a very good way to accept one-line text entries. For example, two text fields could accept a user's login name and password.
Figure 14.13 : An example of text fields.
Now that you know about the component classes that are available in the Java AWT, the next elements to examine are the containers into which you can place the components. These containers differ in the way in which they appear to the user, how they treat components added to them, and how they interact with other containers.
All containers are derived from the common base class, Container, to which objects of type component can be added. Containers come with default managers that determine how the internal components are laid out, but the default layout manager can be changed. Containers can also locate internal components and return lists of the components that they contain.
Dialog boxes are windows that are intended to be short-lived. They must be the children of a frame and can optionally block access to the parent frame while they are open. This container is useful for information that must be either displayed or acquired before a program can continue. For example, you might require users to enter a valid password before allowing them to interact with the program. Or you might want to inform the user of an important event that must be acknowledged before the program continues (see Figure 14.14).
Figure 14.14 : A Java dialog box.
Frames are independent windows. Frames have a title bar and other widgets that are associated with frames on the operating system running your Java program. For example, the frame in Figure 14.15 is running on Windows 95 and has the normal Windows Minimize, Maximize, and Close buttons.
Frames also have a number of useful methods that you can use or override to assist in creating effective interfaces:
Panels are not windows in their own right. They must exist in some other kind of window. They are, however, still containers to which components can be added. Their primary purpose is to arrange a set of components in a location. For example, a cluster of buttons that belong together can be placed into a panel, which can then be added to a window. An arbitrary number of panels can then be added to a window.
Your program design can move the panel to a new location, or the layout manager can move it; either way, everything the panel contains will move with it to the new location. This containment feature, once again, simplifies the design and permits quick modification.
A window by itself is simply a rectangle on the user's screen; Frame and Dialog subclass it to create a window, to which they draw the appropriate image. However, windows can be useful widgets to directly extend to achieve a particular window appearance. You could create a frame with different borders or headers than are usual, for instance.
Placing components into a window based on absolute pixel locations is typically not appropriate in Java programs because different computer systems use different font sizes, draw components differently, and use different window border widths. To solve this problem, Java uses layout managers. Layout managers accept components along with hints of how the components should be laid out in relation to each other.
The layout manager determines how much space each component needs and arranges the components accordingly. For example, when a window is resized, the layout manager intercepts the resize message and lays out the components to accommodate the new space available. Several layout managers are available.
A border layout arranges the components along the edges of a container and allows another component to be laid out in the center of the container in the space left over. The terminology is north, south, east, west, and center. Only one component can be added to each area; if you want to have more than one component occupying a region, you can add the components to a panel and then add the panel to the region.
Additionally, when a border layout object is constructed, you can specify how much area to place between the various screen regions. By default, a border layout places no space between the components.
A card layout is useful when you need to switch between several sets of components. You can add several panels to a card layout, and it can flip between the panels when signaled.
For example, a program that has to get information from a user may have too many questions to fit comfortably on one screen. You can create several panels that ask for information about a certain topic. The program can flip between the panels to acquire information about the various topics. This method is much faster and easier than erasing and redrawing the components. It is also better than maintaining many separate windows.
A flow layout allows you to place components into a container in a simple fashion, from left to right and from top to bottom, as in English text. You can also tell the container to flow a line of components as centered, aligned left, or aligned right. The flow layout is very useful for laying out buttons in a toolbar.
A grid bag layout allows a number of components to be laid out in a container in relation to each other. The grid bag layout allows components to be placed in a grid cell that extends for a number of cells. They are arranged in relation to each other, but the size of cells in a column may change to accommodate the varying sizess of the components that they contain.
A grid layout is a grid of a fixed number of cells. The grid cells are filled in with components from left to right and from top to bottom, in the order that they are added, and they will occupy the same amount of space.
In the following sections you use the objects that are available in the AWT package to construct some real programs.
This section clarifies the function of the Java components that have been covered in this chapter by creating some simple Java programs. These are Java applications, but you can make them into applets by removing the main() function, subclassing Applet, and changing the class constructor to an init() function.
Suppose you have been given the task of writing a cash-register program using the components that you have seen. How will you proceed?
A cash register consists of two entities: a readout that displays the entries and a button pad on which you enter prices and request a sum.
The text display should allow the user to make many entries in a vertical fashion and to scroll through the entries. The buttons should be the same size and arranged neatly in a grid so as to be easy to find.
Do any of these specifications sound familiar? The readout is best implemented as a text area, and the keys should be implemented as buttons placed in a grid layout. The layout of the entire window should be a border layout, with the text area aligned to the north and the keys aligned to the south. Therefore, you will have to place the buttons in a panel, which will then be aligned to the south.
You will need to trap every digit (or decimal) entered, add it to the panel, and place it in an accumulator string.
Aside from the numeric keys, you need three special-case keys:
Listing 14.2 shows a program that accomplishes these tasks.
Listing 14.2. The Cash class.
/*
Java cash register.
This program is a simple demonstration of a java application utilizing
the AWT types Button, TextArea , GridLayout , BorderLayout,
and not least of all, Frame
*/
import java.awt.*;
public class cash extends Frame
// This lets us both create a frame and override default messages to intercept
// the user actions
{
//The following variables are declared globally to this object because they //will be needed in several methods.
TextArea Readout; //This will be the handle to the cash register 'screen'
String Current = ""; //This is the line of text currently being entered.
double Total = 0.0; //This is the sum of the lines thus far entered.
//These strings will be used as labels for the buttons that will be placed
//into the grid. It is important to observe the ordering of these; items are //placed into a grid layout in left to right, top to bottom order.
String [] Symbols =
{
"1" , "2" , "3" , "+" ,
"4" , "5" , "6" , "Clear" ,
"7" , "8" , "9" , "Quit" ,
"." , "0" , "="
};
//This is the constructor for the cash register. The initialization takes //place by default in this method; it is called when the cash register object is created.
public cash()
{
super("Cash register"); //This initializes the parent of the cash register, //the frame.
setLayout( new BorderLayout() ); //Sets the layout for the whole frame
//The readout needs only to be added to the north (top) end of the //container.
//It is created empty, and the layout manager will size it for us.
Readout = new TextArea();
add( "North" , Readout );
//The panel will contain all the buttons and will be using a grid layout
Panel buttonPanel = new Panel();
buttonPanel.setLayout( new GridLayout( 4 , 3 ) );
//create all the buttons from the array
//note that they are added to the panel, and not to the frame.
for( int a = 0 ; a < Symbols.length ; ++ a )
{
buttonPanel.add( new Button( Symbols[ a ] ) );
}
//add the panel( with all its buttons ) to the frame.
add( "South" , buttonPanel );
//resize the frame
resize( 300 , 400 );
//Now that the frame is done, show it! This way the user did not see //everything being laid out. Frames are invisible by default
show();
//Now we just leave the function. The AWT will call out action functions //when the user has done something.
}
//Add the current number to the sum.
//If the current is not a number, let the user know and zero out the current
//entry, allowing them to proceed.
void AddCurrent()
{
try //the code here could throw an exception, which we need to catch, //regardless
{
Double d = new Double( Current ); //create a real number from our //string
Readout.appendText( "\n" ); //insert a carrige
Total += d.doubleValue(); //add the value to the current
}
catch( NumberFormatException e )
{
Readout.appendText( "bad entry\n" ); //let the user know she messed up
}
Current = ""; //In either case, start a fresh entry
}
//process the user actions
public boolean action(Event event, Object arg)
{
//handle quitting elegantly
if ( arg.equals( "Quit" ) )
{
System.exit( 1 );
}
else if ( arg.equals( "Clear" ) ) //clear everthing
{
Readout.setText( "" );
Current = "";
Total = 0.0;
}
//add in the current, and show the total.
//don't clear anything, because the user may want to add more.
else if ( arg.equals( "=" ) )
{
AddCurrent();
Readout.appendText( "----------\n" + Total + "\n" );
}
else if ( arg.equals( "+" ) )
{
AddCurrent();
Readout.appendText( "\n" );
}
//otherwise, see if its one of our numerical buttons. If so,
//tack it into the display and the current accumulator
else if ( arg instanceof String )
{
String str = (String) arg;
int index = "0123456789.".indexOf( str );
if ( index != -1 )
{
Current += str;
Readout.appendText( str );
}
}
return true;
}
//create this as an object for maximum flexibility.
public static void main( String [] args )
{
new cash();
}
}
Programs like this one will not turn you into the next Bill Gates, but they are good ways to learn the Java AWT.
Working in the Java motif, the setting of the next program is a Java coffee shop.
The coffee shop should allow the purchaser to do the following:
The first three choices-coffee type, flavor, and size-can be implemented as choice components. The special requests in the fourth item (cream, cinnamon, or iced) should be check boxes because the user can choose any combination of items from the list of special requests (for example, the customer choosing sugar or cream in her coffee). You will need a button to indicate that the choice has been made and a text area to display the completed order.
The completed program appears in Figure 14.16, and Listing 14.3 shows the source listing.
Figure 14.16 : The Java coffee shop.
Listing 14.3. The Coffee class.
/*
A java coffee shop-- utilizing panels, frames, text areas,
and other cool stuff.
*/
import java.awt.*;
public class coffee extends Frame
{
Choice type , flavor , size;
//This is a function to make construction of text boxes easier.
//It also allows for more rapid modification of properties of several objects.
Choice MakeChoices( String sbuf[] )
{
Choice c = new Choice();
for( int a = 0 ; a < sbuf.length ; ++ a )
{
c.addItem( sbuf[ a ] );
}
return c;
}
//The contents of all the text boxes. Because they are
//grouped here, they are easy to modify in a consistent
//fashion.
//The types of coffee we have available
String [] types = { "Coffee" , "Latte" , "Mocha" ,
"Espresso" , "Cappucino" };
//The flavors of coffee
String [] flavors = { "Plain" , "Hazelnut" , "Amaretto" ,
"Almond" , "Chocolate" };
//and the sizes
String [] sizes = { "Small" , "Medium" , "Large" };
//This constructs the choice panel with the drop-down
//boxes that will allow the user to choose the
//kind, flavor, and size of the coffee
void MakeChoicePanel()
{
Panel p = new Panel();
type = MakeChoices( types );
flavor = MakeChoices( flavors );
size = MakeChoices( sizes );
p.setLayout( new FlowLayout() );
p.add( type );
p.add( flavor );
p.add( size );
add( "North" , p );
}
//these global check boxes will be set to the label of the
//extra to which they refer.
//They will be tested for boolean state later.
Checkbox iced , cream , cinnamon , chocolate;
//This function constructs the panel that contains the
//check boxes that represent the various
//coffee options we have available to us.
void MakeOptions()
{
Panel p = new Panel();
p.setLayout( new FlowLayout() );
iced = new Checkbox( "Iced" );
cream = new Checkbox( "With cream" );
cinnamon = new Checkbox( "Cinnamon" );
chocolate = new Checkbox( "Chocolate" );
p.add( iced );
p.add( cream );
p.add( cinnamon );
p.add( chocolate );
add( "South" , p );
}
//The members of the ordering region; a text area,
//and an ordering button.
TextArea display;
Button order;
//Make the ordering panel.
void MakeOrder()
{
Panel p = new Panel();
p.setLayout( new BorderLayout() );
display = new TextArea();
p.add( "Center" , display );
order = new Button( "Order" );
p.add( "West" , order );
add( "Center" , p );
}
//The constructor of this function sets up a few basic
//window elements, build the panels,
//shows the windows, and exits
public coffee()
{
setTitle( "Java coffee shop" );
resize( 400 , 300 );
setLayout( new BorderLayout() );
MakeChoicePanel();
MakeOptions();
MakeOrder();
show();
}
//Handle the user actions--in this case,
//just showing the order if the order button is pressed.
public boolean action( Event e , Object o )
{
if ( e.target == order ) // if they are ordering coffee!
{
//Construct a string describing the order
String desc = "Okay, you ordered a ";
desc += size.getSelectedItem() + " ";
desc += flavor.getSelectedItem() + " ";
desc += type.getSelectedItem() + "\n";
//now, consider all the extra stuff the customer
//may have asked for.
desc += "And you want that ";
if ( iced.getState() == true )
{
desc += "iced ";
}
if ( cream.getState() == true )
{
desc += "with cream ";
}
if ( cinnamon.getState() == true )
{
desc += "with cinnamon";
}
if ( chocolate.getState() == true )
{
desc += " with chocolate";
}
//Add the current order to the display.
display.appendText( desc + "\n" );
}
return true;
}
//build a new coffee ordering object.
public static void main( String [] args )
{
new coffee();
}
}
Note that this program demonstrates some new techniques. The constructor of the coffee object does not have sole responsibility for creating all the objects that comprise the coffee shop display. Instead, several helper functions each construct a portion of the display. This technique is important because the code to create the display is fairly complex, and the functions partition the functionality into easy-to-understand segments.
These functions use only a few variables that are global to the object. These global variables are needed so that the action() function can query them to generate its list of what kind of coffee the customer desires.
Note that you are somewhat limited in what you can do with the components. The program is functional, but it is not particularly visually appealing. Making it more attractive would be difficult without a great deal more manual effort. (Chapter 15, "GUI Development Using the Visual J++ Resource Wizard," explains how to create attractive visual interfaces using a more automated approach.)
For your final program, you will create a widget not available in the AWT.
The method for creating new widgets is by subclassing the Canvas class. The Canvas class creates an empty area that can be drawn on. It also receives messages concerning user activities in the area. To create a component, you need to subclass the Canvas class, draw in your component's image, and interpret the user's actions in the appropriate fashion.
The class you construct here is an image button. The image button will behave much like an ordinary button, but it will use an image in its display area instead of a text label. It will respond to user clicks by signaling the containing component that a click has occurred (see Listing 14.4).
Listing 14.4. The ImageButton class.
import java.awt.*;
class ImageButton extends Canvas
{
Image image;
Dimension ps;
ImageButton( Image i , int x , int y )
{
image = i;
ps = new Dimension( x , y );
}
//The paint function here is quite simple, but it
//could be more complex. There could be several
//version in various states, depressed, active,
//inactive, and other.
public void paint( Graphics g )
{
if ( image != null )
{
g.drawImage( image , 0 , 0 ,
ps.width , ps.height , this );
}
}
//The following two functions are 'dummies' here,
//but they serve an important purpose. They are
//called when th mouse enters and leaves the component.
//This could be useful to higlight the component
//when entered so as to alert the user for the
//potential for action.
public boolean mouseEnter( Event e , int x , int y )
{
return true;
}
public boolean mouseExit( Event e , int x , int y )
{
return true;
}
//This event does nothing here, but it could be
//used to switch to a 'pushed' version of the button
public boolean mouseDown( Event e , int x , int y )
{
return true;
}
//Ahh.. Now the real work is done. When the button
//is released, the parent is signaled a button press.
public boolean mouseUp( Event e , int x , int y )
{
Event evt = new Event( this , Event.ACTION_EVENT , null );
Container parent = getParent();
parent.postEvent( evt );
return true;
}
//We need to tell the parent what size
//that the button needs to be. This
//is determined upon construction.
public Dimension preferredSize()
{
return ps;
}
public Dimension minimumSize()
{
return ps;
}
}
class Test extends Frame
{
ImageButton ib;
Test()
{
setTitle( "Image button test" );
resize( 300 , 300 );
Toolkit tk = Toolkit.getDefaultToolkit();
Image jana = tk.getImage( "Jana2.jpg" );
System.out.println( "I got " + jana );
ib = new ImageButton( jana , 150 , 200 );
setLayout( new FlowLayout() );
add( ib );
show();
}
public boolean action( Event e , Object o )
{
if ( e.target == ib )
{
System.out.println( " ->click!<-" );
}
return true;
}
public static void main( String [] args )
{
new Test();
}
}
Figure 14.17 shows what the completed program should look like and gives the test readout.
Figure 14.17 : A Java image button.
With the information in this chapter you should be able to create forms by using the objects available in the AWT as well as creating your own controls. This approach is code centered. In Chapter 15 you will look at the merits of constructing controls in a form-centered way-that is, by drawing them into a forms designer that generates the code for you.