MindView Inc.
[ Viewing Hints ] [ 2nd Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 1st edition

©1998 by Bruce Eckel

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]

13: Creating windows
and applets

The original design goal of the graphical user interface (GUI) library in Java 1.0 was to allow the programmer to build a GUI that looks good on all platforms.

That goal was not achieved. Instead, the Java 1.0 Abstract Window Toolkit (AWT) produces a GUI that looks equally mediocre on all systems. In addition it’s restrictive: you can use only four fonts and you cannot access any of the more sophisticated GUI elements that exist in your operating system (OS). The Java 1.0 AWT programming model is also awkward and non-object-oriented.

Much of this situation has been improved with the Java 1.1 AWT event model, which takes a much clearer, object-oriented approach, along with the introduction of Java Beans, a component programming model that is particularly oriented toward the easy creation of visual programming environments. Java 1.2 finishes the transformation away from the old Java 1.0 AWT by adding the Java Foundation Classes (JFC), the GUI portion of which is called “Swing.” These are a rich set of easy-to-use, easy-to-understand Java Beans that can be dragged and dropped (as well as hand programmed) to create a GUI that you can (finally) be satisfied with. The “revision 3” rule of the software industry (a product isn’t good until revision 3) seems to hold true with programming languages as well.

One of Java’s primary design goals is to create applets, which are little programs that run inside a Web browser. Because they must be safe, applets are limited in what they can accomplish. However, they are a powerful tool in supporting client-side programming, a major issue for the Web.

Programming within an applet is so restrictive that it’s often referred to as being “inside the sandbox,” since you always have someone – the Java run-time security system – watching over you. Java 1.1 offers digital signing for applets so you can choose to allow trusted applets to have access to your machine. However, you can also step outside the sandbox and write regular applications, in which case you can access the other features of your OS. We’ve been writing regular applications all along in this book, but they’ve been console applications without any graphical components. The AWT can also be used to build GUI interfaces for regular applications.

In this chapter you’ll first learn the use of the original “old” AWT, which is still supported and used by many of the code examples that you will come across. Although it’s a bit painful to learn the old AWT, it’s necessary because you must read and maintain legacy code that uses the old AWT. Sometimes you’ll even need to write old AWT code to support environments that haven’t upgraded past Java 1.0. In the second part of the chapter you’ll learn about the structure of the “new” AWT in Java 1.1 and see how much better the event model is. (If you can, you should use the newest tools when you’re creating new programs.) Finally, you’ll learn about the new JFC/Swing components, which can be added to Java 1.1 as a library – this means you can use the library without requiring a full upgrade to Java 1.2.

Most of the examples will show the creation of applets, not only because it’s easier but also because that’s where the AWT’s primary usefulness might reside. In addition you’ll see how things are different when you want to create a regular application using the AWT, and how to create programs that are both applets and applications so they can be run either inside a browser or from the command line.

Please be aware that this is not a comprehensive glossary of all the methods for the described classes. This chapter will just get you started with the essentials. When you’re looking for more sophistication, make sure you go to your information browser to look for the classes and methods that will solve your problem. (If you’re using a development environment your information browser might be built in; if you’re using the Sun JDK then you use your Web browser and start in the java root directory.) Appendix F lists other resources for learning library details.

Why use the AWT?

One of the problems with the “old” AWT that you’ll learn about in this chapter is that it is a poor example of both object-oriented design and GUI development kit design. It throws us back into the dark ages of programming (some suggest that the ‘A’ in AWT stands for “awkward,” “awful,” “abominable,” etc.). You must write lines of code to do everything, including tasks that are accomplished much more easily using resources in other environments.

Many of these problems are reduced or eliminated in Java 1.1 because:

  1. The new AWT in Java 1.1 is a much better programming model and a significant step towards a better library. Java Beans is the framework for that library.
  2. “GUI builders” (visual programming environments) will become de rigeur for all development systems. Java Beans and the new AWT allow the GUI builder to write code for you as you place components onto forms using graphical tools. Other component technologies such as ActiveX will be supported in the same fashion.

So why learn to use the old AWT? “Because it’s there.” In this case, “there” has a much more ominous meaning and points to a tenet of object-oriented library design: Once you publicize a component in your library, you can never take it out. If you do, you’ll wreck somebody’s existing code. In addition, there are many existing code examples out there that you’ll read as you learn about Java and they all use the old AWT.

The AWT must reach into the GUI components of the native OS, which means that it performs a task that an applet cannot otherwise accomplish. An untrusted applet cannot make any direct calls into an OS because otherwise it could do bad things to the user’s machine. The only way an untrusted applet can access important functionality such as “draw a window on the screen” is through calls in the standard Java library that’s been specially ported and safety checked for that machine. The original model that Sun created is that this “trusted library” will be provided only by the trusted vendor of the Java system in your Web browser, and the vendor will control what goes into that library.

But what if you want to extend the system by adding a new component that accesses functionality in the OS? Waiting for Sun to decide that your extension should be incorporated into the standard Java library isn’t going to solve your problem. The new model in Java 1.1 is “trusted code” or “signed code” whereby a special server verifies that a piece of code that you download is in fact “signed” by the stated author using a public-key encryption system. This way, you’ll know for sure where the code comes from, that it’s Bob’s code and not just someone pretending to be Bob. This doesn’t prevent Bob from making mistakes or doing something malicious, but it does prevent Bob from shirking responsibility – anonymity is what makes computer viruses possible. A digitally signed applet – a “trusted applet” – in Java 1.1 can reach into your machine and manipulate it directly, just like any other application you get from a “trusted” vendor and install onto your computer.

But the point of all this is that the old AWT is there. There will always be old AWT code floating around and new Java programmers learning from old books will encounter that code. Also, the old AWT is worth studying as an example of poor library design. The coverage of the old AWT given here will be relatively painless since it won’t go into depth and enumerate every single method and class, but instead give you an overview of the old AWT design.

The basic applet

Libraries are often grouped according to their functionality. Some libraries, for example, are used as is, off the shelf. The standard Java library String and Vector classes are examples of these. Other libraries are designed specifically as building blocks to build other classes. A certain class of library is the application framework, whose goal is to help you build applications by providing a class or set of classes that produces the basic behavior that you need in every application of a particular type. Then, to customize the behavior to your own needs you inherit from the application class and override the methods of interest. The application framework’s default control mechanism will call your overridden methods at the appropriate time. An application framework is a good example of “separating the things that change from the things that stay the same,” since it attempts to localize all the unique parts of a program in the overridden methods.

Applets are built using an application framework. You inherit from class Applet and override the appropriate methods. Most of the time you’ll be concerned with only a few important methods that have to do with how the applet is built and used on a Web page. These methods are:

Method

Operation

init( )

Called when the applet is first created to perform first-time initialization of the applet

start( )

Called every time the applet moves into sight on the Web browser to allow the applet to start up its normal operations (especially those that are shut off by stop( )). Also called after init( ).

paint( )

Part of the base class Component (three levels of inheritance up). Called as part of an update( ) to perform special painting on the canvas of an applet.

stop( )

Called every time the applet moves out of sight on the Web browser to allow the applet to shut off expensive operations. Also called right before destroy( ).

destroy( )

Called when the applet is being unloaded from the page to perform final release of resources when the applet is no longer used

Consider the paint( ) method. This method is called automatically when the Component (in this case, the applet) decides that it needs to update itself – perhaps because it’s being moved back onto the screen or placed on the screen for the first time, or perhaps some other window had been temporarily placed over your Web browser. The applet calls its update( ) method (defined in the base class Component), which goes about restoring everything, and as a part of that restoration calls paint( ). You don’t have to override paint( ), but it turns out to be an easy way to make a simple applet, so we’ll start out with paint( ).

When update( ) calls paint( ) it hands it a handle to a Graphics object that represents the surface on which you can paint. This is important because you’re limited to the surface of that particular component and thus cannot paint outside that area, which is a good thing or else you’d be painting outside the lines. In the case of an applet, the surface is the area inside the applet.

The Graphics object also has a set of operations you can perform on it. These operations revolve around painting on the canvas, so most of them have to do with drawing images, shapes, arcs, etc. (Note that you can look all this up in your online Java documentation if you’re curious.) There are some methods that allow you to draw characters, however, and the most commonly used one is drawString( ). For this, you must specify the String you want to draw and its starting location on the applet’s drawing surface. This location is given in pixels, so it will look different on different machines, but at least it’s portable.

With this information you can create a simple applet:

//: Applet1.java
// Very simple applet
package c13;
import java.awt.*;
import java.applet.*;

public class Applet1 extends Applet {
  public void paint(Graphics g) {
    g.drawString("First applet", 10, 10);
  }
} ///:~

Note that applets are not required to have a main( ). That’s all wired in to the application framework; you put any startup code in init( ).

To run this program you must place it inside a Web page and view that page inside your Java-enabled Web browser. To place an applet inside a Web page you put a special tag inside the HTML source for that Web page[51] to tell the page how to load and run the applet. This is the applet tag, and it looks like this for Applet1:

<applet
code=Applet1
width=200
height=200>
</applet>

The code value gives the name of the .class file where the applet resides. The width and height specify the initial size of the applet (in pixels, as before). There are other items you can place within the applet tag: a place to find other .class files on the Internet (codebase), alignment information (align), a special identifier that makes it possible for applets to communicate with each other (name), and applet parameters to provide information that the applet can retrieve. Parameters are in the form

<param name=identifier value = "information">

and there can be as many as you want.

For simple applets all you need to do is place an applet tag in the above form inside your Web page and that will load and run the applet.

Testing applets

You can perform a simple test without any network connection by starting up your Web browser and opening the HTML file containing the applet tag. (Sun’s JDK also contains a tool called the appletviewer that picks the <APPLET> tags out of the HTML file and runs the applets without displaying the surrounding HTML text.[52]) As the HTML file is loaded, the browser will discover the applet tag and go hunt for the .class file specified by the code value. Of course, it looks at the CLASSPATH to find out where to hunt, and if your .class file isn’t in the CLASSPATH then it will give an error message on the status line of the browser to the effect that it couldn’t find that .class file.

When you want to try this out on your Web site things are a little more complicated. First of all, you must have a Web site, which for most people means a third-party Internet Service Provider (ISP) at a remote location. Then you must have a way to move the HTML files and the .class files from your site to the correct directory (your WWW directory) on the ISP machine. This is typically done with a File Transfer Protocol (FTP) program, of which there are many different types freely available. So it would seem that all you need to do is move the files to the ISP machine with FTP, then connect to the site and HTML file using your browser; if the applet comes up and works, then everything checks out, right?

Here’s where you can get fooled. If the browser cannot locate the .class file on the server, it will hunt through the CLASSPATH on your local machine. Thus, the applet might not be loading properly from the server, but to you it looks fine because the browser finds it on your machine. When someone else logs in, however, his or her browser can’t find it. So when you’re testing, make sure you erase the relevant .class files on your machine to be safe.

One of the most insidious places where this happened to me is when I innocently placed an applet inside a package. After uploading the HTML file and applet, it turned out that the server path to the applet was confused because of the package name. However, my browser found it in the local CLASSPATH. So I was the only one who could properly load the applet. It took some time to discover that the package statement was the culprit. In general, you’ll want to leave the package statement out of an applet.

A more graphical example

The example above isn’t too thrilling, so let’s try adding a slightly more interesting graphic component:

//: Applet2.java
// Easy graphics
import java.awt.*;
import java.applet.*;

public class Applet2 extends Applet {
  public void paint(Graphics g) {
    g.drawString("Second applet", 10, 15);
    g.draw3DRect(0, 0, 100, 20, true);
  }
} ///:~

This puts a box around the string. Of course, all the numbers are hard-coded and are based on pixels, so on some machines the box will fit nicely around the string and on others it will probably be off, because fonts will be different on different machines.

There are other interesting things you can find in the documentation for the Graphic class. Any sort of graphics activity is usually entertaining, so further experiments of this sort are left to the reader.

Demonstrating
the framework methods

It’s interesting to see some of the framework methods in action. (This example will look only at init( ), start( ), and stop( ) because paint( ) and destroy( ) are self-evident and not so easily traceable.) The following applet keeps track of the number of times these methods are called and displays them using paint( ):

//: Applet3.java
// Shows init(), start() and stop() activities
import java.awt.*;
import java.applet.*;

public class Applet3 extends Applet {
  String s;
  int inits = 0;
  int starts = 0;
  int stops = 0;
  public void init() { inits++; }
  public void start() { starts++; }
  public void stop() { stops++; }
  public void paint(Graphics g) {
    s = "inits: " + inits + 
      ", starts: " + starts +
      ", stops: " + stops;
    g.drawString(s, 10, 10);
  }
} ///:~

Normally when you override a method you’ll want to look to see whether you need to call the base-class version of that method, in case it does something important. For example, with init( ) you might need to call super.init( ). However, the Applet documentation specifically states that the init( ), start( ), and stop( ) methods in Applet do nothing, so it’s not necessary to call them here.

When you experiment with this applet you’ll discover that if you minimize the Web browser or cover it up with another window you might not get calls to stop( ) and start( ). (This behavior seems to vary among implementations; you might wish to contrast the behavior of Web browsers with that of applet viewers.) The only time the calls will occur is when you move to a different Web page and then come back to the one containing the applet.

Making a button

Making a button is quite simple: you just call the Button constructor with the label you want on the button. (You can also use the default constructor if you want a button with no label, but this is not very useful.) Usually you’ll want to create a handle for the button so you can refer to it later.

The Button is a component, like its own little window, that will automatically get repainted as part of an update. This means that you don’t explicitly paint a button or any other kind of control; you simply place them on the form and let them automatically take care of painting themselves. So to place a button on a form you override init( ) instead of overriding paint( ):

//: Button1.java
// Putting buttons on an applet
import java.awt.*;
import java.applet.*;

public class Button1 extends Applet {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public void init() {
    add(b1);
    add(b2);
  }
} ///:~

It’s not enough to create the Button (or any other control). You must also call the Applet add( ) method to cause the button to be placed on the applet’s form. This seems a lot simpler than it is, because the call to add( ) actually decides, implicitly, where to place the control on the form. Controlling the layout of a form is examined shortly.

Capturing an event

You’ll notice that if you compile and run the applet above, nothing happens when you press the buttons. This is where you must step in and write some code to determine what will happen. The basis of event-driven programming, which comprises a lot of what a GUI is about, is tying events to code that responds to those events.

After working your way this far through the book and grasping some of the fundamentals of object-oriented programming, you might think that of course there will be some sort of object-oriented approach to handling events. For example, you might have to inherit each button and override some “button pressed” method (this, it turns out, is too tedious and restrictive). You might also think there’s some master “event” class that contains a method for each event you want to respond to.

Before objects, the typical approach to handling events was the “giant switch statement.” Each event would have a unique integer value and inside the master event handling method you’d write a switch on that value.

The AWT in Java 1.0 doesn’t use any object-oriented approach. Neither does it use a giant switch statement that relies on the assignment of numbers to events. Instead, you must create a cascaded set of if statements. What you’re trying to do with the if statements is detect the object that was the target of the event. That is, if you click on a button, then that particular button is the target. Normally, that’s all you care about – if a button is the target of an event, then it was most certainly a mouse click and you can continue based on that assumption. However, events can contain other information as well. For example, if you want to find out the pixel location where a mouse click occurred so you can draw a line to that location, the Event object will contain the location. (You should also be aware that Java 1.0 components can be limited in the kinds of events they generate, while Java 1.1 and Swing/JFC components produce a full set of events.)

The Java 1.0 AWT method where your cascaded if statement resides is called action( ). Although the whole Java 1.0 Event model has been deprecated in Java 1.1, it is still widely used for simple applets and in systems that do not yet support Java 1.1, so I recommend you become comfortable with it, including the use of the following action() method approach.

action( ) has two arguments: the first is of type Event and contains all the information about the event that triggered this call to action( ). For example, it could be a mouse click, a normal keyboard press or release, a special key press or release, the fact that the component got or lost the focus, mouse movements, or drags, etc. The second argument is usually the target of the event, which you’ll often ignore. The second argument is also encapsulated in the Event object so it is redundant as an argument.

The situations in which action( ) gets called are extremely limited: When you place controls on a form, some types of controls (buttons, check boxes, drop-down lists, menus) have a “standard action” that occurs, which causes the call to action( ) with the appropriate Event object. For example, with a button the action( ) method is called when the button is pressed and at no other time. Usually this is just fine, since that’s what you ordinarily look for with a button. However, it’s possible to deal with many other types of events via the handleEvent( ) method as we will see later in this chapter.

The previous example can be extended to handle button clicks as follows:

//: Button2.java
// Capturing button presses
import java.awt.*;
import java.applet.*;

public class Button2 extends Applet {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public void init() {
    add(b1);
    add(b2);
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b1))
      getAppletContext().showStatus("Button 1");
    else if(evt.target.equals(b2))
      getAppletContext().showStatus("Button 2");
    // Let the base class handle it:
    else 
      return super.action(evt, arg);
    return true; // We've handled it here
  }
} ///:~

To see what the target is, ask the Event object what its target member is and then use the equals( ) method to see if it matches the target object handle you’re interested in. When you’ve written handlers for all the objects you’re interested in you must call super.action(evt, arg) in the else statement at the end, as shown above. Remember from Chapter 7 (polymorphism) that your overridden method is called instead of the base class version. However, the base-class version contains code to handle all of the cases that you’re not interested in, and it won’t get called unless you call it explicitly. The return value indicates whether you’ve handled it or not, so if you do match an event you should return true, otherwise return whatever the base-class event( ) returns.

For this example, the simplest action is to print what button is pressed. Some systems allow you to pop up a little window with a message in it, but applets discourage this. However, you can put a message at the bottom of the Web browser window on its status line by calling the Applet method getAppletContext( ) to get access to the browser and then showStatus( ) to put a string on the status line.[53] You can print out a complete description of an event the same way, with getAppletContext().showStatus(evt + "" ). (The empty String forces the compiler to convert evt to a String.) Both of these reports are really useful only for testing and debugging since the browser might overwrite your message.

Strange as it might seem, you can also match an event to the text that’s on a button through the second argument in event( ). Using this technique, the example above becomes:

//: Button3.java
// Matching events on button text
import java.awt.*;
import java.applet.*;

public class Button3 extends Applet {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public void init() {
    add(b1);
    add(b2);
  }
  public boolean action (Event evt, Object arg) {
    if(arg.equals("Button 1"))
      getAppletContext().showStatus("Button 1");
    else if(arg.equals("Button 2"))
      getAppletContext().showStatus("Button 2");
    // Let the base class handle it:
    else 
      return super.action(evt, arg);
    return true; // We've handled it here
  }
} ///:~

It’s difficult to know exactly what the equals( ) method is doing here. The biggest problem with this approach is that most new Java programmers who start with this technique spend at least one frustrating session discovering that they’ve gotten the capitalization or spelling wrong when comparing to the text on a button. (I had this experience.) Also, if you change the text of the button, the code will no longer work (but you won’t get any compile-time or run-time error messages). You should avoid this approach if possible.

Text fields

A TextField is a one line area that allows the user to enter and edit text. TextField is inherited from TextComponent, which lets you select text, get the selected text as a String, get or set the text, and set whether the TextField is editable, along with other associated methods that you can find in your online reference. The following example demonstrates some of the functionality of a TextField; you can see that the method names are fairly obvious:

//: TextField1.java
// Using the text field control
import java.awt.*;
import java.applet.*;

public class TextField1 extends Applet {
  Button
    b1 = new Button("Get Text"),
    b2 = new Button("Set Text");
  TextField 
    t = new TextField("Starting text", 30);
  String s = new String();
  public void init() {
    add(b1);
    add(b2);
    add(t);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(b1)) {
      getAppletContext().showStatus(t.getText());
      s = t.getSelectedText();
      if(s.length() == 0) s = t.getText();
      t.setEditable(true);
    }
    else if(evt.target.equals(b2)) {
      t.setText("Inserted by Button 2: " + s);
      t.setEditable(false);
    }
    // Let the base class handle it:
    else 
      return super.action(evt, arg);
    return true; // We've handled it here
  }
} ///:~

There are several ways to construct a TextField; the one shown here provides an initial string and sets the size of the field in characters.

Pressing button 1 either gets the text you’ve selected with the mouse or it gets all the text in the field and places the result in String s. It also allows the field to be edited. Pressing button 2 puts a message and s into the text field and prevents the field from being edited (although you can still select the text). The editability of the text is controlled by passing setEditable( ) a true or false.

Text areas

A TextArea is like a TextField except that it can have multiple lines and has significantly more functionality. In addition to what you can do with a TextField, you can append text and insert or replace text at a given location. It seems like this functionality could be useful for TextField as well, so it’s a little confusing to try to detect how the distinction is made. You might think that if you want TextArea functionality everywhere you can simply use a one line TextArea in places where you would otherwise use a TextField. In Java 1.0, you also got scroll bars with a TextArea even when they weren’t appropriate; that is, you got both vertical and horizontal scroll bars for a one line TextArea. In Java 1.1 this was remedied with an extra constructor that allows you to select which scroll bars (if any) are present. The following example shows only the Java 1.0 behavior, in which the scrollbars are always on. Later in the chapter you’ll see an example that demonstrates Java 1.1 TextAreas.

//: TextArea1.java
// Using the text area control
import java.awt.*;
import java.applet.*;

public class TextArea1 extends Applet {
  Button b1 = new Button("Text Area 1");
  Button b2 = new Button("Text Area 2");
  Button b3 = new Button("Replace Text");
  Button b4 = new Button("Insert Text");
  TextArea t1 = new TextArea("t1", 1, 30);
  TextArea t2 = new TextArea("t2", 4, 30);
  public void init() {
    add(b1);
    add(t1);
    add(b2);
    add(t2);
    add(b3);
    add(b4);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(b1))
      getAppletContext().showStatus(t1.getText());
    else if(evt.target.equals(b2)) {
      t2.setText("Inserted by Button 2");
      t2.appendText(": " + t1.getText());
      getAppletContext().showStatus(t2.getText());
    }
    else if(evt.target.equals(b3)) {
      String s = " Replacement ";
      t2.replaceText(s, 3, 3 + s.length());
    }
    else if(evt.target.equals(b4))
      t2.insertText(" Inserted ", 10);
    // Let the base class handle it:
    else 
      return super.action(evt, arg);
    return true; // We've handled it here
  }
} ///:~

There are several different TextArea constructors, but the one shown here gives a starting string and the number of rows and columns. The different buttons show getting, appending, replacing, and inserting text.

Labels

A Label does exactly what it sounds like it should: places a label on the form. This is particularly important for text fields and text areas that don’t have labels of their own, and can also be useful if you simply want to place textual information on a form. You can, as shown in the first example in this chapter, use drawString( ) inside paint( ) to place text in an exact location. When you use a Label it allows you to (approximately) associate the text with some other component via the layout manager (which will be discussed later in this chapter).

With the constructor you can create a blank label, a label with initial text in it (which is what you’ll typically do), and a label with an alignment of CENTER, LEFT, or RIGHT (static final ints defined in class Label). You can also change the label and its alignment with setText( ) and setAlignment( ), and if you’ve forgotten what you’ve set these to you can read the values with getText( ) and getAlignment( ). This example shows what you can do with labels:

//: Label1.java
// Using labels
import java.awt.*;
import java.applet.*;

public class Label1 extends Applet {
  TextField t1 = new TextField("t1", 10);
  Label labl1 = new Label("TextField t1");
  Label labl2 = new Label("                   ");
  Label labl3 = new Label("                    ",
    Label.RIGHT);
  Button b1 = new Button("Test 1");
  Button b2 = new Button("Test 2");
  public void init() {
    add(labl1); add(t1);
    add(b1); add(labl2);
    add(b2); add(labl3);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(b1))
      labl2.setText("Text set into Label");
    else if(evt.target.equals(b2)) {
      if(labl3.getText().trim().length() == 0)
        labl3.setText("labl3");
      if(labl3.getAlignment() == Label.LEFT)
        labl3.setAlignment(Label.CENTER);
      else if(labl3.getAlignment()==Label.CENTER)
        labl3.setAlignment(Label.RIGHT);
      else if(labl3.getAlignment() == Label.RIGHT)
        labl3.setAlignment(Label.LEFT);
    }
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

The first use of the label is the most typical: labeling a TextField or TextArea. In the second part of the example, a bunch of empty spaces are reserved and when you press the “Test 1” button setText( ) is used to insert text into the field. Because a number of blank spaces do not equal the same number of characters (in a proportionally-spaced font) you’ll see that the text gets truncated when inserted into the label.

The third part of the example reserves empty space, then the first time you press the “Test 2” button it sees that there are no characters in the label (since trim( ) removes all of the blank spaces at each end of a String) and inserts a short label, which is initially left-aligned. The rest of the times you press the button it changes the alignment so you can see the effect.

You might think that you could create an empty label and then later put text in it with setText( ). However, you cannot put text into an empty label – presumably because it has zero width – so creating a label with no text seems to be a useless thing to do. In the example above, the “blank” label is filled with empty spaces so it has enough width to hold text that’s placed inside later.

Similarly, setAlignment( ) has no effect on a label that you’d typically create with text in the constructor. The label width is the width of the text, so changing the alignment doesn’t do anything. However, if you start with a long label and then change it to a shorter one you can see the effect of the alignment.

These behaviors occur because of the default layout manager that’s used for applets, which causes things to be squished together to their smallest size. Layout managers will be covered later in this chapter, when you’ll see that other layouts don’t have the same effect.

Check boxes

A check box provides a way to make a single on-off choice; it consists of a tiny box and a label. The box typically holds a little ‘x’ (or some other indication that it is set) or is empty depending on whether that item was selected.

You’ll normally create a Checkbox using a constructor that takes the label as an argument. You can get and set the state, and also get and set the label if you want to read or change it after the Checkbox has been created. Note that the capitalization of Checkbox is inconsistent with the other controls, which could catch you by surprise since you might expect it to be “CheckBox.”

Whenever a Checkbox is set or cleared an event occurs, which you can capture the same way you do a button. The following example uses a TextArea to enumerate all the check boxes that have been checked:

//: CheckBox1.java
// Using check boxes
import java.awt.*;
import java.applet.*;

public class CheckBox1 extends Applet {
  TextArea t = new TextArea(6, 20);
  Checkbox cb1 = new Checkbox("Check Box 1");
  Checkbox cb2 = new Checkbox("Check Box 2");
  Checkbox cb3 = new Checkbox("Check Box 3");
  public void init() {
    add(t); add(cb1); add(cb2); add(cb3);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(cb1))
      trace("1", cb1.getState());
    else if(evt.target.equals(cb2))
      trace("2", cb2.getState());
    else if(evt.target.equals(cb3))
      trace("3", cb3.getState());
    else 
      return super.action(evt, arg);
    return true;
  }
  void trace(String b, boolean state) {
    if(state)
      t.appendText("Box " + b + " Set\n");
    else
      t.appendText("Box " + b + " Cleared\n");
  }
} ///:~

The trace( ) method sends the name of the selected Checkbox and its current state to the TextArea using appendText( ) so you’ll see a cumulative list of the checkboxes that were selected and what their state is.

Radio buttons

The concept of a radio button in GUI programming comes from pre-electronic car radios with mechanical buttons: when you push one in, any other button that was pressed pops out. Thus it allows you to force a single choice among many.

The AWT does not have a separate class to represent the radio button; instead it reuses the Checkbox. However, to put the Checkbox in a radio button group (and to change its shape so it’s visually different from an ordinary Checkbox) you must use a special constructor that takes a CheckboxGroup object as an argument. (You can also call setCheckboxGroup( ) after the Checkbox has been created.)

A CheckboxGroup has no constructor argument; its sole reason for existence is to collect some Checkboxes into a group of radio buttons. One of the Checkbox objects must have its state set to true before you try to display the group of radio buttons; otherwise you’ll get an exception at run time. If you try to set more than one radio button to true then only the final one set will be true.

Here’s a simple example of the use of radio buttons. Note that you capture radio button events like all others:

//: RadioButton1.java
// Using radio buttons
import java.awt.*;
import java.applet.*;

public class RadioButton1 extends Applet {
  TextField t = 
    new TextField("Radio button 2", 30);
  CheckboxGroup g = new CheckboxGroup();
  Checkbox 
    cb1 = new Checkbox("one", g, false),
    cb2 = new Checkbox("two", g, true),
    cb3 = new Checkbox("three", g, false);
  public void init() {
    t.setEditable(false);
    add(t); 
    add(cb1); add(cb2); add(cb3); 
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(cb1))
      t.setText("Radio button 1");
    else if(evt.target.equals(cb2))
      t.setText("Radio button 2");
    else if(evt.target.equals(cb3))
      t.setText("Radio button 3");
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

To display the state, an text field is used. This field is set to non-editable because it’s used only to display data, not to collect it. This is shown as an alternative to using a Label. Notice the text in the field is initialized to “Radio button 2” since that’s the initial selected radio button.

You can have any number of CheckboxGroups on a form.

Drop-down lists

Like a group of radio buttons, a drop-down list is a way to force the user to select only one element from a group of possibilities. However, it’s a much more compact way to accomplish this, and it’s easier to change the elements of the list without surprising the user. (You can change radio buttons dynamically, but that tends to be visibly jarring).

Java’s Choice box is not like the combo box in Windows, which lets you select from a list or type in your own selection. With a Choice box you choose one and only one element from the list. In the following example, the Choice box starts with a certain number of entries and then new entries are added to the box when a button is pressed. This allows you to see some interesting behaviors in Choice boxes:

//: Choice1.java
// Using drop-down lists
import java.awt.*;
import java.applet.*;

public class Choice1 extends Applet {
  String[] description = { "Ebullient", "Obtuse",
    "Recalcitrant", "Brilliant", "Somnescent",
    "Timorous", "Florid", "Putrescent" };
  TextField t = new TextField(30);
  Choice c = new Choice();
  Button b = new Button("Add items");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      c.addItem(description[count++]);
    add(t);
    add(c);
    add(b);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(c))
      t.setText("index: " +  c.getSelectedIndex()
        + "   " + (String)arg);
    else if(evt.target.equals(b)) {
      if(count < description.length)
        c.addItem(description[count++]);
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

The TextField displays the “selected index,” which is the sequence number of the currently selected element, as well as the String representation of the second argument of action( ), which is in this case the string that was selected.

When you run this applet, pay attention to the determination of the size of the Choice box: in Windows, the size is fixed from the first time you drop down the list. This means that if you drop down the list, then add more elements to the list, the elements will be there but the drop-down list won’t get any longer[54] (you can scroll through the elements). However, if you add all the elements before the first time the list is dropped down, then it will be sized correctly. Of course, the user will expect to see the whole list when it’s dropped down, so this behavior puts some significant limitations on adding elements to Choice boxes.

List boxes

List boxes are significantly different from Choice boxes, and not just in appearance. While a Choice box drops down when you activate it, a List occupies some fixed number of lines on a screen all the time and doesn’t change. In addition, a List allows multiple selection: if you click on more than one item the original item stays highlighted and you can select as many as you want. If you want to see the items in a list, you simply call getSelectedItems( ), which produces an array of String of the items that have been selected. To remove an item from a group you have to click it again.

A problem with a List is that the default action is double clicking, not single clicking. A single click adds or removes elements from the selected group and a double click calls action( ). One way around this is to re-educate your user, which is the assumption made in the following program:

//: List1.java
// Using lists with action()
import java.awt.*;
import java.applet.*;

public class List1 extends Applet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  // Show 6 items, allow multiple selection:
  List lst = new List(6, true);
  TextArea t = new TextArea(flavors.length, 30);
  Button b = new Button("test");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      lst.addItem(flavors[count++]);
    add(t);
    add(lst);
    add(b);
  }
  public boolean action (Event evt, Object arg) {
    if(evt.target.equals(lst)) {
      t.setText("");
      String[] items = lst.getSelectedItems();
      for(int i = 0; i < items.length; i++)
        t.appendText(items[i] + "\n");
    }
    else if(evt.target.equals(b)) {
      if(count < flavors.length)
        lst.addItem(flavors[count++], 0);
    }
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

When you press the button it adds items to the top of the list (because of the second argument 0 to addItem( )). Adding elements to a List is more reasonable than the Choice box because users expect to scroll a list box (for one thing, it has a built-in scroll bar) but they don’t expect to have to figure out how to get a drop-down list to scroll, as in the previous example.

However, the only way for action( ) to be called is through a double-click. If you need to monitor other activities that the user is doing on your List (in particular, single clicks) you must take an alternative approach.

handleEvent( )

So far we’ve been using action( ), but there’s another method that gets first crack at everything: handleEvent( ). Any time an event happens, it happens “over” or “to” a particular object. The handleEvent( ) method for that object is automatically called and an Event object is created and passed to handleEvent( ). The default handleEvent( ) (which is defined in Component, the base class for virtually all the “controls” in the AWT) will call either action( ), as we’ve been using, or other similar methods to indicate mouse activity, keyboard activity, or to indicate that the focus has moved. We’ll look at those later in this chapter.

What if these other methods – action( ) in particular – don’t satisfy your needs? In the case of List, for example, what if you want to catch single mouse clicks but action( ) responds to only double clicks? The solution is to override handleEvent( ) for your applet, which after all is derived from Applet and can therefore override any non-final methods. When you override handleEvent( ) for the applet you’re getting all the applet events before they are routed, so you cannot just assume “This has to do with my button so I can assume it’s been pressed,” since that’s true only for action( ). Inside handleEvent( ) it’s possible that the button has the focus and someone is typing to it. Whether it makes sense or not, those are events that you can detect and act upon in handleEvent( ).

To modify the List example so that it will react to single mouse clicks, the button detection will be left in action( ) but the code to handle the List will be moved into handleEvent( ) as follows:

//: List2.java
// Using lists with handleEvent()
import java.awt.*;
import java.applet.*;

public class List2 extends Applet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  // Show 6 items, allow multiple selection:
  List lst = new List(6, true);
  TextArea t = new TextArea(flavors.length, 30);
  Button b = new Button("test");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      lst.addItem(flavors[count++]);
    add(t);
    add(lst);
    add(b);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.LIST_SELECT ||
       evt.id == Event.LIST_DESELECT) {
      if(evt.target.equals(lst)) {
        t.setText("");
        String[] items = lst.getSelectedItems();
        for(int i = 0; i < items.length; i++)
          t.appendText(items[i] + "\n");
      }
      else 
        return super.handleEvent(evt);
    } 
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b)) {
      if(count < flavors.length)
        lst.addItem(flavors[count++], 0);
    }
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

The example is the same as before except for the addition of handleEvent( ). Inside, a check is made to see whether a list selection or deselection has occurred. Now remember, handleEvent( ) is being overridden for the applet, so this occurrence could be anywhere on the form and it could be happening to another list. Thus, you must also check to see what the target is. (Although in this case there’s only one list on the applet so we could have made the assumption that all list events must be about that list. This is bad practice since it’s going to be a problem as soon as another list is added.) If the list matches the one we’re interested in, the same code as before will do the trick.

Note that the form for handleEvent( ) is similar to action( ): if you deal with a particular event you return true, but if you’re not interested in any of the other events via handleEvent( ) you must return super.handleEvent(evt). This is vital because if you don’t do this, none of the other event-handling code will get called. For example, try commenting out the return super.handleEvent(evt) in the code above. You’ll discover that action( ) never gets called, certainly not what you want. For both action( ) and handleEvent( ) it’s important to follow the format above and always return the base-class version of the method when you do not handle the event yourself (in which case you should return true). (Fortunately, these kinds of bug-prone details are relegated to Java 1.0. The new design in Java 1.1 that you will see later in the chapter eliminates these kinds of issues.)

In Windows, a list box automatically allows multiple selections if you hold down the shift key. This is nice because it allows the user to choose a single or multiple selection rather than fixing it during programming. You might think you’ll be clever and implement this yourself by checking to see if the shift key is held down when a mouse click was made by testing for evt.shiftDown( ). Alas, the design of the AWT stymies you – you’d have to be able to know which item was clicked on if the shift key wasn’t pressed so you could deselect all the rest and select only that one. However, you cannot figure that out in Java 1.0. (Java 1.1 sends all mouse, keyboard, and focus events to a List, so you’ll be able to accomplish this.)

Controlling layout

The way that you place components on a form in Java is probably different from any other GUI system you’ve used. First, it’s all code; there are no “resources” that control placement of components. Second, the way components are placed on a form is controlled by a “layout manager” that decides how the components lie based on the order that you add( ) them. The size, shape, and placement of components will be remarkably different from one layout manager to another. In addition, the layout managers adapt to the dimensions of your applet or application window, so if that window dimension is changed (for example, in the HTML page’s applet specification) the size, shape, and placement of the components could change.

Both the Applet and Frame classes are derived from Container, whose job it is to contain and display Components. (The Container is a Component so it can also react to events.) In Container, there’s a method called setLayout( ) that allows you to choose a different layout manager.

In this section we’ll explore the various layout managers by placing buttons in them (since that’s the simplest thing to do). There won’t be any capturing of button events since this is just intended to show how the buttons are laid out.

FlowLayout

So far, all the applets that have been created seem to have laid out their components using some mysterious internal logic. That’s because the applet uses a default layout scheme: the FlowLayout. This simply “flows” the components onto the form, from left to right until the top space is full, then moves down a row and continues flowing the components.

Here’s an example that explicitly (redundantly) sets the layout manager in an applet to FlowLayout and then places buttons on the form. You’ll notice that with FlowLayout the components take on their “natural” size. A Button, for example, will be the size of its string.

//: FlowLayout1.java
// Demonstrating the FlowLayout
import java.awt.*;
import java.applet.*;

public class FlowLayout1 extends Applet {
  public void init() {
    setLayout(new FlowLayout());
    for(int i = 0; i < 20; i++)
      add(new Button("Button " + i));
  }
} ///:~

All components will be compacted to their smallest size in a FlowLayout, so you might get a little bit of surprising behavior. For example, a label will be the size of its string, so right-justifying it yields an unchanged display.

BorderLayout

This layout manager has the concept of four border regions and a center area. When you add something to a panel that’s using a BorderLayout you must use an add( ) method that takes a String object as its first argument, and that string must specify (with proper capitalization) “North” (top), “South” (bottom), “East” (right), “West” (left), or “Center.” If you misspell or mis-capitalize, you won’t get a compile-time error, but the applet simply won’t do what you expect. Fortunately, as you will see shortly, there’s a much-improved approach in Java 1.1.

Here’s a simple example:

//: BorderLayout1.java
// Demonstrating the BorderLayout
import java.awt.*;
import java.applet.*;

public class BorderLayout1 extends Applet {
  public void init() {
    int i = 0;
    setLayout(new BorderLayout());
    add("North", new Button("Button " + i++));
    add("South", new Button("Button " + i++));
    add("East", new Button("Button " + i++));
    add("West", new Button("Button " + i++));
    add("Center", new Button("Button " + i++));
  }
} ///:~

For every placement but “Center,” the element that you add is compressed to fit in the smallest amount of space along one dimension while it is stretched to the maximum along the other dimension. “Center,” however, spreads out along both dimensions to occupy the middle.

The BorderLayout is the default layout manager for applications and dialogs.

GridLayout

A GridLayout allows you to build a table of components, and as you add them they are placed left-to-right and top-to-bottom in the grid. In the constructor you specify the number of rows and columns that you need and these are laid out in equal proportions.

//: GridLayout1.java
// Demonstrating the GridLayout
import java.awt.*;
import java.applet.*;

public class GridLayout1 extends Applet {
  public void init() {
    setLayout(new GridLayout(7,3));
    for(int i = 0; i < 20; i++)
      add(new Button("Button " + i));
  }
} ///:~

In this case there are 21 slots but only 20 buttons. The last slot is left empty; no “balancing” goes on with a GridLayout.

CardLayout

The CardLayout allows you to create the rough equivalent of a “tabbed dialog,” which in more sophisticated environments has actual file-folder tabs running across one edge, and all you have to do is press a tab to bring forward a different dialog. Not so in the AWT: The CardLayout is simply a blank space and you’re responsible for bringing forward new cards. (The JFC/Swing library contains tabbed panes that look much better and take care of all the details for you.)

Combining layouts

This example will combine more than one layout type, which seems rather difficult at first since only one layout manager can be operating for an applet or application. This is true, but if you create more Panel objects, each one of those Panels can have its own layout manager and then be integrated into the applet or application as simply another component, using the applet or application’s layout manager. This gives you much greater flexibility as seen in the following example:

//: CardLayout1.java
// Demonstrating the CardLayout
import java.awt.*;
import java.applet.Applet;

class ButtonPanel extends Panel {
  ButtonPanel(String id) {
    setLayout(new BorderLayout());
    add("Center", new Button(id));
  }
}

public class CardLayout1 extends Applet {
  Button
    first = new Button("First"),
    second = new Button("Second"),
    third = new Button("Third");
  Panel cards = new Panel();
  CardLayout cl = new CardLayout();
  public void init() {
    setLayout(new BorderLayout());
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    p.add(first);
    p.add(second);
    p.add(third);
    add("North", p);
    cards.setLayout(cl);
    cards.add("First card", 
      new ButtonPanel("The first one"));
    cards.add("Second card", 
      new ButtonPanel("The second one"));
    cards.add("Third card", 
      new ButtonPanel("The third one"));
    add("Center", cards);
  }
  public boolean action(Event evt, Object arg) {
    if (evt.target.equals(first)) {
      cl.first(cards);
    } 
    else if (evt.target.equals(second)) {
      cl.first(cards);
      cl.next(cards);
    } 
    else if (evt.target.equals(third)) {
      cl.last(cards);
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
} ///:~

This example begins by creating a new kind of Panel: a ButtonPanel. This contains a single button, placed at the center of a BorderLayout, which means that it will expand to fill the entire panel. The label on the button will let you know which panel you’re on in the CardLayout.

In the applet, both the Panel cards where the cards will live and the layout manager cl for the CardLayout must be members of the class because you need to have access to those handles when you want to manipulate the cards.

The applet is changed to use a BorderLayout instead of its default FlowLayout, a Panel is created to hold three buttons (using a FlowLayout), and this panel is placed at the “North” end of the applet. The cards panel is added to the “Center” of the applet, effectively occupying the rest of the real estate.

When you add the ButtonPanels (or whatever other components you want) to the panel of cards, the add( ) method’s first argument is not “North,” “South,” etc. Instead, it’s a string that describes the card. Although this string doesn’t show up anywhere on the card, you can use it if you want to flip that card using the string. This approach is not used in action( ); instead the first( ), next( ), and last( ) methods are used. Check your documentation for the other approach.

In Java, the use of some sort of “tabbed panel” mechanism is quite important because (as you’ll see later) in applet programming the use of pop-up dialogs is heavily discouraged. For Java 1.0 applets, the CardLayout is the only viable way for the applet to have a number of different forms that “pop up” on command.

GridBagLayout

Some time ago, it was believed that all the stars, planets, the sun, and the moon revolved around the earth. It seemed intuitive from observation. But then astronomers became more sophisticated and started tracking the motion of individual objects, some of which seemed at times to go backward in their paths. Since it was known that everything revolved around the earth, those astronomers spent large amounts of time coming up with equations and theories to explain the motion of the stellar objects.

When trying to work with GridBagLayout, you can consider yourself the analog of one of those early astronomers. The basic precept (decreed, interestingly enough, by the designers at “Sun”) is that everything should be done in code. The Copernican revolution (again dripping with irony, the discovery that the planets in the solar system revolve around the sun) is the use of resources to determine the layout and make the programmer’s job easy. Until these are added to Java, you’re stuck (to continue the metaphor) in the Spanish Inquisition of GridBagLayout and GridBagConstraints.

My recommendation is to avoid GridBagLayout. Instead, use the other layout managers and especially the technique of combining several panels using different layout managers within a single program. Your applets won’t look that different; at least not enough to justify the trouble that GridBagLayout entails. For my part, it’s just too painful to come up with an example for this (and I wouldn’t want to encourage this kind of library design). Instead, I’ll refer you to Core Java by Cornell & Horstmann (2nd ed., Prentice-Hall, 1997) to get started.

Alternatives to action

As noted previously, action( ) isn’t the only method that’s automatically called by handleEvent( ) once it sorts everything out for you. There are three other sets of methods that are called, and if you want to capture certain types of events (keyboard, mouse, and focus events) all you have to do is override the provided method. These methods are defined in the base class Component, so they’re available in virtually all the controls that you might place on a form. However, you should be aware that this approach is deprecated in Java 1.1, so although you might see legacy code using this technique you should use the Java 1.1 approaches (described later in this chapter) instead.

Component method

When it’s called

action (Event evt, Object what)

When the “typical” event occurs for this component (for example, when a button is pushed or a drop-down list item is selected)

keyDown (Event evt, int key)

A key is pressed when this component has the focus. The second argument is the key that was pressed and is redundantly copied from evt.key.

keyUp(Event evt, int key)

A key is released when this component has the focus.

lostFocus(Event evt, Object what)

The focus has moved away from the target. Normally, what is redundantly copied from evt.arg.

gotFocus(Event evt, Object what)

The focus has moved into the target.

mouseDown(Event evt,
int x, int y)

A mouse down has occurred over the component, at the coordinates x, y.

mouseUp(Event evt, int x, int y)

A mouse up has occurred over the component.

mouseMove(Event evt, int x, int y)

The mouse has moved while it’s over the component.

mouseDrag(Event evt, int x, int y)

The mouse is being dragged after a mouseDown occurred over the component. All drag events are reported to the component in which the mouseDown occurred until there is a mouseUp.

mouseEnter(Event evt, int x, int y)

The mouse wasn’t over the component before, but now it is.

mouseExit(Event evt, int x, int y)

The mouse used to be over the component, but now it isn’t.

You can see that each method receives an Event object along with some information that you’ll typically need when you’re handling that particular situation – with a mouse event, for example, it’s likely that you’ll want to know the coordinates where the mouse event occurred. It’s interesting to note that when Component’s handleEvent( ) calls any of these methods (the typical case), the extra arguments are always redundant as they are contained within the Event object. In fact, if you look at the source code for Component.handleEvent( ) you can see that it explicitly plucks the additional arguments out of the Event object. (This might be considered inefficient coding in some languages, but remember that Java’s focus is on safety, not necessarily speed.)

To prove to yourself that these events are in fact being called and as an interesting experiment, it’s worth creating an applet that overrides each of the methods above (except for action( ), which is overridden in many other places in this chapter) and displays data about each of the events as they happen.

This example also shows you how to make your own button object because that’s what is used as the target of all the events of interest. You might first (naturally) assume that to make a new button, you’d inherit from Button. But this doesn’t work. Instead, you inherit from Canvas (a much more generic component) and paint your button on that canvas by overriding the paint( ) method. As you’ll see, it’s really too bad that overriding Button doesn’t work, since there’s a bit of code involved to paint the button. (If you don’t believe me, try exchanging Button for Canvas in this example, and remember to call the base-class constructor super(label). You’ll see that the button doesn’t get painted and the events don’t get handled.)

The myButton class is specific: it works only with an AutoEvent “parent window” (not a base class, but the window in which this button is created and lives). With this knowledge, myButton can reach into the parent window and manipulate its text fields, which is what’s necessary to be able to write the status information into the fields of the parent. Of course this is a much more limited solution, since myButton can be used only in conjunction with AutoEvent. This kind of code is sometimes called “highly coupled.” However, to make myButton more generic requires a lot more effort that isn’t warranted for this example (and possibly for many of the applets that you will write). Again, keep in mind that the following code uses APIs that are deprecated in Java 1.1.

//: AutoEvent.java
// Alternatives to action()
import java.awt.*;
import java.applet.*;
import java.util.*;

class MyButton extends Canvas {
  AutoEvent parent;
  Color color;
  String label;
  MyButton(AutoEvent parent, 
           Color color, String label) {
    this.label = label;
    this.parent = parent;
    this.color = color;
  }
  public void paint(Graphics  g) {
    g.setColor(color);
    int rnd = 30;
    g.fillRoundRect(0, 0, size().width, 
                    size().height, rnd, rnd);
    g.setColor(Color.black);
    g.drawRoundRect(0, 0, size().width, 
                    size().height, rnd, rnd);
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin = (size().width - width)/2;
    int verMargin = (size().height - height)/2;
    g.setColor(Color.white);
    g.drawString(label, horizMargin, 
                 verMargin + ascent + leading);
  }
  public boolean keyDown(Event evt, int key) {
    TextField t = 
      (TextField)parent.h.get("keyDown");
    t.setText(evt.toString());
    return true;
  }
  public boolean keyUp(Event evt, int key) {
    TextField t = 
      (TextField)parent.h.get("keyUp");
    t.setText(evt.toString());
    return true;
  }
  public boolean lostFocus(Event evt, Object w) {
    TextField t = 
      (TextField)parent.h.get("lostFocus");
    t.setText(evt.toString());
    return true;
  }
  public boolean gotFocus(Event evt, Object w) {
    TextField t = 
      (TextField)parent.h.get("gotFocus");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseDown(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseDown");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseDrag(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseDrag");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseEnter(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseEnter");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseExit(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseExit");
    t.setText(evt.toString());
    return true;
  }
  public boolean 
  mouseMove(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseMove");
    t.setText(evt.toString());
    return true;
  }
  public boolean mouseUp(Event evt,int x,int y) {
    TextField t = 
      (TextField)parent.h.get("mouseUp");
    t.setText(evt.toString());
    return true;
  }
}

public class AutoEvent extends Applet {
  Hashtable h = new Hashtable();
  String[] event = {
    "keyDown", "keyUp", "lostFocus", 
    "gotFocus", "mouseDown", "mouseUp", 
    "mouseMove", "mouseDrag", "mouseEnter", 
    "mouseExit"
  };
  MyButton 
    b1 = new MyButton(this, Color.blue, "test1"),
    b2 = new MyButton(this, Color.red, "test2");
  public void init() {
    setLayout(new GridLayout(event.length+1,2));
    for(int i = 0; i < event.length; i++) {
      TextField t = new TextField();
      t.setEditable(false);
      add(new Label(event[i], Label.CENTER)); 
      add(t);
      h.put(event[i], t);
    }
    add(b1);
    add(b2);
  }
} ///:~

You can see the constructor uses the technique of using the same name for the argument as what it’s assigned to, and differentiating between the two using this:

this.label = label;

The paint( ) method starts out simple: it fills a “round rectangle” with the button’s color, and then draws a black line around it. Notice the use of size( ) to determine the width and height of the component (in pixels, of course). After this, paint( ) seems quite complicated because there’s a lot of calculation going on to figure out how to center the button’s label inside the button using the “font metrics.” You can get a pretty good idea of what’s going on by looking at the method call, and it turns out that this is pretty stock code, so you can just cut and paste it when you want to center a label inside any component.

You can’t understand exactly how the keyDown( ), keyUp( ), etc. methods work until you look down at the AutoEvent class. This contains a Hashtable to hold the strings representing the type of event and the TextField where information about that event is held. Of course, these could have been created statically rather than putting them in a Hashtable, but I think you’ll agree that it’s a lot easier to use and change. In particular, if you need to add or remove a new type of event in AutoEvent, you simply add or remove a string in the event array – everything else happens automatically.

The place where you look up the strings is in the keyDown( ), keyUp( ), etc. methods back in MyButton. Each of these methods uses the parent handle to reach back to the parent window. Since that parent is an AutoEvent it contains the Hashtable h, and the get( ) method, when provided with the appropriate String, will produce a handle to an Object that we happen to know is a TextField – so it is cast to that. Then the Event object is converted to its String representation, which is displayed in the TextField.

It turns out this example is rather fun to play with since you can really see what’s going on with the events in your program.

Applet restrictions

For safety’s sake, applets are quite restricted and there are many things you can’t do. You can generally answer the question of what an applet is able to do by looking at what it is supposed to do: extend the functionality of a Web page in a browser. Since, as a net surfer, you never really know if a Web page is from a friendly place or not, you want any code that it runs to be safe. So the biggest restrictions you’ll notice are probably:

1) An applet can’t touch the local disk. This means writing or reading, since you wouldn’t want an applet to read and transmit important information about you across the Web. Writing is prevented, of course, since that would be an open invitation to a virus. These restrictions can be relaxed when digital signing is fully implemented.

2) An applet can’t have menus. (Note: this is fixed in Swing) This is probably less oriented toward safety and more toward reducing confusion. You might have noticed that an applet looks like it blends right in as part of a Web page; you often don’t see the boundaries of the applet. There’s no frame or title bar to hang the menu from, other than the one belonging to the Web browser. Perhaps the design could be changed to allow you to merge your applet menu with the browser menu – that would be complicated and would also get a bit too close to the edge of safety by allowing the applet to affect its environment.

3) Dialog boxes are “untrusted.” In Java, dialog boxes present a bit of a quandary. First of all, they’re not exactly disallowed in applets but they’re heavily discouraged. If you pop up a dialog box from within an applet you’ll get an “untrusted applet” message attached to that dialog. This is because, in theory, it would be possible to fool the user into thinking that they’re dealing with a regular native application and to get them to type in their credit card number, which then goes across the Web. After seeing the kinds of GUIs that the AWT produces you might have a hard time believing anybody could be fooled that way. But an applet is always attached to a Web page and visible within your Web browser, while a dialog box is detached so in theory it could be possible. As a result it will be rare to see an applet that uses a dialog box.

Many applet restrictions are relaxed for trusted applets (those signed by a trusted source) in newer browsers.

There are other issues when thinking about applet development:

Applet advantages

If you can live within the restrictions, applets have definite advantages, especially when building client/server or other networked applications:

Windowed applications

It’s possible to see that for safety’s sake you can have only limited behavior within an applet. In a real sense, the applet is a temporary extension to the Web browser so its functionality must be limited along with its knowledge and control. There are times, however, when you’d like to make a windowed program do something else than sit on a Web page, and perhaps you’d like it to do some of the things a “regular” application can do and yet have the vaunted instant portability provided by Java. In previous chapters in this book we’ve made command-line applications, but in some operating environments (the Macintosh, for example) there isn’t a command line. So for any number of reasons you’d like to build a windowed, non-applet program using Java. This is certainly a reasonable desire.

A Java windowed application can have menus and dialog boxes (impossible or difficult with an applet), and yet if you’re using an older version of Java you sacrifice the native operating environment’s look and feel. The JFC/Swing library allows you to make an application that preserves the look and feel of the underlying operating environment. If you want to build windowed applications, it makes sense to do so only if you can use the latest version of Java and associated tools so you can deliver applications that won’t confound your users. If for some reason you’re forced to use an older version of Java, think hard before committing to building a significant windowed application.

Menus

It’s impossible to put a menu directly on an applet (in Java 1.0 and Java 1.1; the Swing library does allow it), so they’re for applications. Go ahead, try it if you don’t believe me and you’re sure that it would make sense to have menus on applets. There’s no setMenuBar( ) method in Applet and that’s the way a menu is attached. (You’ll see later that it’s possible to spawn a Frame from within an Applet, and the Frame can contain menus.)

There are four different types of MenuComponent, all derived from that abstract class: MenuBar (you can have one MenuBar only on a particular Frame), Menu to hold one individual drop-down menu or submenu, MenuItem to represent one single element on a menu, and CheckboxMenuItem, which is derived from MenuItem and produces a checkmark to indicate whether that menu item is selected.

Unlike a system that uses resources, with Java and the AWT you must hand assemble all the menus in source code. Here are the ice cream flavors again, used to create menus:

//: Menu1.java
// Menus work only with Frames.
// Shows submenus, checkbox menu items
// and swapping menus.
import java.awt.*;

public class Menu1 extends Frame {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  TextField t = new TextField("No flavor", 30);
  MenuBar mb1 = new MenuBar();
  Menu f = new Menu("File");
  Menu m = new Menu("Flavors");
  Menu s = new Menu("Safety");
  // Alternative approach:
  CheckboxMenuItem[] safety = {
    new CheckboxMenuItem("Guard"),
    new CheckboxMenuItem("Hide")
  };
  MenuItem[] file = {
    new MenuItem("Open"),
    new MenuItem("Exit")
  };
  // A second menu bar to swap to:
  MenuBar mb2 = new MenuBar();
  Menu fooBar = new Menu("fooBar");
  MenuItem[] other = {
    new MenuItem("Foo"),
    new MenuItem("Bar"),
    new MenuItem("Baz"),
  };
  Button b = new Button("Swap Menus");
  public Menu1() {
    for(int i = 0; i < flavors.length; i++) {
      m.add(new MenuItem(flavors[i]));
      // Add separators at intervals:
      if((i+1) % 3 == 0) 
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++)
      s.add(safety[i]);
    f.add(s);
    for(int i = 0; i < file.length; i++)
      f.add(file[i]);
    mb1.add(f);
    mb1.add(m);
    setMenuBar(mb1);
    t.setEditable(false);
    add("Center", t);
    // Set up the system for swapping menus:
    add("North", b);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b)) {
      MenuBar m = getMenuBar();
      if(m == mb1) setMenuBar(mb2);
      else if (m == mb2) setMenuBar(mb1);
    } 
    else if(evt.target instanceof MenuItem) {
      if(arg.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i < flavors.length; i++)
          if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      }
      else if(evt.target.equals(file[1]))
        System.exit(0);
      // CheckboxMenuItems cannot use String 
      // matching; you must match the target:
      else if(evt.target.equals(safety[0]))
        t.setText("Guard the Ice Cream! " +
          "Guarding is " + safety[0].getState());
      else if(evt.target.equals(safety[1]))
        t.setText("Hide the Ice Cream! " +
          "Is it cold? " + safety[1].getState());
      else 
        t.setText(arg.toString());
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Menu1 f = new Menu1();
    f.resize(300,200);
    f.show();
  }
} ///:~

In this program I avoided the typical long lists of add( ) calls for each menu because that seemed like a lot of unnecessary typing. Instead, I placed the menu items into arrays and then simply stepped through each array calling add( ) in a for loop. This makes adding or subtracting a menu item less tedious.

As an alternative approach (which I find less desirable since it requires more typing), the CheckboxMenuItems are created in an array of handles called safety; this is true for the arrays file and other as well.

This program creates not one but two MenuBars to demonstrate that menu bars can be actively swapped while the program is running. You can see how a MenuBar is made up of Menus, and each Menu is made up of MenuItems, CheckboxMenuItems, or even other Menus (which produce submenus). When a MenuBar is assembled it can be installed into the current program with the setMenuBar( ) method. Note that when the button is pressed, it checks to see which menu is currently installed using getMenuBar( ), then puts the other menu bar in its place.

When testing for “Open,” notice that spelling and capitalization are critical, but Java signals no error if there is no match with “Open.” This kind of string comparison is a clear source of programming errors.

The checking and un-checking of the menu items is taken care of automatically, but dealing with CheckboxMenuItems can be a bit surprising since for some reason they don’t allow string matching. (Although string matching isn’t a good approach, this seems inconsistent.) So you can match only the target object and not its label. As shown, the getState( ) method can be used to reveal the state. You can also change the state of a CheckboxMenuItem with setState( ).

You might think that one menu could reasonably reside on more than one menu bar. This does seem to make sense because all you’re passing to the MenuBar add( ) method is a handle. However, if you try this, the behavior will be strange and not what you expect. (It’s difficult to know if this is a bug or if they intended it to work this way.)

This example also shows what you need to do to create an application instead of an applet. (Again, because an application can support menus and an applet cannot directly have a menu.) Instead of inheriting from Applet, you inherit from Frame. Instead of init( ) to set things up, you make a constructor for your class. Finally, you create a main( ) and in that you build an object of your new type, resize it, and then call show( ). It’s different from an applet in only a few small places, but it’s now a standalone windowed application and you’ve got menus.

Dialog boxes

A dialog box is a window that pops up out of another window. Its purpose is to deal with some specific issue without cluttering the original window with those details. Dialog boxes are heavily used in windowed programming environments, but as mentioned previously, rarely used in applets.

To create a dialog box, you inherit from Dialog, which is just another kind of Window, like a Frame. Unlike a Frame, a Dialog cannot have a menu bar or change the cursor, but other than that they’re quite similar. A dialog has a layout manager (which defaults to BorderLayout) and you override action( ) etc., or handleEvent( ) to deal with events. One significant difference you’ll want to note in handleEvent( ): when the WINDOW_DESTROY event occurs, you don’t want to shut down the application! Instead, you release the resources used by the dialog’s window by calling dispose( ).

In the following example, the dialog box is made up of a grid (using GridLayout) of a special kind of button that is defined here as class ToeButton. This button draws a frame around itself and, depending on its state, a blank, an “x,” or an “o” in the middle. It starts out blank, and then depending on whose turn it is, changes to an “x” or an “o.” However, it will also flip back and forth between “x” and “o” when you click on the button. (This makes the tic-tac-toe concept only slightly more annoying than it already is.) In addition, the dialog box can be set up for any number of rows and columns by changing numbers in the main application window.

//: ToeTest.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;

class ToeButton extends Canvas {
  int state = ToeDialog.BLANK;
  ToeDialog parent;
  ToeButton(ToeDialog parent) {
    this.parent = parent;
  }
  public void paint(Graphics  g) {
    int x1 = 0;
    int y1 = 0;
    int x2 = size().width - 1;
    int y2 = size().height - 1;
    g.drawRect(x1, y1, x2, y2);
    x1 = x2/4;
    y1 = y2/4;
    int wide = x2/2;
    int high = y2/2;
    if(state == ToeDialog.XX) {
      g.drawLine(x1, y1, x1 + wide, y1 + high);
      g.drawLine(x1, y1 + high, x1 + wide, y1);
    }
    if(state == ToeDialog.OO) {
      g.drawOval(x1, y1, x1+wide/2, y1+high/2);
    }
  }
  public boolean 
  mouseDown(Event evt, int x, int y) {
    if(state == ToeDialog.BLANK) {
      state = parent.turn;
      parent.turn= (parent.turn == ToeDialog.XX ?
        ToeDialog.OO : ToeDialog.XX);
    } 
    else
      state = (state == ToeDialog.XX ? 
        ToeDialog.OO : ToeDialog.XX);
    repaint();
    return true;
  }
}

class ToeDialog extends Dialog {
  // w = number of cells wide
  // h = number of cells high
  static final int BLANK = 0;
  static final int XX = 1;
  static final int OO = 2;
  int turn = XX; // Start with x's turn
  public ToeDialog(Frame parent, int w, int h) {
    super(parent, "The game itself", false);
    setLayout(new GridLayout(w, h));
    for(int i = 0; i < w * h; i++)
      add(new ToeButton(this));
    resize(w * 50, h * 50);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      dispose();
    else 
      return super.handleEvent(evt);
    return true;
  }
}

public class ToeTest extends Frame {
  TextField rows = new TextField("3");
  TextField cols = new TextField("3");
  public ToeTest() {
    setTitle("Toe Test");
    Panel p = new Panel();
    p.setLayout(new GridLayout(2,2));
    p.add(new Label("Rows", Label.CENTER));
    p.add(rows);
    p.add(new Label("Columns", Label.CENTER));
    p.add(cols);
    add("North", p);
    add("South", new Button("go"));
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(arg.equals("go")) {
      Dialog d = new ToeDialog(
        this, 
        Integer.parseInt(rows.getText()),
        Integer.parseInt(cols.getText()));
      d.show();
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Frame f = new ToeTest();
    f.resize(200,100);
    f.show();
  }
} ///:~

The ToeButton class keeps a handle to its parent, which must be of type ToeDialog. As before, this introduces high coupling because a ToeButton can be used only with a ToeDialog, but it solves a number of problems, and in truth it doesn’t seem like such a bad solution because there’s no other kind of dialog that’s keeping track of whose turn it is. Of course, you can take another approach, which is to make ToeDialog.turn a static member of ToeButton. This eliminates the coupling, but prevents you from having more than one ToeDialog at a time. (More than one that works properly, anyway.)

The paint( ) method is concerned with the graphics: drawing the square around the button and drawing the “x” or the “o.” This is full of tedious calculations, but it’s straightforward.

A mouse click is captured by the overridden mouseDown( ) method, which first checks to see if the button has anything written on it. If not, the parent window is queried to find out whose turn it is and that is used to establish the state of the button. Note that the button then reaches back into the parent and changes the turn. If the button is already displaying an “x” or an “o” then that is flopped. You can see in these calculations the convenient use of the ternary if-else described in Chapter 3. After a button state change, the button is repainted.

The constructor for ToeDialog is quite simple: it adds into a GridLayout as many buttons as you request, then resizes it for 50 pixels on a side for each button. (If you don’t resize a Window, it won’t show up!) Note that handleEvent( ) just calls dispose( ) for a WINDOW_DESTROY so the whole application doesn’t go away.

ToeTest sets up the whole application by creating the TextFields (for inputting the rows and columns of the button grid) and the “go” button. You’ll see in action( ) that this program uses the less-desirable “string match” technique for detecting the button press (make sure you get spelling and capitalization right!). When the button is pressed, the data in the TextFields must be fetched, and, since they are in String form, turned into ints using the static Integer.parseInt( ) method. Once the Dialog is created, the show( ) method must be called to display and activate it.

You’ll notice that the ToeDialog object is assigned to a Dialog handle d. This is an example of upcasting, although it really doesn’t make much difference here since all that’s happening is the show( ) method is called. However, if you wanted to call some method that existed only in ToeDialog you would want to assign to a ToeDialog handle and not lose the information in an upcast.

File dialogs

Some operating systems have a number of special built-in dialog boxes to handle the selection of things such as fonts, colors, printers, and the like. Virtually all graphical operating systems support the opening and saving of files, however, and so Java’s FileDialog encapsulates these for easy use. This, of course, makes no sense at all to use from an applet since an applet can neither read nor write files on the local disk. (This will change for trusted applets in newer browsers.)

The following application exercises the two forms of file dialogs, one for opening and one for saving. Most of the code should by now be familiar, and all the interesting activities happen in action( ) for the two different button clicks:

//: FileDialogTest.java
// Demonstration of File dialog boxes
import java.awt.*;

public class FileDialogTest extends Frame {
  TextField filename = new TextField();
  TextField directory = new TextField();
  Button open = new Button("Open");
  Button save = new Button("Save");
  public FileDialogTest() {
    setTitle("File Dialog Test");
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    p.add(open);
    p.add(save);
    add("South", p);
    directory.setEditable(false);
    filename.setEditable(false);
    p = new Panel();
    p.setLayout(new GridLayout(2,1));
    p.add(filename);
    p.add(directory);
    add("North", p);
  }
  public boolean handleEvent(Event evt) {
    if(evt.id == Event.WINDOW_DESTROY) 
      System.exit(0);
    else 
      return super.handleEvent(evt);
    return true;
  }
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(open)) {
      // Two arguments, defaults to open file:
      FileDialog d = new FileDialog(this,
        "What file do you want to open?");
      d.setFile("*.java"); // Filename filter
      d.setDirectory("."); // Current directory
      d.show();
      String openFile;
      if((openFile = d.getFile()) != null) {
        filename.setText(openFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    } 
    else if(evt.target.equals(save)) {
      FileDialog d = new FileDialog(this,
        "What file do you want to save?",
        FileDialog.SAVE);
      d.setFile("*.java");
      d.setDirectory(".");
      d.show();
      String saveFile;
      if((saveFile = d.getFile()) != null) {
        filename.setText(saveFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    } 
    else 
      return super.action(evt, arg);
    return true;
  }
  public static void main(String[] args) {
    Frame f = new FileDialogTest();
    f.resize(250,110);
    f.show();
  }
} ///:~

For an “open file” dialog, you use the constructor that takes two arguments; the first is the parent window handle and the second is the title for the title bar of the FileDialog. The method setFile( ) provides an initial file name – presumably the native OS supports wildcards, so in this example all the .java files will initially be displayed. The setDirectory( ) method chooses the directory where the file selection will begin. (In general, the OS allows the user to change directories.)

The show( ) command doesn’t return until the dialog is closed. The FileDialog object still exists, so you can read data from it. If you call getFile( ) and it returns null it means the user canceled out of the dialog. Both the file name and the results of getDirectory( ) are displayed in the TextFields.

The button for saving works the same way, except that it uses a different constructor for the FileDialog. This constructor takes three arguments and the third argument must be either FileDialog.SAVE or FileDialog.OPEN.

The new AWT

In Java 1.1 a dramatic change has been accomplished in the creation of the new AWT. Most of this change revolves around the new event model used in Java 1.1: as bad, awkward, and non-object-oriented as the old event model was, the new event model is possibly the most elegant I have seen. It’s difficult to understand how such a bad design (the old AWT) and such a good one (the new event model) could come out of the same group. This new way of thinking about events seems to drop so easily into your mind that the issue no longer becomes an impediment; instead, it’s a tool that helps you design the system. It’s also essential for Java Beans, described later in the chapter.

Instead of the non-object-oriented cascaded if statements in the old AWT, the new approach designates objects as “sources” and “listeners” of events. As you will see, the use of inner classes is integral to the object-oriented nature of the new event model. In addition, events are now represented in a class hierarchy instead of a single class, and you can create your own event types.

You’ll also find, if you’ve programmed with the old AWT, that Java 1.1 has made a number of what might seem like gratuitous name changes. For example, setSize( ) replaces resize( ). This will make sense when you learn about Java Beans, because Beans use a particular naming convention. The names had to be modified to make the standard AWT components into Beans.

Java 1.1 continues to support the old AWT to ensure backward compatibility with existing programs. Without fully admitting disaster, the online documents for Java 1.1 list all the problems involved with programming the old AWT and describe how those problems are addressed in the new AWT.

Clipboard operations are supported in 1.1, although drag-and-drop “will be supported in a future release.” You can access the desktop color scheme so your Java program can fit in with the rest of the desktop. Pop-up menus are available, and there are some improvements for graphics and images. Mouseless operation is supported. There is a simple API for printing and simplified support for scrolling.

The new event model

In the new event model a component can initiate (“fire”) an event. Each type of event is represented by a distinct class. When an event is fired, it is received by one or more “listeners,” which act on that event. Thus, the source of an event and the place where the event is handled can be separate.

Each event listener is an object of a class that implements a particular type of listener interface. So as a programmer, all you do is create a listener object and register it with the component that’s firing the event. This registration is performed by calling a addXXXListener( ) method in the event-firing component, in which XXX represents the type of event listened for. You can easily know what types of events can be handled by noticing the names of the addListener methods, and if you try to listen for the wrong events you’ll find out your mistake at compile time. Java Beans also uses the names of the addListener methods to determine what a Bean can do.

All of your event logic, then, will go inside a listener class. When you create a listener class, the sole restriction is that it must implement the appropriate interface. You can create a global listener class, but this is a situation in which inner classes tend to be quite useful, not only because they provide a logical grouping of your listener classes inside the UI or business logic classes they are serving, but because (as you shall see later) the fact that an inner class object keeps a handle to its parent object provides a nice way to call across class and subsystem boundaries.

A simple example will make this clear. Consider the Button2.java example from earlier in this chapter.

//: Button2New.java
// Capturing button presses
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;

public class Button2New extends Applet {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
  }
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      getAppletContext().showStatus("Button 1");
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      getAppletContext().showStatus("Button 2");
    }
  }
  /* The old way:
  public boolean action(Event evt, Object arg) {
    if(evt.target.equals(b1))
      getAppletContext().showStatus("Button 1");
    else if(evt.target.equals(b2))
      getAppletContext().showStatus("Button 2");
    // Let the base class handle it:
    else 
      return super.action(evt, arg);
    return true; // We've handled it here
  }
  */
} ///:~

So you can compare the two approaches, the old code is left in as a comment. In init( ), the only change is the addition of the two lines:

b1.addActionListener(new B1());
b2.addActionListener(new B2());

addActionListener( ) tells a button which object to activate when the button is pressed. The classes B1 and B2 are inner classes that implement the interface ActionListener. This interface contains a single method actionPerformed( ) (meaning “This is the action that will be performed when the event is fired”). Note that actionPerformed( ) does not take a generic event, but rather a specific type of event, ActionEvent. So you don’t need to bother testing and downcasting the argument if you want to extract specific ActionEvent information.

One of the nicest things about actionPerformed( ) is how simple it is. It’s just a method that gets called. Compare it to the old action( ) method, in which you must figure out what happened and act appropriately, and also worry about calling the base class version of action( ) and return a value to indicate whether it’s been handled. With the new event model you know that all the event-detection logic is taken care of so you don’t have to figure that out; you just say what happens and you’re done. If you don’t already prefer this approach over the old one, you will soon.

Event and listener types

All the AWT components have been changed to include addXXXListener( ) and removeXXXListener( ) methods so that the appropriate types of listeners can be added and removed from each component. You’ll notice that the “XXX” in each case also represents the argument for the method, for example, addFooListener(FooListener fl). The following table includes the associated events, listeners, methods, and the components that support those particular events by providing the addXXXListener( ) and removeXXXListener( ) methods.

Event, listener interface and add- and remove-methods

Components supporting this event

ActionEvent
ActionListener
addActionListener( )
removeActionListener( )

Button, List, TextField, MenuItem, and its derivatives including CheckboxMenuItem, Menu, and PopupMenu

AdjustmentEvent
AdjustmentListener
addAdjustmentListener( )
removeAdjustmentListener( )

Scrollbar
Anything you create that implements the Adjustable interface

ComponentEvent
ComponentListener
addComponentListener( )
removeComponentListener( )

Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField

ContainerEvent
ContainerListener
addContainerListener( )
removeContainerListener( )

Container and its derivatives, including Panel, Applet, ScrollPane, Window, Dialog, FileDialog, and Frame

FocusEvent
FocusListener
addFocusListener( )
removeFocusListener( )

Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame Label, List, Scrollbar, TextArea, and TextField

KeyEvent
KeyListener
addKeyListener( )
removeKeyListener( )

Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField

MouseEvent (for both clicks and motion)
MouseListener
addMouseListener( )
removeMouseListener( )

Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField

MouseEvent[55] (for both clicks and motion)
MouseMotionListener
addMouseMotionListener( )
removeMouseMotionListener( )

Component and its derivatives, including Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, and TextField

WindowEvent
WindowListener
addWindowListener( )
removeWindowListener( )

Window and its derivatives, including Dialog, FileDialog, and Frame

ItemEvent
ItemListener
addItemListener( )
removeItemListener( )

Checkbox, CheckboxMenuItem, Choice, List, and anything that implements the ItemSelectable interface

TextEvent
TextListener
addTextListener( )
removeTextListener( )

Anything derived from TextComponent, including TextArea and TextField

You can see that each type of component supports only certain types of events. It’s helpful to see the events supported by each component, as shown in the following table:

Component type

Events supported by this component

Adjustable

AdjustmentEvent

Applet

ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Button

ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Canvas

FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Checkbox

ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

CheckboxMenuItem

ActionEvent, ItemEvent

Choice

ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Component

FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Container

ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Dialog

ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

FileDialog

ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Frame

ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Label

FocusEvent, KeyEvent, MouseEvent, ComponentEvent

List

ActionEvent, FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent

Menu

ActionEvent

MenuItem

ActionEvent

Panel

ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

PopupMenu

ActionEvent

Scrollbar

AdjustmentEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

ScrollPane

ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

TextArea

TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

TextComponent

TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

TextField

ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Window

ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Once you know which events a particular component supports, you don’t need to look anything up to react to that event. You simply:

  1. Take the name of the event class and remove the word “Event.” Add the word “Listener” to what remains. This is the listener interface you need to implement in your inner class.
  2. Implement the interface above and write out the methods for the events you want to capture. For example, you might be looking for mouse movements, so you write code for the mouseMoved( ) method of the MouseMotionListener interface. (You must implement the other methods, of course, but there’s a shortcut for that which you’ll see soon.)
  3. Create an object of the listener class in step 2. Register it with your component with the method produced by prefixing “add” to your listener name. For example, addMouseMotionListener( ).

To finish what you need to know, here are the listener interfaces:

Listener interface
w/ adapter

Methods in interface

ActionListener

actionPerformed(ActionEvent)

AdjustmentListener

adjustmentValueChanged(
AdjustmentEvent)

ComponentListener
ComponentAdapter

componentHidden(ComponentEvent)
componentShown(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)

ContainerListener
ContainerAdapter

componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)

FocusListener
FocusAdapter

focusGained(FocusEvent)
focusLost(FocusEvent)

KeyListener
KeyAdapter

keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)

MouseListener
MouseAdapter

mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)

MouseMotionListener
MouseMotionAdapter

mouseDragged(MouseEvent)
mouseMoved(MouseEvent)

WindowListener
WindowAdapter

windowOpened(WindowEvent)
windowClosing(WindowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)

ItemListener

itemStateChanged(ItemEvent)

TextListener

textValueChanged(TextEvent)

Using listener adapters for simplicity

In the table above, you can see that some listener interfaces have only one method. These are trivial to implement since you’ll implement them only when you want to write that particular method. However, the listener interfaces that have multiple methods could be less pleasant to use. For example, something you must always do when creating an application is provide a WindowListener to the Frame so that when you get the windowClosing( ) event you can call System.exit(0) to exit the application. But since WindowListener is an interface, you must implement all of the other methods even if they don’t do anything. This can be annoying.

To solve the problem, each of the listener interfaces that have more than one method are provided with adapters, the names of which you can see in the table above. Each adapter provides default methods for each of the interface methods. (Alas, WindowAdapter does not have a default windowClosing( ) that calls System.exit(0).) Then all you need to do is inherit from the adapter and override only the methods you need to change. For example, the typical WindowListener you’ll use looks like this:

class MyWindowListener extends WindowAdapter {
  public void windowClosing(WindowEvent e) {
    System.exit(0);
  }
}

The whole point of the adapters is to make the creation of listener classes easy.

There is a downside to adapters, however, in the form of a pitfall. Suppose you write a WindowAdapter like the one above:

class MyWindowListener extends WindowAdapter {
  public void WindowClosing(WindowEvent e) {
    System.exit(0);
  }
}

This doesn’t work, but it will drive you crazy trying to figure out why, since everything will compile and run fine – except that closing the window won’t exit the program. Can you see the problem? It’s in the name of the method: WindowClosing( ) instead of windowClosing( ). A simple slip in capitalization results in the addition of a completely new method. However, this is not the method that’s called when the window is closing, so you don’t get the desired results.

Making windows and applets
with the Java 1.1 AWT

Often you’ll want to be able to create a class that can be invoked as either a window or an applet. To accomplish this, you simply add a main( ) to your applet that builds an instance of the applet inside a Frame. As a simple example, let’s look at Button2New.java modified to work as both an application and an applet:

//: Button2NewB.java
// An application and an applet
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;

public class Button2NewB extends Applet {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  TextField t = new TextField(20);
  public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
    add(t);
  }
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Button 1");
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Button 2");
    }
  }
  // To close the application:
  static class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  }
  // A main() for the application:
  public static void main(String[] args) {
    Button2NewB applet = new Button2NewB();
    Frame aFrame = new Frame("Button2NewB");
    aFrame.addWindowListener(new WL());
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

The inner class WL and the main( ) are the only two elements added to the applet, and the rest of the applet is untouched. In fact, you can usually copy and paste the WL class and main( ) into your own applets with little modification. The WL class is static so it can be easily created in main( ). (Remember that an inner class normally needs an outer class handle when it’s created. Making it static eliminates this need.) You can see that in main( ), the applet is explicitly initialized and started since in this case the browser isn’t available to do it for you. Of course, this doesn’t provide the full behavior of the browser, which also calls stop( ) and destroy( ), but for most situations it’s acceptable. If it’s a problem, you can:

  1. Make the handle applet a static member of the class (instead of a local variable of main( )), and then:
  2. Call applet.stop( ) and applet.destroy( ) inside WindowAdapter.windowClosing( ) before you call System.exit( ).

Notice the last line:

aFrame.setVisible(true);

This is one of the changes in the Java 1.1 AWT. The show( ) method is deprecated and setVisible(true) replaces it. These sorts of seemingly capricious changes will make more sense when you learn about Java Beans later in the chapter.

This example is also modified to use a TextField rather than printing to the console or to the browser status line. One restriction in making a program that’s both an applet and an application is that you must choose input and output forms that work for both situations.

There’s another small new feature of the Java 1.1 AWT shown here. You no longer need to use the error-prone approach of specifying BorderLayout positions using a String. When adding an element to a BorderLayout in Java 1.1, you can say:

aFrame.add(applet, BorderLayout.CENTER);

You name the location with one of the BorderLayout constants, which can then be checked at compile-time (rather than just quietly doing the wrong thing, as with the old form). This is a definite improvement, and will be used throughout the rest of the book.

Making the window listener
an anonymous class

Any of the listener classes could be implemented as anonymous classes, but there’s always a chance that you might want to use their functionality elsewhere. However, the window listener is used here only to close the application’s window so you can safely make it an anonymous class. Then, in main( ), the line:

aFrame.addWindowListener(new WL());

will become:

aFrame.addWindowListener(
  new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
      System.exit(0);
    }
  });

This has the advantage that it doesn’t require yet another class name. You must decide for yourself whether it makes the code easier to understand or more difficult. However, for the remainder of the book an anonymous inner class will usually be used for the window listener.

Packaging the applet into a JAR file

An important JAR use is to optimize applet loading. In Java 1.0, people tended to try to cram all their code into a single Applet class so the client would need only a single server hit to download the applet code. Not only did this result in messy, hard to read (and maintain) programs, but the .class file was still uncompressed so downloading wasn’t as fast as it could have been.

JAR files change all of that by compressing all of your .class files into a single file that is downloaded by the browser. Now you don’t need to create an ugly design to minimize the number of classes you create, and the user will get a much faster download time.

Consider the example above. It looks like Button2NewB is a single class, but in fact it contains three inner classes, so that’s four in all. Once you’ve compiled the program, you package it into a JAR file with the line:

jar cf Button2NewB.jar *.class

This assumes that the only .class files in the current directory are the ones from Button2NewB.java (otherwise you’ll get extra baggage).

Now you can create an HTML page with the new archive tag to indicate the name of the JAR file, like this:

<head><title>Button2NewB Example Applet
</title></head>
<body>
<applet code="Button2NewB.class" 
        archive="Button2NewB.jar" 
        width=200 height=150>
</applet>
</body>

Everything else about applet tags in HTML files remains the same.

Revisiting the earlier examples

To see a number of examples using the new event model and to study the way a program can be converted from the old to the new event model, the following examples revisit many of the issues demonstrated in the first part of this chapter using the old event model. In addition, each program is now both an applet and an application so you can run it with or without a browser.

Text fields

This is similar to TextField1.java, but it adds significant extra behavior:

//: TextNew.java
// Text fields with Java 1.1 events
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class TextNew extends Applet {
  Button 
    b1 = new Button("Get Text"), 
    b2 = new Button("Set Text");
  TextField 
    t1 = new TextField(30),
    t2 = new TextField(30),
    t3 = new TextField(30);
  String s = new String();
  public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    t1.addTextListener(new T1());
    t1.addActionListener(new T1A());
    t1.addKeyListener(new T1K());
    add(b1);
    add(b2);
    add(t1);
    add(t2);
    add(t3);
  }
  class T1 implements TextListener {
    public void textValueChanged(TextEvent e) {
      t2.setText(t1.getText());
    }
  }
  class T1A implements ActionListener {
    private int count = 0;
    public void actionPerformed(ActionEvent e) {
      t3.setText("t1 Action Event " + count++);
    }
  }
  class T1K extends KeyAdapter {
    public void keyTyped(KeyEvent e) {
      String ts = t1.getText();
      if(e.getKeyChar() == 
          KeyEvent.VK_BACK_SPACE) {
        // Ensure it's not empty:
        if( ts.length() > 0) {
          ts = ts.substring(0, ts.length() - 1);
          t1.setText(ts);
        }
      } 
      else
        t1.setText(
          t1.getText() +
            Character.toUpperCase(
              e.getKeyChar()));
      t1.setCaretPosition(
        t1.getText().length());
      // Stop regular character from appearing:
      e.consume(); 
    }
  }
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      s = t1.getSelectedText();
      if(s.length() == 0) s = t1.getText();
      t1.setEditable(true);
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t1.setText("Inserted by Button 2: " + s);
      t1.setEditable(false);
    }
  }
  public static void main(String[] args) {
    TextNew applet = new TextNew();
    Frame aFrame = new Frame("TextNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

The TextField t3 is included as a place to report when the action listener for the TextField t1 is fired. You’ll see that the action listener for a TextField is fired only when you press the “enter” key.

The TextField t1 has several listeners attached to it. The T1 listener copies all text from t1 into t2 and the T1K listener forces all characters to upper case. You’ll notice that the two work together, and if you add the T1K listener after you add the T1 listener, it doesn’t matter: all characters will still be forced to upper case in both text fields. It would seem that keyboard events are always fired before TextComponent events, and if you want the characters in t2 to retain the original case that was typed in, you must do some extra work.

T1K has some other activities of interest. You must detect a backspace (since you’re controlling everything now) and perform the deletion. The caret must be explicitly set to the end of the field; otherwise it won’t behave as you expect. Finally, to prevent the original character from being handled by the default mechanism, the event must be “consumed” using the consume( ) method that exists for event objects. This tells the system to stop firing the rest of the event handlers for this particular event.

This example also quietly demonstrates one of the benefits of the design of inner classes. Note that in the inner class:

  class T1 implements TextListener {
    public void textValueChanged(TextEvent e) {
      t2.setText(t1.getText());
    }
  }

t1 and t2 are not members of T1, and yet they’re accessible without any special qualification. This is because an object of an inner class automatically captures a handle to the outer object that created it, so you can treat members and methods of the enclosing class object as if they’re yours. As you can see, this is quite convenient.[56]

Text areas

The most significant change to text areas in Java 1.1 concerns scroll bars. With the TextArea constructor, you can now control whether a TextArea will have scroll bars: vertical, horizontal, both, or neither. This example modifies the earlier Java 1.0 TextArea1.java to show the Java 1.1 scrollbar constructors:

//: TextAreaNew.java
// Controlling scrollbars with the TextArea
// component in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class TextAreaNew extends Applet {
  Button b1 = new Button("Text Area 1");
  Button b2 = new Button("Text Area 2");
  Button b3 = new Button("Replace Text");
  Button b4 = new Button("Insert Text");
  TextArea t1 = new TextArea("t1", 1, 30);
  TextArea t2 = new TextArea("t2", 4, 30);
  TextArea t3 = new TextArea("t3", 1, 30,
    TextArea.SCROLLBARS_NONE);
  TextArea t4 = new TextArea("t4", 10, 10,
    TextArea.SCROLLBARS_VERTICAL_ONLY);
  TextArea t5 = new TextArea("t5", 4, 30,
    TextArea.SCROLLBARS_HORIZONTAL_ONLY);
  TextArea t6 = new TextArea("t6", 10, 10,
    TextArea.SCROLLBARS_BOTH);
  public void init() {
    b1.addActionListener(new B1L());
    add(b1);
    add(t1);
    b2.addActionListener(new B2L());
    add(b2);
    add(t2);
    b3.addActionListener(new B3L());
    add(b3);
    b4.addActionListener(new B4L());
    add(b4);
    add(t3); add(t4); add(t5); add(t6);
  }
  class B1L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t5.append(t1.getText() + "\n");
    }
  }
  class B2L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t2.setText("Inserted by Button 2");
      t2.append(": " + t1.getText());
      t5.append(t2.getText() + "\n");
    }
  }
  class B3L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String s = " Replacement ";
      t2.replaceRange(s, 3, 3 + s.length());
    }
  }
  class B4L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t2.insert(" Inserted ", 10);
    }
  }
  public static void main(String[] args) {
    TextAreaNew applet = new TextAreaNew();
    Frame aFrame = new Frame("TextAreaNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,725);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

You’ll notice that you can control the scrollbars only at the time of construction of the TextArea. Also, even if a TextArea doesn’t have a scrollbar, you can move the cursor such that scrolling will be forced. (You can see this behavior by playing with the example.)

Check boxes and radio buttons

As noted previously, check boxes and radio buttons are both created with the same class, Checkbox, but radio buttons are Checkboxes placed into a CheckboxGroup. In either case, the interesting event is ItemEvent, for which you create an ItemListener.

When dealing with a group of check boxes or radio buttons, you have a choice. You can either create a new inner class to handle the event for each different Checkbox or you can create one inner class that determines which Checkbox was clicked and register a single object of that inner class with each Checkbox object. The following example shows both approaches:

//: RadioCheckNew.java
// Radio buttons and Check Boxes in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class RadioCheckNew extends Applet {
  TextField t = new TextField(30);
  Checkbox[] cb = {
    new Checkbox("Check Box 1"),
    new Checkbox("Check Box 2"),
    new Checkbox("Check Box 3") };
  CheckboxGroup g = new CheckboxGroup();
  Checkbox 
    cb4 = new Checkbox("four", g, false),
    cb5 = new Checkbox("five", g, true),
    cb6 = new Checkbox("six", g, false);
  public void init() {
    t.setEditable(false);
    add(t); 
    ILCheck il = new ILCheck();
    for(int i = 0; i < cb.length; i++) {
      cb[i].addItemListener(il);
      add(cb[i]);
    }
    cb4.addItemListener(new IL4());
    cb5.addItemListener(new IL5());
    cb6.addItemListener(new IL6());
    add(cb4); add(cb5); add(cb6); 
  }
  // Checking the source:
  class ILCheck implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      for(int i = 0; i < cb.length; i++) {
        if(e.getSource().equals(cb[i])) {
          t.setText("Check box " + (i + 1));
          return;
        }
      }
    }
  }
  // vs. an individual class for each item:
  class IL4 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Radio button four");
    }
  }
  class IL5 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Radio button five");
    }
  }
  class IL6 implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("Radio button six");
    }
  }
  public static void main(String[] args) {
    RadioCheckNew applet = new RadioCheckNew();
    Frame aFrame = new Frame("RadioCheckNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

ILCheck has the advantage that it automatically adapts when you add or subtract Checkboxes. Of course, you can use this with radio buttons as well. It should be used, however, only when your logic is general enough to support this approach. Otherwise you’ll end up with a cascaded if statement, a sure sign that you should revert to using independent listener classes.

Drop-down lists

Drop-down lists (Choice) in Java 1.1 also use ItemListeners to notify you when a choice has changed:

//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class ChoiceNew extends Applet {
  String[] description = { "Ebullient", "Obtuse",
    "Recalcitrant", "Brilliant", "Somnescent",
    "Timorous", "Florid", "Putrescent" };
  TextField t = new TextField(100);
  Choice c = new Choice();
  Button b = new Button("Add items");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      c.addItem(description[count++]);
    add(t);
    add(c);
    add(b);
    c.addItemListener(new CL());
    b.addActionListener(new BL());
  }
  class CL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("index: " +  c.getSelectedIndex()
        + "   " + e.toString());
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < description.length)
        c.addItem(description[count++]);
    }
  }
  public static void main(String[] args) {
    ChoiceNew applet = new ChoiceNew();
    Frame aFrame = new Frame("ChoiceNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(750,100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

Nothing else here is particularly new (except that Java 1.1 has significantly fewer bugs in the UI classes).

Lists

You’ll recall that one of the problems with the Java 1.0 List design is that it took extra work to make it do what you’d expect: react to a single click on one of the list elements. Java 1.1 has solved this problem:

//: ListNew.java
// Java 1.1 Lists are easier to use
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

public class ListNew extends Applet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  // Show 6 items, allow multiple selection:
  List lst = new List(6, true);
  TextArea t = new TextArea(flavors.length, 30);
  Button b = new Button("test");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      lst.addItem(flavors[count++]);
    add(t);
    add(lst);
    add(b);
    lst.addItemListener(new LL());
    b.addActionListener(new BL());
  }
  class LL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("");
      String[] items = lst.getSelectedItems();
      for(int i = 0; i < items.length; i++)
        t.append(items[i] + "\n");
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < flavors.length)
        lst.addItem(flavors[count++], 0);
    }
  }
  public static void main(String[] args) {
    ListNew applet = new ListNew();
    Frame aFrame = new Frame("ListNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(300,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

You can see that no extra logic is required to support a single click on a list item. You just attach a listener like you do everywhere else.

Menus

The event handling for menus does seem to benefit from the Java 1.1 event model, but Java’s approach to menus is still messy and requires a lot of hand coding. The right medium for a menu seems to be a resource rather than a lot of code. Keep in mind that program-building tools will generally handle the creation of menus for you, so that will reduce the pain somewhat (as long as they will also handle the maintenance!).

In addition, you’ll find the events for menus are inconsistent and can lead to confusion: MenuItems use ActionListeners, but CheckboxMenuItems use ItemListeners. The Menu objects can also support ActionListeners, but that’s not usually helpful. In general, you’ll attach listeners to each MenuItem or CheckboxMenuItem, but the following example (revised from the earlier version) also shows ways to combine the capture of multiple menu components into a single listener class. As you’ll see, it’s probably not worth the hassle to do this.

//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;

public class MenuNew extends Frame {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  TextField t = new TextField("No flavor", 30);
  MenuBar mb1 = new MenuBar();
  Menu f = new Menu("File");
  Menu m = new Menu("Flavors");
  Menu s = new Menu("Safety");
  // Alternative approach:
  CheckboxMenuItem[] safety = {
    new CheckboxMenuItem("Guard"),
    new CheckboxMenuItem("Hide")
  };
  MenuItem[] file = {
    // No menu shortcut:
    new MenuItem("Open"),
    // Adding a menu shortcut is very simple:
    new MenuItem("Exit", 
      new MenuShortcut(KeyEvent.VK_E))
  };
  // A second menu bar to swap to:
  MenuBar mb2 = new MenuBar();
  Menu fooBar = new Menu("fooBar");
  MenuItem[] other = {
    new MenuItem("Foo"),
    new MenuItem("Bar"),
    new MenuItem("Baz"),
  };
  // Initialization code:
  {
    ML ml = new ML();
    CMIL cmil = new CMIL();
    safety[0].setActionCommand("Guard");
    safety[0].addItemListener(cmil);
    safety[1].setActionCommand("Hide");
    safety[1].addItemListener(cmil);
    file[0].setActionCommand("Open");
    file[0].addActionListener(ml);
    file[1].setActionCommand("Exit");
    file[1].addActionListener(ml);
    other[0].addActionListener(new FooL());
    other[1].addActionListener(new BarL());
    other[2].addActionListener(new BazL());
  }
  Button b = new Button("Swap Menus");
  public MenuNew() {
    FL fl = new FL();
    for(int i = 0; i < flavors.length; i++) {
      MenuItem mi = new MenuItem(flavors[i]);
      mi.addActionListener(fl);
      m.add(mi);
      // Add separators at intervals:
      if((i+1) % 3 == 0) 
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++)
      s.add(safety[i]);
    f.add(s);
    for(int i = 0; i < file.length; i++)
      f.add(file[i]);
    mb1.add(f);
    mb1.add(m);
    setMenuBar(mb1);
    t.setEditable(false);
    add(t, BorderLayout.CENTER);
    // Set up the system for swapping menus:
    b.addActionListener(new BL());
    add(b, BorderLayout.NORTH);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuBar m = getMenuBar();
      if(m == mb1) setMenuBar(mb2);
      else if (m == mb2) setMenuBar(mb1);
    }
  }
  class ML implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      String actionCommand = 
        target.getActionCommand();
      if(actionCommand.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i < flavors.length; i++)
          if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      } else if(actionCommand.equals("Exit")) {
        dispatchEvent(
          new WindowEvent(MenuNew.this, 
            WindowEvent.WINDOW_CLOSING));
      }
    }
  }
  class FL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      t.setText(target.getLabel());
    }
  }
  // Alternatively, you can create a different
  // class for each different MenuItem. Then you
  // Don't have to figure out which one it is:
  class FooL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Foo selected");
    }
  }
  class BarL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Bar selected");
    }
  }
  class BazL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Baz selected");
    }
  }
  class CMIL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      CheckboxMenuItem target = 
        (CheckboxMenuItem)e.getSource();
      String actionCommand = 
        target.getActionCommand();
      if(actionCommand.equals("Guard"))
        t.setText("Guard the Ice Cream! " +
          "Guarding is " + target.getState());
      else if(actionCommand.equals("Hide"))
        t.setText("Hide the Ice Cream! " +
          "Is it cold? " + target.getState());
    }
  }
  public static void main(String[] args) {
    MenuNew f = new MenuNew();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~

This code is similar to the previous (Java 1.0) version, until you get to the initialization section (marked by the opening brace right after the comment “Initialization code:”). Here you can see the ItemListeners and ActionListeners attached to the various menu components.

Java 1.1 supports “menu shortcuts,” so you can select a menu item using the keyboard instead of the mouse. These are quite simple; you just use the overloaded MenuItem constructor that takes as a second argument a MenuShortcut object. The constructor for MenuShortcut takes the key of interest, which magically appears on the menu item when it drops down. The example above adds Control-E to the “Exit” menu item.

You can also see the use of setActionCommand( ). This seems a bit strange because in each case the “action command” is exactly the same as the label on the menu component. Why not just use the label instead of this alternative string? The problem is internationalization. If you retarget this program to another language, you want to change only the label in the menu, and not go through the code changing all the logic that will no doubt introduce new errors. So to make this easy for code that checks the text string associated with a menu component, the “action command” can be immutable while the menu label can change. All the code works with the “action command,” so it’s unaffected by changes to the menu labels. Note that in this program, not all the menu components are examined for their action commands, so those that aren’t don’t have their action command set.

Much of the constructor is the same as before, with the exception of a couple of calls to add listeners. The bulk of the work happens in the listeners. In BL, the MenuBar swapping happens as in the previous example. In ML, the “figure out who rang” approach is taken by getting the source of the ActionEvent and casting it to a MenuItem, then getting the action command string to pass it through a cascaded if statement. Much of this is the same as before, but notice that if “Exit” is chosen, a new WindowEvent is created, passing in the handle of the enclosing class object (MenuNew.this) and creating a WINDOW_CLOSING event. This is handed to the dispatchEvent( ) method of the enclosing class object, which then ends up calling windowClosing( ) inside the window listener for the Frame (this listener is created as an anonymous inner class, inside main( )), just as if the message had been generated the “normal” way. Through this mechanism, you can dispatch any message you want in any circumstances, so it’s quite powerful.

The FL listener is simple even though it’s handling all the different flavors in the flavor menu. This approach is useful if you have enough simplicity in your logic, but in general, you’ll want to take the approach used with FooL, BarL, and BazL, in which they are each attached to only a single menu component so no extra detection logic is necessary and you know exactly who called the listener. Even with the profusion of classes generated this way, the code inside tends to be smaller and the process is more foolproof.

Dialog boxes

This is a direct rewrite of the earlier ToeTest.java. In this version, however, everything is placed inside an inner class. Although this completely eliminates the need to keep track of the object that spawned any class, as was the case in ToeTest.java, it could be taking the concept of inner classes a bit too far. At one point, the inner classes are nested four deep! This is the kind of design in which you need to decide whether the benefit of inner classes is worth the increased complexity. In addition, when you create a non-static inner class you’re tying that class to its surrounding class. Sometimes a standalone class can more easily be reused.

//: ToeTestNew.java
// Demonstration of dialog boxes
// and creating your own components
import java.awt.*;
import java.awt.event.*;

public class ToeTestNew extends Frame {
  TextField rows = new TextField("3");
  TextField cols = new TextField("3");
  public ToeTestNew() {
    setTitle("Toe Test");
    Panel p = new Panel();
    p.setLayout(new GridLayout(2,2));
    p.add(new Label("Rows", Label.CENTER));
    p.add(rows);
    p.add(new Label("Columns", Label.CENTER));
    p.add(cols);
    add(p, BorderLayout.NORTH);
    Button b = new Button("go");
    b.addActionListener(new BL());
    add(b, BorderLayout.SOUTH);
  }
  static final int BLANK = 0;
  static final int XX = 1;
  static final int OO = 2;
  class ToeDialog extends Dialog {
    // w = number of cells wide
    // h = number of cells high
    int turn = XX; // Start with x's turn
    public ToeDialog(int w, int h) {
      super(ToeTestNew.this, 
        "The game itself", false);
      setLayout(new GridLayout(w, h));
      for(int i = 0; i < w * h; i++)
        add(new ToeButton());
      setSize(w * 50, h * 50);
      addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          dispose();
        }
      });
    }
    class ToeButton extends Canvas {
      int state = BLANK;
      ToeButton() {
        addMouseListener(new ML());
      }
      public void paint(Graphics  g) {
        int x1 = 0;
        int y1 = 0;
        int x2 = getSize().width - 1;
        int y2 = getSize().height - 1;
        g.drawRect(x1, y1, x2, y2);
        x1 = x2/4;
        y1 = y2/4;
        int wide = x2/2;
        int high = y2/2;
        if(state == XX) {
          g.drawLine(x1, y1, 
            x1 + wide, y1 + high);
          g.drawLine(x1, y1 + high, 
            x1 + wide, y1);
        }
        if(state == OO) {
          g.drawOval(x1, y1, 
            x1 + wide/2, y1 + high/2);
        }
      }
      class ML extends MouseAdapter {
        public void mousePressed(MouseEvent e) {
          if(state == BLANK) {
            state = turn;
            turn = (turn == XX ? OO : XX);
          } 
          else
            state = (state == XX ? OO : XX);
          repaint();
        }
      }
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      Dialog d = new ToeDialog(
        Integer.parseInt(rows.getText()),
        Integer.parseInt(cols.getText()));
      d.show();
    }
  }
  public static void main(String[] args) {
    Frame f = new ToeTestNew();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    f.setSize(200,100);
    f.setVisible(true);
  }
} ///:~

Because statics can be at only the outer level of the class, inner classes cannot have static data or static inner classes.

File dialogs

Converting from FileDialogTest.java to the new event model is straightforward:

//: FileDialogNew.java
// Demonstration of File dialog boxes
import java.awt.*;
import java.awt.event.*;

public class FileDialogNew extends Frame {
  TextField filename = new TextField();
  TextField directory = new TextField();
  Button open = new Button("Open");
  Button save = new Button("Save");
  public FileDialogNew() {
    setTitle("File Dialog Test");
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    open.addActionListener(new OpenL());
    p.add(open);
    save.addActionListener(new SaveL());
    p.add(save);
    add(p, BorderLayout.SOUTH);
    directory.setEditable(false);
    filename.setEditable(false);
    p = new Panel();
    p.setLayout(new GridLayout(2,1));
    p.add(filename);
    p.add(directory);
    add(p, BorderLayout.NORTH);
  }
  class OpenL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      // Two arguments, defaults to open file:
      FileDialog d = new FileDialog(
        FileDialogNew.this,
        "What file do you want to open?");
      d.setFile("*.java");
      d.setDirectory("."); // Current directory
      d.show();
      String yourFile = "*.*";
      if((yourFile = d.getFile()) != null) {
        filename.setText(yourFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    }
  }
  class SaveL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      FileDialog d = new FileDialog(
        FileDialogNew.this,
        "What file do you want to save?",
        FileDialog.SAVE);
      d.setFile("*.java");
      d.setDirectory(".");
      d.show();
      String saveFile;
      if((saveFile = d.getFile()) != null) {
        filename.setText(saveFile);
        directory.setText(d.getDirectory());
      } else {
        filename.setText("You pressed cancel");
        directory.setText("");
      }
    }
  }
  public static void main(String[] args) {
    Frame f = new FileDialogNew();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    f.setSize(250,110);
    f.setVisible(true);
  }
} ///:~

It would be nice if all the conversions were this easy, but they’re usually easy enough, and your code benefits from the improved readability.

Binding events dynamically

One of the benefits of the new AWT event model is flexibility. In the old model you were forced to hard code the behavior of your program, but with the new model you can add and remove event behavior with single method calls. The following example demonstrates this:

//: DynamicEvents.java
// The new Java 1.1 event model allows you to
// change event behavior dynamically. Also
// demonstrates multiple actions for an event.
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class DynamicEvents extends Frame {
  Vector v = new Vector();
  int i = 0;
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public DynamicEvents() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B());
    b1.addActionListener(new B1());
    b2.addActionListener(new B());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
  }
  class B implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("A button was pressed");
    }
  }
  class CountListener implements ActionListener {
    int index;
    public CountListener(int i) { index = i; }
    public void actionPerformed(ActionEvent e) {
      System.out.println(
        "Counted Listener " + index);
    }
  }    
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 1 pressed");
      ActionListener a = new CountListener(i++);
      v.addElement(a);
      b2.addActionListener(a);
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 2 pressed");
      int end = v.size() -1;
      if(end >= 0) {
        b2.removeActionListener(
          (ActionListener)v.elementAt(end));
        v.removeElementAt(end);
      }
    }
  }
  public static void main(String[] args) {
    Frame f = new DynamicEvents();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.show();
  }
} ///:~

The new twists in this example are:

  1. There is more than one listener attached to each Button. Usually, components handle events as multicast, meaning that you can register many listeners for a single event. In the special components in which an event is handled as unicast, you’ll get a TooManyListenersException.
  2. During the execution of the program, listeners are dynamically added and removed from the Button b2. Adding is accomplished in the way you’ve seen before, but each component also has a removeXXXListener( ) method to remove each type of listener.

This kind of flexibility provides much greater power in your programming.

You should notice that event listeners are not guaranteed to be called in the order they are added (although most implementations do in fact work that way).

Separating business logic
from UI logic

In general you’ll want to design your classes so that each one does “only one thing.” This is particularly important when user-interface code is concerned, since it’s easy to wrap up “what you’re doing” with “how you’re displaying it.” This kind of coupling prevents code reuse. It’s much more desirable to separate your “business logic” from the GUI. This way, you can not only reuse the business logic more easily, it’s also easier to reuse the GUI.

Another issue is multi-tiered systems, where the “business objects” reside on a completely separate machine. This central location of the business rules allows changes to be instantly effective for all new transactions, and is thus a compelling way to set up a system. However, these business objects can be used in many different applications and so should not be tied to any particular mode of display. They should just perform the business operations and nothing more.

The following example shows how easy it is to separate the business logic from the GUI code:

//: Separation.java
// Separating GUI logic and business objects
import java.awt.*;
import java.awt.event.*;
import java.applet.*;

class BusinessLogic {
  private int modifier;
  BusinessLogic(int mod) {
    modifier = mod;
  }
  public void setModifier(int mod) {
    modifier = mod;
  }
  public int getModifier() {
    return modifier;
  }
  // Some business operations:
  public int calculation1(int arg) {
    return arg * modifier;
  }
  public int calculation2(int arg) {
    return arg + modifier;
  }
}

public class Separation extends Applet {
  TextField 
    t = new TextField(20),
    mod = new TextField(20);
  BusinessLogic bl = new BusinessLogic(2);
  Button
    calc1 = new Button("Calculation 1"),
    calc2 = new Button("Calculation 2");
  public void init() {
    add(t);
    calc1.addActionListener(new Calc1L());
    calc2.addActionListener(new Calc2L());
    add(calc1); add(calc2);
    mod.addTextListener(new ModL());
    add(new Label("Modifier:"));
    add(mod);
  }
  static int getValue(TextField tf) {
    try {
      return Integer.parseInt(tf.getText());
    } catch(NumberFormatException e) {
      return 0;
    }
  }
  class Calc1L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText(Integer.toString(
        bl.calculation1(getValue(t))));
    }
  }
  class Calc2L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText(Integer.toString(
        bl.calculation2(getValue(t))));
    }
  }
  class ModL implements TextListener {
    public void textValueChanged(TextEvent e) {
      bl.setModifier(getValue(mod));
    }
  }
  public static void main(String[] args) {
    Separation applet = new Separation();
    Frame aFrame = new Frame("Separation");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(200,200);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~

You can see that BusinessLogic is a straightforward class that performs its operations without even a hint that it might be used in a GUI environment. It just does its job.

Separation keeps track of all the UI details, and it talks to BusinessLogic only through its public interface. All the operations are centered around getting information back and forth through the UI and the BusinessLogic object. So Separation, in turn, just does its job. Since Separation knows only that it’s talking to a BusinessLogic object (that is, it isn’t highly coupled), it could be massaged into talking to other types of objects without much trouble.

Thinking in terms of separating UI from business logic also makes life easier when you’re adapting legacy code to work with Java.

Recommended coding approaches

Inner classes, the new event model, and the fact that the old event model is still supported along with new library features that rely on old-style programming has added a new element of confusion. Now there are even more different ways for people to write unpleasant code. Unfortunately, this kind of code is showing up in books and article examples, and even in documentation and examples distributed from Sun! In this section we’ll look at some misunderstandings about what you should and shouldn’t do with the new AWT, and end by showing that except in extenuating circumstances you can always use listener classes (written as inner classes) to solve your event-handling needs. Since this is also the simplest and clearest approach, it should be a relief for you to learn this.

Before looking at anything else, you should know that although Java 1.1 is backward-compatible with Java 1.0 (that is, you can compile and run 1.0 programs with 1.1), you cannot mix the event models within the same program. That is, you cannot use the old-style action( ) method in the same program in which you employ listeners. This can be a problem in a larger program when you’re trying to integrate old code with a new program, since you must decide whether to use the old, hard-to-maintain approach with the new program or to update the old code. This shouldn’t be too much of a battle since the new approach is so superior to the old.

Baseline: the good way to do it

To give you something to compare with, here’s an example showing the recommended approach. By now it should be reasonably familiar and comfortable:

//: GoodIdea.java
// The best way to design classes using the new
// Java 1.1 event model: use an inner class for
// each different event. This maximizes 
// flexibility and modularity.
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class GoodIdea extends Frame {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public GoodIdea() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B1L());
    b2.addActionListener(new B2L());
    add(b1);
    add(b2);
  }
  public class B1L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 1 pressed");
    }
  }
  public class B2L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 2 pressed");
    }
  }
  public static void main(String[] args) {
    Frame f = new GoodIdea();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.out.println("Window Closing");
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~

This is fairly trivial: each button has its own listener that prints something out to the console. But notice that there isn’t an if statement in the entire program, or any statement that says, “I wonder what caused this event.” Each piece of code is concerned with doing, not type-checking. This is the best way to write your code; not only is it easier to conceptualize, but much easier to read and maintain. Cutting and pasting to create new programs is also much easier.

Implementing the main class as a listener

The first bad idea is a common and recommended approach. This makes the main class (typically Applet or Frame, but it could be any class) implement the various listeners. Here’s an example:

//: BadIdea1.java
// Some literature recommends this approach,
// but it's missing the point of the new event
// model in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class BadIdea1 extends Frame 
    implements ActionListener, WindowListener {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public BadIdea1() {
    setLayout(new FlowLayout());
    addWindowListener(this);
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }
  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == b1)
      System.out.println("Button 1 pressed");
    else if(source == b2)
      System.out.println("Button 2 pressed");
    else
      System.out.println("Something else");
  }    
  public void windowClosing(WindowEvent e) {
    System.out.println("Window Closing");
    System.exit(0);
  }
  public void windowClosed(WindowEvent e) {}
  public void windowDeiconified(WindowEvent e) {}
  public void windowIconified(WindowEvent e) {}
  public void windowActivated(WindowEvent e) {}
  public void windowDeactivated(WindowEvent e) {}
  public void windowOpened(WindowEvent e) {}  

  public static void main(String[] args) {
    Frame f = new BadIdea1();
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~

The use of this shows up in the three lines:

    addWindowListener(this);
    b1.addActionListener(this);
    b2.addActionListener(this);

Since BadIdea1 implements ActionListener and WindowListener, these lines are certainly acceptable, and if you’re still stuck in the mode of trying to make fewer classes to reduce server hits during applet loading, it seems to be a good idea. However:

  1. Java 1.1 supports JAR files so all your files can be placed in a single compressed JAR archive that requires only one server hit. You no longer need to reduce class count for Internet efficiency.
  2. The code above is much less modular so it’s harder to grab and paste. Note that you must not only implement the various interfaces for your main class, but in actionPerformed( ) you’ve got to detect which action was performed using a cascaded if statement. Not only is this going backwards, away from the listener model, but you can’t easily reuse the actionPerformed( ) method since it’s specific to this particular application. Contrast this with GoodIdea.java, in which you can just grab one listener class and paste it in anywhere else with minimal fuss. Plus you can register multiple listener classes with a single event, allowing even more modularity in what each listener class does.

Mixing the approaches

The second bad idea is to mix the two approaches: use inner listener classes, but also implement one or more listener interfaces as part of the main class. This approach has appeared without explanation in books and documentation, and I can only assume that the authors thought they must use the different approaches for different purposes. But you don’t – in your programming you can probably use inner listener classes exclusively.

//: BadIdea2.java
// An improvement over BadIdea1.java, since it
// uses the WindowAdapter as an inner class 
// instead of implementing all the methods of
// WindowListener, but still misses the
// valuable modularity of inner classes
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class BadIdea2 extends Frame 
    implements ActionListener {
  Button 
    b1 = new Button("Button 1"), 
    b2 = new Button("Button 2");
  public BadIdea2() {
    setLayout(new FlowLayout());
    addWindowListener(new WL());
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }
  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == b1)
      System.out.println("Button 1 pressed");
    else if(source == b2)
      System.out.println("Button 2 pressed");
    else
      System.out.println("Something else");
  }
  class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.out.println("Window Closing");
      System.exit(0);
    }
  }
  public static void main(String[] args) {
    Frame f = new BadIdea2();
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~

Since actionPerformed( ) is still tightly coupled to the main class, it’s hard to reuse that code. It’s also messier and less pleasant to read than the inner class approach.

There’s no reason that you have to use any of the old thinking for events in Java 1.1 – so why do it?

Inheriting a component

Another place where you’ll often see variations on the old way of doing things is when creating a new type of component. Here’s an example showing that here, too, the new way works:

//: GoodTechnique.java
// Your first choice when overriding components
// should be to install listeners. The code is
// much safer, more modular and maintainable.
import java.awt.*;
import java.awt.event.*;

class Display {
  public static final int
    EVENT = 0, COMPONENT = 1,
    MOUSE = 2, MOUSE_MOVE = 3,
    FOCUS = 4, KEY = 5, ACTION = 6,
    LAST = 7;
  public String[] evnt;
  Display() {
    evnt = new String[LAST];
    for(int i = 0; i < LAST; i++)
      evnt[i] = new String();
  }
  public void show(Graphics g) {
    for(int i = 0; i < LAST; i++)
      g.drawString(evnt[i], 0, 10 * i + 10);
  }
}

class EnabledPanel extends Panel {
  Color c;
  int id;
  Display display = new Display();
  public EnabledPanel(int i, Color mc) {
    id = i;
    c = mc;
    setLayout(new BorderLayout());
    add(new MyButton(), BorderLayout.SOUTH);
    addComponentListener(new CL());
    addFocusListener(new FL());
    addKeyListener(new KL());
    addMouseListener(new ML());
    addMouseMotionListener(new MML());
  }
  // To eliminate flicker:
  public void update(Graphics g) {
    paint(g);
  }
  public void paint(Graphics  g) {
    g.setColor(c);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    display.show(g);
  }
  // Don't need to enable anything for this:
  public void processEvent(AWTEvent e) {
    display.evnt[Display.EVENT]= e.toString();
    repaint();
    super.processEvent(e);
  }
  class CL implements ComponentListener {
    public void componentMoved(ComponentEvent e){
      display.evnt[Display.COMPONENT] = 
        "Component moved";
      repaint();
    }
    public void 
    componentResized(ComponentEvent e) {
      display.evnt[Display.COMPONENT] = 
        "Component resized";
      repaint();
    }
    public void 
    componentHidden(ComponentEvent e) {
      display.evnt[Display.COMPONENT] = 
        "Component hidden";
      repaint();
    }
    public void componentShown(ComponentEvent e){
      display.evnt[Display.COMPONENT] = 
        "Component shown";
      repaint();
    }
  }
  class FL implements FocusListener {
    public void focusGained(FocusEvent e) {
      display.evnt[Display.FOCUS] = 
        "FOCUS gained";
      repaint();
    }
    public void focusLost(FocusEvent e) {
      display.evnt[Display.FOCUS] = 
        "FOCUS lost";
      repaint();
    }
  }
  class KL implements KeyListener {
    public void keyPressed(KeyEvent e) {
      display.evnt[Display.KEY] = 
        "KEY pressed: "; 
      showCode(e);
    }
    public void keyReleased(KeyEvent e) {
      display.evnt[Display.KEY] = 
        "KEY released: "; 
      showCode(e);
    }
    public void keyTyped(KeyEvent e) {
      display.evnt[Display.KEY] = 
        "KEY typed: ";
      showCode(e);
    }
    void showCode(KeyEvent e) {
      int code = e.getKeyCode();
      display.evnt[Display.KEY] += 
        KeyEvent.getKeyText(code);
      repaint();
    }
  }
  class ML implements MouseListener {
    public void mouseClicked(MouseEvent e) {
      requestFocus(); // Get FOCUS on click
      display.evnt[Display.MOUSE] = 
        "MOUSE clicked";
      showMouse(e);
    }
    public void mousePressed(MouseEvent e) {
      display.evnt[Display.MOUSE] = 
        "MOUSE pressed";
      showMouse(e);
    }
    public void mouseReleased(MouseEvent e) {
      display.evnt[Display.MOUSE] = 
        "MOUSE released";
      showMouse(e);
    }
    public void mouseEntered(MouseEvent e) { 
      display.evnt[Display.MOUSE] = 
        "MOUSE entered";
      showMouse(e);
    }
    public void mouseExited(MouseEvent e) { 
      display.evnt[Display.MOUSE] = 
        "MOUSE exited";
      showMouse(e);
    }
    void showMouse(MouseEvent e) {
      display.evnt[Display.MOUSE] += 
        ", x = " + e.getX() + 
        ", y = " + e.getY();
      repaint();
    }
  }
  class MML implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] = 
        "MOUSE dragged";
      showMouse(e);
    }
    public void mouseMoved(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] = 
        "MOUSE moved";
      showMouse(e);
    }
    void showMouse(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] += 
        ", x = " + e.getX() + 
        ", y = " + e.getY();
      repaint();
    }
  }
}

class MyButton extends Button {
  int clickCounter;
  String label = "";
  public MyButton() {
    addActionListener(new AL());
  }
  public void paint(Graphics g) {
    g.setColor(Color.green);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, s.width - 1, s.height - 1);
    drawLabel(g);
  }
  private void drawLabel(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin = 
      (getSize().width - width)/2;
    int verMargin = 
      (getSize().height - height)/2;
    g.setColor(Color.red);
    g.drawString(label, horizMargin, 
      verMargin + ascent + leading);
  }
  class AL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      clickCounter++;
      label = "click #" + clickCounter +
        " " + e.toString();
      repaint();
    }
  }
}
  
public class GoodTechnique extends Frame {
  GoodTechnique() {
    setLayout(new GridLayout(2,2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
  }
  public static void main(String[] args) {
    Frame f = new GoodTechnique();
    f.setTitle("Good Technique");
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.out.println(e);
          System.out.println("Window Closing");
          System.exit(0);
        }
      });
    f.setSize(700,700);
    f.setVisible(true);
  }
} ///:~

This example also demonstrates the various events that occur and displays the information about them. The class Display is a way to centralize that information display. There’s an array of Strings to hold information about each type of event, and the method show( ) takes a handle to whatever Graphics object you have and writes directly on that surface. The scheme is intended to be somewhat reusable.

EnabledPanel represents the new type of component. It’s a colored panel with a button at the bottom, and it captures all the events that happen over it by using inner listener classes for every single event except those in which EnabledPanel overrides processEvent( ) in the old style (notice it must also call super.processEvent( )). The only reason for using this method is that it captures every event that happens, so you can view everything that goes on. processEvent( ) does nothing more than show the string representation of each event, otherwise it would have to use a cascade of if statements to figure out what event it was. On the other hand, the inner listener classes already know precisely what event occurred. (Assuming you register them to components in which you don’t need any control logic, which should be your goal.) Thus, they don’t have to check anything out; they just do their stuff.

Each listener modifies the Display string associated with its particular event and calls repaint( ) so the strings get displayed. You can also see a trick that will usually eliminate flicker:

  public void update(Graphics g) {
    paint(g);
  }

You don’t always need to override update( ), but if you write something that flickers, try it. The default version of update clears the background and then calls paint( ) to redraw any graphics. This clearing is usually what causes flicker but is not necessary since paint( ) redraws the entire surface.

You can see that there are a lot of listeners – however, type checking occurs for the listeners, and you can’t listen for something that the component doesn’t support (unlike BadTechnique.java, which you will see momentarily).

Experimenting with this program is quite educational since you learn a lot about the way that events occur in Java. For one thing, it shows a flaw in the design of most windowing systems: it’s pretty hard to click and release the mouse without moving it, and the windowing system will often think you’re dragging when you’re actually just trying to click on something. A solution to this is to use mousePressed( ) and mouseReleased( ) instead of mouseClicked( ), and then determine whether to call your own “mouseReallyClicked( )” method based on time and about 4 pixels of mouse hysteresis.

Ugly component inheritance

The alternative, which you will see put forward in many published works, is to call enableEvents( ) and pass it the masks corresponding to the events you want to handle. This causes those events to be sent to the old-style methods (although they’re new to Java 1.1) with names like processFocusEvent( ). You must also remember to call the base-class version. Here’s what it looks like:

//: BadTechnique.java
// It's possible to override components this way,
// but the listener approach is much better, so
// why would you?
import java.awt.*;
import java.awt.event.*;

class Display {
  public static final int
    EVENT = 0, COMPONENT = 1,
    MOUSE = 2, MOUSE_MOVE = 3,
    FOCUS = 4, KEY = 5, ACTION = 6,
    LAST = 7;
  public String[] evnt;
  Display() {
    evnt = new String[LAST];
    for(int i = 0; i < LAST; i++)
      evnt[i] = new String();
  }
  public void show(Graphics g) {
    for(int i = 0; i < LAST; i++)
      g.drawString(evnt[i], 0, 10 * i + 10);
  }
}

class EnabledPanel extends Panel {
  Color c;
  int id;
  Display display = new Display();
  public EnabledPanel(int i, Color mc) {
    id = i;
    c = mc;
    setLayout(new BorderLayout());
    add(new MyButton(), BorderLayout.SOUTH);
    // Type checking is lost. You can enable and
    // process events that the component doesn't
    // capture:
    enableEvents(
      // Panel doesn't handle these:
      AWTEvent.ACTION_EVENT_MASK |
      AWTEvent.ADJUSTMENT_EVENT_MASK |
      AWTEvent.ITEM_EVENT_MASK |
      AWTEvent.TEXT_EVENT_MASK |
      AWTEvent.WINDOW_EVENT_MASK |
      // Panel can handle these:
      AWTEvent.COMPONENT_EVENT_MASK |
      AWTEvent.FOCUS_EVENT_MASK |
      AWTEvent.KEY_EVENT_MASK |
      AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK |
      AWTEvent.CONTAINER_EVENT_MASK);
      // You can enable an event without
      // overriding its process method.
  }
  // To eliminate flicker:
  public void update(Graphics g) {
    paint(g);
  }
  public void paint(Graphics  g) {
    g.setColor(c);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    display.show(g);
  }
  public void processEvent(AWTEvent e) {
    display.evnt[Display.EVENT]= e.toString();
    repaint();
    super.processEvent(e);
  }
  public void
  processComponentEvent(ComponentEvent e) {
    switch(e.getID()) {
      case ComponentEvent.COMPONENT_MOVED:
        display.evnt[Display.COMPONENT] = 
          "Component moved";
        break;
      case ComponentEvent.COMPONENT_RESIZED:
        display.evnt[Display.COMPONENT] = 
          "Component resized";
        break;
      case ComponentEvent.COMPONENT_HIDDEN:
        display.evnt[Display.COMPONENT] = 
          "Component hidden";
        break;
      case ComponentEvent.COMPONENT_SHOWN:
        display.evnt[Display.COMPONENT] = 
          "Component shown";
        break;
      default:
    }
    repaint();
    // Must always remember to call the "super"
    // version of whatever you override:
    super.processComponentEvent(e);
  }
  public void processFocusEvent(FocusEvent e) {
    switch(e.getID()) {
      case FocusEvent.FOCUS_GAINED:
        display.evnt[Display.FOCUS] = 
          "FOCUS gained";
        break;
      case FocusEvent.FOCUS_LOST:
        display.evnt[Display.FOCUS] = 
          "FOCUS lost";
        break;
      default:
    }
    repaint();
    super.processFocusEvent(e);
  }
  public void processKeyEvent(KeyEvent e) {
    switch(e.getID()) {
      case KeyEvent.KEY_PRESSED:
        display.evnt[Display.KEY] = 
          "KEY pressed: "; 
        break;
      case KeyEvent.KEY_RELEASED:
        display.evnt[Display.KEY] = 
          "KEY released: "; 
        break;
      case KeyEvent.KEY_TYPED:
        display.evnt[Display.KEY] = 
          "KEY typed: ";
        break;
      default:
    }
    int code = e.getKeyCode();
    display.evnt[Display.KEY] += 
      KeyEvent.getKeyText(code);
    repaint();
    super.processKeyEvent(e);
  }
  public void processMouseEvent(MouseEvent e) {
    switch(e.getID()) {
      case MouseEvent.MOUSE_CLICKED:
        requestFocus(); // Get FOCUS on click
        display.evnt[Display.MOUSE] = 
          "MOUSE clicked";
        break;
      case MouseEvent.MOUSE_PRESSED:
        display.evnt[Display.MOUSE] = 
          "MOUSE pressed";
        break;
      case MouseEvent.MOUSE_RELEASED:
        display.evnt[Display.MOUSE] = 
          "MOUSE released";
        break;
      case MouseEvent.MOUSE_ENTERED: 
        display.evnt[Display.MOUSE] = 
          "MOUSE entered";
        break;
      case MouseEvent.MOUSE_EXITED: 
        display.evnt[Display.MOUSE] = 
          "MOUSE exited";
        break;
      default:
    }
    display.evnt[Display.MOUSE] += 
      ", x = " + e.getX() + 
      ", y = " + e.getY();
    repaint();
    super.processMouseEvent(e);
  }
  public void
  processMouseMotionEvent(MouseEvent e) {
    switch(e.getID()) {
      case MouseEvent.MOUSE_DRAGGED:
        display.evnt[Display.MOUSE_MOVE] = 
          "MOUSE dragged";
        break;
      case MouseEvent.MOUSE_MOVED:
        display.evnt[Display.MOUSE_MOVE] = 
          "MOUSE moved";
        break;
      default:
    }
    display.evnt[Display.MOUSE_MOVE] += 
      ", x = " + e.getX() + 
      ", y = " + e.getY();
    repaint();
    super.processMouseMotionEvent(e);
  }
}

class MyButton extends Button {
  int clickCounter;
  String label = "";
  public MyButton() {
    enableEvents(AWTEvent.ACTION_EVENT_MASK);
  }
  public void paint(Graphics g) {
    g.setColor(Color.green);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, s.width - 1, s.height - 1);
    drawLabel(g);
  }
  private void drawLabel(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin = 
      (getSize().width - width)/2;
    int verMargin = 
      (getSize().height - height)/2;
    g.setColor(Color.red);
    g.drawString(label, horizMargin, 
                 verMargin + ascent + leading);
  }
  public void processActionEvent(ActionEvent e) {
    clickCounter++;
    label = "click #" + clickCounter +
      " " + e.toString();
    repaint();
    super.processActionEvent(e);
  }
}
  
public class BadTechnique extends Frame {
  BadTechnique() {
    setLayout(new GridLayout(2,2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
    // You can also do it for Windows:
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
  }
  public void processWindowEvent(WindowEvent e) {
    System.out.println(e);
    if(e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.out.println("Window Closing");
      System.exit(0);
    }
  }
  public static void main(String[] args) {
    Frame f = new BadTechnique();
    f.setTitle("Bad Technique");
    f.setSize(700,700);
    f.setVisible(true);
  }
} ///:~

Sure, it works. But it’s ugly and hard to write, read, debug, maintain, and reuse. So why bother when you can use inner listener classes?

Java 1.1 UI APIs

Java 1.1 has also added some important new functionality, including focus traversal, desktop color access, printing “inside the sandbox,” and the beginnings of clipboard support.

Focus traversal is quite easy, since it’s transparently present in the AWT library components and you don’t have to do anything to make it work. If you make your own components and want them to handle focus traversal, you override isFocusTraversable( ) to return true. If you want to capture the keyboard focus on a mouse click, you catch the mouse down event and call requestFocus( ).

Desktop colors

The desktop colors provide a way for you to know what the various color choices are on the current user’s desktop. This way, you can use those colors in your program if you desire. The colors are automatically initialized and placed in static members of class SystemColor, so all you need to do is read the member you’re interested in. The names are intentionally self-explanatory: desktop, activeCaption, activeCaptionText, activeCaptionBorder, inactiveCaption, inactiveCaptionText, inactiveCaptionBorder, window, windowBorder, windowText, menu, menuText, text, textText, textHighlight, textHighlightText, textInactiveText, control, controlText, controlHighlight, controlLtHighlight, controlShadow, controlDkShadow, scrollbar, info (for help), and infoText (for help text).

Printing

Unfortunately, there isn’t much that’s automatic with printing. Instead you must go through a number of mechanical, non-OO steps in order to print. Printing a component graphically can be slightly more automatic: by default, the print( ) method calls paint( ) to do its work. There are times when this is satisfactory, but if you want to do anything more specialized you must know that you’re printing so you can in particular find out the page dimensions.

The following example demonstrates the printing of both text and graphics, and the different approaches you can use for printing graphics. In addition, it tests the printing support:

//: PrintDemo.java
// Printing with Java 1.1
import java.awt.*;
import java.awt.event.*;

public class PrintDemo extends Frame {
  Button 
    printText = new Button("Print Text"),
    printGraphics = new Button("Print Graphics");
  TextField ringNum = new TextField(3);
  Choice faces = new Choice();
  Graphics g = null;
  Plot plot = new Plot3(); // Try different plots
  Toolkit tk = Toolkit.getDefaultToolkit();
  public PrintDemo() {
    ringNum.setText("3");
    ringNum.addTextListener(new RingL());
    Panel p = new Panel();
    p.setLayout(new FlowLayout());
    printText.addActionListener(new TBL());
    p.add(printText);
    p.add(new Label("Font:"));
    p.add(faces);
    printGraphics.addActionListener(new GBL());
    p.add(printGraphics);
    p.add(new Label("Rings:"));
    p.add(ringNum);
    setLayout(new BorderLayout());
    add(p, BorderLayout.NORTH);
    add(plot, BorderLayout.CENTER);
    String[] fontList = tk.getFontList();
    for(int i = 0; i < fontList.length; i++)
      faces.add(fontList[i]);
    faces.select("Serif");
  }
  class PrintData {
    public PrintJob pj;
    public int pageWidth, pageHeight;
    PrintData(String jobName) {
      pj = getToolkit().getPrintJob(
        PrintDemo.this, jobName, null);
      if(pj != null) {
        pageWidth = pj.getPageDimension().width;
        pageHeight= pj.getPageDimension().height;
        g = pj.getGraphics();
      }
    }
    void end() { pj.end(); }
  }
  class ChangeFont {
    private int stringHeight;
    ChangeFont(String face, int style,int point){
      if(g != null) {
        g.setFont(new Font(face, style, point));
        stringHeight = 
          g.getFontMetrics().getHeight();
      }
    }
    int stringWidth(String s) {
      return g.getFontMetrics().stringWidth(s);
    }
    int stringHeight() { return stringHeight; }
  }
  class TBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      PrintData pd = 
        new PrintData("Print Text Test");
      // Null means print job canceled:
      if(pd == null) return;
      String s = "PrintDemo";
      ChangeFont cf = new ChangeFont(
        faces.getSelectedItem(), Font.ITALIC,72);
      g.drawString(s, 
        (pd.pageWidth - cf.stringWidth(s)) / 2,
        (pd.pageHeight - cf.stringHeight()) / 3);

      s = "A smaller point size";
      cf = new ChangeFont(
        faces.getSelectedItem(), Font.BOLD, 48);
      g.drawString(s, 
        (pd.pageWidth - cf.stringWidth(s)) / 2,
        (int)((pd.pageHeight - 
           cf.stringHeight())/1.5));
      g.dispose();
      pd.end();
    }
  }
  class GBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      PrintData pd = 
        new PrintData("Print Graphics Test");
      if(pd == null) return;
      plot.print(g);
      g.dispose();
      pd.end();
    }
  }
  class RingL implements TextListener {
    public void textValueChanged(TextEvent e) {
      int i = 1;
      try {
        i = Integer.parseInt(ringNum.getText());
      } catch(NumberFormatException ex) {
        i = 1;
      }
      plot.rings = i;
      plot.repaint();
    }
  }
  public static void main(String[] args) {
    Frame pdemo = new PrintDemo();
    pdemo.setTitle("Print Demo");
    pdemo.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    pdemo.setSize(500, 500);
    pdemo.setVisible(true);
  }
}

class Plot extends Canvas {
  public int rings = 3;
}

class Plot1 extends Plot {
  // Default print() calls paint():
  public void paint(Graphics g) {
    int w = getSize().width;
    int h = getSize().height;
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} 

class Plot2 extends Plot {
  // To fit the picture to the page, you must
  // know whether you're printing or painting:
  public void paint(Graphics g) {
    int w, h;
    if(g instanceof PrintGraphics) {
      PrintJob pj = 
        ((PrintGraphics)g).getPrintJob();
      w = pj.getPageDimension().width;
      h = pj.getPageDimension().height;
    } 
    else {
      w = getSize().width;
      h = getSize().height;
    }
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} 

class Plot3 extends Plot {
  // Somewhat better. Separate 
  // printing from painting:
  public void print(Graphics g) {
    // Assume it's a PrintGraphics object:
    PrintJob pj = 
      ((PrintGraphics)g).getPrintJob();
    int w = pj.getPageDimension().width;
    int h = pj.getPageDimension().height;
    doGraphics(g, w, h);
  }
  public void paint(Graphics g) {
    int w = getSize().width;
    int h = getSize().height;
    doGraphics(g, w, h);
  }
  private void doGraphics(
      Graphics g, int w, int h) {
    int xc = w / 2;
    int yc = w / 2;
    int x = 0, y = 0;
    for(int i = 0; i < rings; i++) {
      if(x < xc && y < yc) {
        g.drawOval(x, y, w, h);
        x += 10; y += 10;
        w -= 20; h -= 20;
      }
    }
  }
} ///:~

The program allows you to select fonts from a Choice list (and you’ll see that the number of fonts available in Java 1.1 is still extremely limited, and has nothing to do with any extra fonts you install on your machine). It uses these to print out text in bold, italic, and in different sizes. In addition, a new type of component called a Plot is created to demonstrate graphics. A Plot has rings that it will display on the screen and print onto paper, and the three derived classes Plot1, Plot2, and Plot3 perform these tasks in different ways so that you can see your alternatives when printing graphics. Also, you can change the number of rings in a plot – this is interesting because it shows the printing fragility in Java 1.1. On my system, the printer gave error messages and didn’t print correctly when the ring count got “too high” (whatever that means), but worked fine when the count was “low enough.” You will notice, too, that the page dimensions produced when printing do not seem to correspond to the actual dimensions of the page. This might be fixed in a future release of Java, and you can use this program to test it.

This program encapsulates functionality inside inner classes whenever possible, to facilitate reuse. For example, whenever you want to begin a print job (whether for graphics or text), you must create a PrintJob object, which has its own Graphics object along with the width and height of the page. The creation of a PrintJob and extraction of page dimensions is encapsulated in the PrintData class.

Printing text

Conceptually, printing text is straightforward: you choose a typeface and size, decide where the string should go on the page, and draw it with Graphics.drawString( ). This means, however, that you must perform the calculations of exactly where each line will go on the page to make sure it doesn’t run off the end of the page or collide with other lines. If you want to make a word processor, your work is cut out for you.

ChangeFont encapsulates a little of the process of changing from one font to another by automatically creating a new Font object with your desired typeface, style (Font.BOLD or Font.ITALIC – there’s no support for underline, strikethrough, etc.), and point size. It also simplifies the calculation of the width and height of a string.

When you press the “Print text” button, the TBL listener is activated. You can see that it goes through two iterations of creating a ChangeFont object and calling drawString( ) to print out the string in a calculated position, centered, one-third, and two-thirds down the page, respectively. Notice whether these calculations produce the expected results. (They didn’t with the version I used.)

Printing graphics

When you press the “Print graphics” button the GBL listener is activated. The creation of a PrintData object initializes g, and then you simply call print( ) for the component you want to print. To force printing you must call dispose( ) for the Graphics object and end( ) for the PrintData object (which turns around and calls end( ) for the PrintJob).

The work is going on inside the Plot object. You can see that the base-class Plot is simple – it extends Canvas and contains an int called rings to indicate how many concentric rings to draw on this particular Canvas. The three derived classes show different approaches to accomplishing the same goal: drawing on both the screen and on the printed page.

Plot1 takes the simplest approach to coding: ignore the fact that there are differences in painting and printing, and just override paint( ). The reason this works is that the default print( ) method simply turns around and calls paint( ). However, you’ll notice that the size of the output depends on the size of the on-screen canvas, which makes sense since the width and height are determined by calling Canvas.getSize( ). The other situation in which this is acceptable is if your image is always a fixed size.

When the size of the drawing surface is important, then you must discover the dimensions. Unfortunately, this turns out to be awkward, as you can see in Plot2. For some possibly good reason that I don’t know, you cannot simply ask the Graphics object the dimensions of its drawing surface. This would have made the whole process quite elegant. Instead, to see if you’re printing rather than painting, you must detect the PrintGraphics using the RTTI instanceof keyword (described in Chapter 11), then downcast and call the sole PrintGraphics method: getPrintJob( ). Now you have a handle to the PrintJob and you can find out the width and height of the paper. This is a hacky approach, but perhaps there is some rational reason for it. (On the other hand, you’ve seen some of the other library designs by now so you might get the impression that the designers were, in fact, just hacking around...)

You can see that paint( ) in Plot2 goes through both possibilities of printing or painting. But since the print( ) method should be called when printing, why not use that? This approach is used in Plot3, and it eliminates the need to use instanceof since inside print( ) you can assume that you can cast to a PrintGraphics object. This is a little better. The situation is improved by placing the common drawing code (once the dimensions have been detected) inside a separate method doGraphics( ).

Running Frames within applets

What if you’d like to print from within an applet? Well, to print anything you must get a PrintJob object through a Toolkit object’s getPrintJob( ) method, which takes only a Frame object and not an Applet. Thus it would seem that it’s possible to print from within an application, but not an applet. However, it turns out that you can create a Frame from within an applet (which is the reverse of what I’ve been doing for the applet/application examples so far, which has been making an applet and putting inside a Frame). This is a useful technique since it allows you to use many applications within applets (as long as they don’t violate applet security). When the application window comes up within an applet, however, you’ll notice that the Web browser sticks a little caveat on it, something to the effect of “Warning: Applet Window.”

You can see that it’s quite straightforward to put a Frame inside an applet. The only thing that you must add is code to dispose( ) of the Frame when the user closes it (instead of calling System.exit( )):

//: PrintDemoApplet.java
// Creating a Frame from within an Applet
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class PrintDemoApplet extends Applet {
  public void init() {
    Button b = new Button("Run PrintDemo");
    b.addActionListener(new PDL());
    add(b);
  }
  class PDL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      final PrintDemo pd = new PrintDemo();
      pd.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          pd.dispose();
        }
      });
      pd.setSize(500, 500);
      pd.show();
    }
  }
} ///:~

There’s some confusion involved with Java 1.1 printing support. Some of the publicity seemed to claim that you’d be able to print from within an applet. However, the Java security system contains a feature that could lock out an applet from initiating its own print job, requiring that the initiation be done via a Web browser or applet viewer. At the time of this writing, this seemed to remain an unresolved issue. When I ran this program from within a Web browser, the PrintDemo window came up just fine, but it wouldn’t print from the browser.

The clipboard

Java 1.1 supports limited operations with the system clipboard (in the java.awt.datatransfer package). You can copy String objects to the clipboard as text, and you can paste text from the clipboard into String objects. Of course, the clipboard is designed to hold any type of data, but how this data is represented on the clipboard is up to the program doing the cutting and pasting. Although it currently supports only string data, the Java clipboard API provides for extensibility through the concept of a “flavor.” When data comes off the clipboard, it has an associated set of flavors that it can be converted to (for example, a graph might be represented as a string of numbers or as an image) and you can see if that particular clipboard data supports the flavor you’re interested in.

The following program is a simple demonstration of cut, copy, and paste with String data in a TextArea. One thing you’ll notice is that the keyboard sequences you normally use for cutting, copying, and pasting also work. But if you look at any TextField or TextArea in any other program you’ll find that they also automatically support the clipboard key sequences. This example simply adds programmatic control of the clipboard, and you could use these techniques if you want to capture clipboard text into some non-TextComponent.

//: CutAndPaste.java
// Using the clipboard from Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;

public class CutAndPaste extends Frame {
  MenuBar mb = new MenuBar();
  Menu edit = new Menu("Edit");
  MenuItem
    cut = new MenuItem("Cut"),
    copy = new MenuItem("Copy"),
    paste = new MenuItem("Paste");
  TextArea text = new TextArea(20,20);
  Clipboard clipbd = 
    getToolkit().getSystemClipboard();
  public CutAndPaste() {
    cut.addActionListener(new CutL());
    copy.addActionListener(new CopyL());
    paste.addActionListener(new PasteL());
    edit.add(cut);
    edit.add(copy);
    edit.add(paste);
    mb.add(edit);
    setMenuBar(mb);
    add(text, BorderLayout.CENTER);
  }
  class CopyL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      StringSelection clipString = 
        new StringSelection(selection);
      clipbd.setContents(clipString, clipString);
    }
  }
  class CutL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      StringSelection clipString = 
        new StringSelection(selection);
      clipbd.setContents(clipString, clipString);
      text.replaceRange("", 
        text.getSelectionStart(), 
        text.getSelectionEnd());
    }
  }
  class PasteL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      Transferable clipData =
        clipbd.getContents(CutAndPaste.this);
      try {
        String clipString = 
          (String)clipData.
            getTransferData(
              DataFlavor.stringFlavor);
        text.replaceRange(clipString, 
          text.getSelectionStart(), 
          text.getSelectionEnd());
      } catch(Exception ex) {
        System.out.println("not String flavor");
      }
    }
  }
  public static void main(String[] args) {
    CutAndPaste cp = new CutAndPaste();
    cp.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    cp.setSize(300,200);
    cp.setVisible(true);
  }
} ///:~

The creation and addition of the menu and TextArea should by now seem a pedestrian activity. What’s different is the creation of the Clipboard field clipbd, which is done through the Toolkit.

All the action takes place in the listeners. The CopyL and CutL listeners are the same except for the last line of CutL, which erases the line that’s been copied. The special two lines are the creation of a StringSelection object from the String and the call to setContents( ) with this StringSelection. That’s all there is to putting a String on the clipboard.

In PasteL, data is pulled off the clipboard using getContents( ). What comes back is a fairly anonymous Transferable object, and you don’t really know what it contains. One way to find out is to call getTransferDataFlavors( ), which returns an array of DataFlavor objects indicating which flavors are supported by this particular object. You can also ask it directly with isDataFlavorSupported( ), passing in the flavor you’re interested in. Here, however, the bold approach is taken: getTransferData( ) is called assuming that the contents supports the String flavor, and if it doesn’t the problem is sorted out in the exception handler.

In the future you can expect more data flavors to be supported.

Visual programming
and Beans

So far in this book you’ve seen how valuable Java is for creating reusable pieces of code. The “most reusable” unit of code has been the class, since it comprises a cohesive unit of characteristics (fields) and behaviors (methods) that can be reused either directly via composition or through inheritance.

Inheritance and polymorphism are essential parts of object-oriented programming, but in the majority of cases when you’re putting together an application, what you really want is components that do exactly what you need. You’d like to drop these parts into your design like the electronic engineer puts together chips on a circuit board (or even, in the case of Java, onto a Web page). It seems, too, that there should be some way to accelerate this “modular assembly” style of programming.

Visual programming” first became successful – very successful – with Microsoft’s Visual Basic (VB), followed by a second-generation design in Borland’s Delphi (the primary inspiration for the Java Beans design). With these programming tools the components are represented visually, which makes sense since they usually display some kind of visual component such as a button or a text field. The visual representation, in fact, is often exactly the way the component will look in the running program. So part of the process of visual programming involves dragging a component from a pallet and dropping it onto your form. The application builder tool writes code as you do this, and that code will cause the component to be created in the running program.

Simply dropping the component onto a form is usually not enough to complete the program. Often, you must change the characteristics of a component, such as what color it is, what text is on it, what database it’s connected to, etc. Characteristics that can be modified at design time are referred to as properties. You can manipulate the properties of your component inside the application builder tool, and when you create the program this configuration data is saved so that it can be rejuvenated when the program is started.

By now you’re probably used to the idea that an object is more than characteristics; it’s also a set of behaviors. At design-time, the behaviors of a visual component are partially represented by events, meaning “Here’s something that can happen to the component.” Ordinarily, you decide what you want to happen when an event occurs by tying code to that event.

Here’s the critical part: the application builder tool is able to dynamically interrogate (using reflection) the component to find out which properties and events the component supports. Once it knows what they are, it can display the properties and allow you to change those (saving the state when you build the program), and also display the events. In general, you do something like double clicking on an event and the application builder tool creates a code body and ties it to that particular event. All you have to do at that point is write the code that executes when the event occurs.

All this adds up to a lot of work that’s done for you by the application builder tool. As a result you can focus on what the program looks like and what it is supposed to do, and rely on the application builder tool to manage the connection details for you. The reason that visual programming tools have been so successful is that they dramatically speed up the process of building an application – certainly the user interface, but often other portions of the application as well.

What is a Bean?

After the dust settles, then, a component is really just a block of code, typically embodied in a class. The key issue is the ability for the application builder tool to discover the properties and events for that component. To create a VB component, the programmer had to write a fairly complicated piece of code following certain conventions to expose the properties and events. Delphi was a second-generation visual programming tool and the language was actively designed around visual programming so it is much easier to create a visual component. However, Java has brought the creation of visual components to its most advanced state with Java Beans, because a Bean is just a class. You don’t have to write any extra code or use special language extensions in order to make something a Bean. The only thing you need to do, in fact, is slightly modify the way that you name your methods. It is the method name that tells the application builder tool whether this is a property, an event, or just an ordinary method.

In the Java documentation, this naming convention is mistakenly termed a “design pattern.” This is unfortunate since design patterns (see Chapter 16) are challenging enough without this sort of confusion. It’s not a design pattern, it’s just a naming convention and it’s fairly simple:

  1. For a property named xxx, you typically create two methods: getXxx( ) and setXxx( ). Note that the first letter after get or set is automatically lowercased to produce the property name. The type produced by the “get” method is the same as the type of the argument to the “set” method. The name of the property and the type for the “get” and “set” are not related.
  2. For a boolean property, you can use the “get” and “set” approach above, but you can also use “is” instead of “get.”
  3. Ordinary methods of the Bean don’t conform to the above naming convention, but they’re public.
  4. For events, you use the “listener” approach. It’s exactly the same as you’ve been seeing: addFooBarListener(FooBarListener) and removeFooBarListener(FooBarListener) to handle a FooBarEvent. Most of the time the built-in events and listeners will satisfy your needs, but you can also create your own events and listener interfaces.

Point 1 above answers a question about something you might have noticed in the change from Java 1.0 to Java 1.1: a number of method names have had small, apparently meaningless name changes. Now you can see that most of those changes had to do with adapting to the “get” and “set” naming conventions in order to make that particular component into a Bean.

We can use these guidelines to create a simple Bean:

//: Frog.java
// A trivial Java Bean
package frogbean;
import java.awt.*;
import java.awt.event.*;

class Spots {}

public class Frog {
  private int jumps;
  private Color color;
  private Spots spots;
  private boolean jmpr;
  public int getJumps() { return jumps; }
  public void setJumps(int newJumps) { 
    jumps = newJumps;
  }
  public Color getColor() { return color; }
  public void setColor(Color newColor) { 
    color = newColor; 
  }
  public Spots getSpots() { return spots; }
  public void setSpots(Spots newSpots) {
    spots = newSpots; 
  }
  public boolean isJumper() { return jmpr; }
  public void setJumper(boolean j) { jmpr = j; }
  public void addActionListener(
      ActionListener l) {
    //...
  }
  public void removeActionListener(
      ActionListener l) {
    // ...
  }
  public void addKeyListener(KeyListener l) {
    // ...
  }
  public void removeKeyListener(KeyListener l) {
    // ...
  }
  // An "ordinary" public method:
  public void croak() {
    System.out.println("Ribbet!");
  }
} ///:~

First, you can see that it’s just a class. Usually, all your fields will be private, and accessible only through methods. Following the naming convention, the properties are jumps, color, spots, and jumper (notice the change in case of the first letter in the property name). Although the name of the internal identifier is the same as the name of the property in the first three cases, in jumper you can see that the property name does not force you to use any particular name for internal variables (or, indeed, to even have any internal variable for that property).

The events this Bean handles are ActionEvent and KeyEvent, based on the naming of the “add” and “remove” methods for the associated listener. Finally, you can see that the ordinary method croak( ) is still part of the Bean simply because it’s a public method, not because it conforms to any naming scheme.

Extracting BeanInfo
with the Introspector

One of the most critical parts of the Bean scheme occurs when you drag a Bean off a palette and plop it down on a form. The application builder tool must be able to create the Bean (which it can do if there’s a default constructor) and then, without access to the Bean’s source code, extract all the necessary information to create the property sheet and event handlers.

Part of the solution is already evident from the end of Chapter 11: Java 1.1 reflection allows all the methods of an anonymous class to be discovered. This is perfect for solving the Bean problem without requiring you to use any extra language keywords like those required in other visual programming languages. In fact, one of the prime reasons that reflection was added to Java 1.1 was to support Beans (although reflection also supports object serialization and remote method invocation). So you might expect that the creator of the application builder tool would have to reflect each Bean and hunt through its methods to find the properties and events for that Bean.

This is certainly possible, but the Java designers wanted to provide a standard interface for everyone to use, not only to make Beans simpler to use but also to provide a standard gateway to the creation of more complex Beans. This interface is the Introspector class, and the most important method in this class is the static getBeanInfo( ). You pass a Class handle to this method and it fully interrogates that class and returns a BeanInfo object that you can then dissect to find properties, methods, and events.

Usually you won’t care about any of this – you’ll probably get most of your Beans off the shelf from vendors, and you don’t need to know all the magic that’s going on underneath. You’ll simply drag your Beans onto your form, then configure their properties and write handlers for the events you’re interested in. However, it’s an interesting and educational exercise to use the Introspector to display information about a Bean, so here’s a tool that does it (you’ll find it in the frogbean subdirectory):

//: BeanDumper.java
// A method to introspect a Bean
import java.beans.*;
import java.lang.reflect.*;

public class BeanDumper {
  public static void dump(Class bean){
    BeanInfo bi = null;
    try {
      bi = Introspector.getBeanInfo(
        bean, java.lang.Object.class);
    } catch(IntrospectionException ex) {
      System.out.println("Couldn't introspect " +
        bean.getName());
      System.exit(1);
    }
    PropertyDescriptor[] properties = 
      bi.getPropertyDescriptors();
    for(int i = 0; i < properties.length; i++) {
      Class p = properties[i].getPropertyType();
      System.out.println(
        "Property type:\n  " + p.getName());
      System.out.println(
        "Property name:\n  " + 
        properties[i].getName());
      Method readMethod = 
        properties[i].getReadMethod();
      if(readMethod != null)
        System.out.println(
          "Read method:\n  " + 
          readMethod.toString());
      Method writeMethod = 
        properties[i].getWriteMethod();
      if(writeMethod != null)
        System.out.println(
          "Write method:\n  " +
          writeMethod.toString());
      System.out.println("====================");
    }
    System.out.println("Public methods:");
    MethodDescriptor[] methods =
      bi.getMethodDescriptors();
    for(int i = 0; i < methods.length; i++)
      System.out.println(
        methods[i].getMethod().toString());
    System.out.println("======================");
    System.out.println("Event support:");
    EventSetDescriptor[] events = 
      bi.getEventSetDescriptors();
    for(int i = 0; i < events.length; i++) {
      System.out.println("Listener type:\n  " +
        events[i].getListenerType().getName());
      Method[] lm = 
        events[i].getListenerMethods();
      for(int j = 0; j < lm.length; j++)
        System.out.println(
          "Listener method:\n  " +
          lm[j].getName());
      MethodDescriptor[] lmd = 
        events[i].getListenerMethodDescriptors();
      for(int j = 0; j < lmd.length; j++)
        System.out.println(
          "Method descriptor:\n  " +
          lmd[j].getMethod().toString());
      Method addListener = 
        events[i].getAddListenerMethod();
      System.out.println(
          "Add Listener Method:\n  " +
        addListener.toString());
      Method removeListener =
        events[i].getRemoveListenerMethod();
      System.out.println(
        "Remove Listener Method:\n  " +
        removeListener.toString());
      System.out.println("====================");
    }
  }
  // Dump the class of your choice:
  public static void main(String[] args) {
    if(args.length < 1) {
      System.err.println("usage: \n" +
        "BeanDumper fully.qualified.class");
      System.exit(0);
    }
    Class c = null;
    try {
      c = Class.forName(args[0]);
    } catch(ClassNotFoundException ex) {
      System.err.println(
        "Couldn't find " + args[0]);
      System.exit(0);
    }
    dump(c);
  }
} ///:~

BeanDumper.dump( ) is the method that does all the work. First it tries to create a BeanInfo object, and if successful calls the methods of BeanInfo that produce information about properties, methods, and events. In Introspector.getBeanInfo( ), you’ll see there is a second argument. This tells the Introspector where to stop in the inheritance hierarchy. Here, it stops before it parses all the methods from Object, since we’re not interested in seeing those.

For properties, getPropertyDescriptors( ) returns an array of PropertyDescriptors. For each PropertyDescriptor you can call getPropertyType( ) to find the class of object that is passed in and out via the property methods. Then, for each property you can get its pseudonym (extracted from the method names) with getName( ), the method for reading with getReadMethod( ), and the method for writing with getWriteMethod( ). These last two methods return a Method object that can actually be used to invoke the corresponding method on the object (this is part of reflection).

For the public methods (including the property methods), getMethodDescriptors( ) returns an array of MethodDescriptors. For each one you can get the associated Method object and print out its name.

For the events, getEventSetDescriptors( ) returns an array of (what else?) EventSetDescriptors. Each of these can be queried to find out the class of the listener, the methods of that listener class, and the add- and remove-listener methods. The BeanDumper program prints out all of this information.

If you invoke BeanDumper on the Frog class like this:

java BeanDumper frogbean.Frog

the output, after removing extra details that are unnecessary here, is:

class name: Frog
Property type:
  Color
Property name:
  color
Read method:
  public Color getColor()
Write method:
  public void setColor(Color)
====================
Property type:
  Spots
Property name:
  spots
Read method:
  public Spots getSpots()
Write method:
  public void setSpots(Spots)
====================
Property type:
  boolean
Property name:
  jumper
Read method:
  public boolean isJumper()
Write method:
  public void setJumper(boolean)
====================
Property type:
  int
Property name:
  jumps
Read method:
  public int getJumps()
Write method:
  public void setJumps(int)
====================
Public methods:
public void setJumps(int)
public void croak()
public void removeActionListener(ActionListener)
public void addActionListener(ActionListener)
public int getJumps()
public void setColor(Color)
public void setSpots(Spots)
public void setJumper(boolean)
public boolean isJumper()
public void addKeyListener(KeyListener)
public Color getColor()
public void removeKeyListener(KeyListener)
public Spots getSpots()
======================
Event support:
Listener type:
  KeyListener
Listener method:
  keyTyped
Listener method:
  keyPressed
Listener method:
  keyReleased
Method descriptor:
  public void keyTyped(KeyEvent)
Method descriptor:
  public void keyPressed(KeyEvent)
Method descriptor:
  public void keyReleased(KeyEvent)
Add Listener Method:
  public void addKeyListener(KeyListener)
Remove Listener Method:
  public void removeKeyListener(KeyListener)
====================
Listener type:
  ActionListener
Listener method:
  actionPerformed
Method descriptor:
  public void actionPerformed(ActionEvent)
Add Listener Method:
  public void addActionListener(ActionListener)
Remove Listener Method:
  public void removeActionListener(ActionListener)
====================

This reveals most of what the Introspector sees as it produces a BeanInfo object from your Bean. You can see that the type of the property and its name are independent. Notice the lowercasing of the property name. (The only time this doesn’t occur is when the property name begins with more than one capital letter in a row.) And remember that the method names you’re seeing here (such as the read and write methods) are actually produced from a Method object that can be used to invoke the associated method on the object.

The public method list includes the methods that are not associated with a property or event, such as croak( ), as well as those that are. These are all the methods that you can call programmatically for a Bean, and the application builder tool can choose to list all of these while you’re making method calls, to ease your task.

Finally, you can see that the events are fully parsed out into the listener, its methods, and the add- and remove-listener methods. Basically, once you have the BeanInfo, you can find out everything of importance for the Bean. You can also call the methods for that Bean, even though you don’t have any other information except the object (again, a feature of reflection).

A more sophisticated Bean

This next example is slightly more sophisticated, albeit frivolous. It’s a canvas that draws a little circle around the mouse whenever the mouse is moved. When you press the mouse, the word “Bang!” appears in the middle of the screen, and an action listener is fired.

The properties you can change are the size of the circle as well as the color, size, and text of the word that is displayed when you press the mouse. A BangBean also has its own addActionListener( ) and removeActionListener( ) so you can attach your own listener that will be fired when the user clicks on the BangBean. You should be able to recognize the property and event support:

//: BangBean.java
// A graphical Bean
package bangbean;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;

public class BangBean extends Canvas
     implements Serializable {
  protected int xm, ym;
  protected int cSize = 20; // Circle size
  protected String text = "Bang!";
  protected int fontSize = 48;
  protected Color tColor = Color.red;
  protected ActionListener actionListener;
  public BangBean() {
    addMouseListener(new ML());
    addMouseMotionListener(new MML());
  }
  public int getCircleSize() { return cSize; }
  public void setCircleSize(int newSize) {
    cSize = newSize;
  }
  public String getBangText() { return text; }
  public void setBangText(String newText) {
    text = newText;
  }
  public int getFontSize() { return fontSize; }
  public void setFontSize(int newSize) {
    fontSize = newSize;
  }
  public Color getTextColor() { return tColor; }
  public void setTextColor(Color newColor) {
    tColor = newColor;
  }
  public void paint(Graphics g) {
    g.setColor(Color.black);
    g.drawOval(xm - cSize/2, ym - cSize/2, 
      cSize, cSize);
  }
  // This is a unicast listener, which is
  // the simplest form of listener management:
  public void addActionListener (
      ActionListener l) 
        throws TooManyListenersException {
    if(actionListener != null)
      throw new TooManyListenersException();
    actionListener = l;
  }
  public void removeActionListener(
      ActionListener l) {
    actionListener = null;
  }
  class ML extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      Graphics g = getGraphics();
      g.setColor(tColor);
      g.setFont(
        new Font(
          "TimesRoman", Font.BOLD, fontSize));
      int width = 
        g.getFontMetrics().stringWidth(text);
      g.drawString(text, 
        (getSize().width - width) /2,
        getSize().height/2);
      g.dispose();
      // Call the listener's method:
      if(actionListener != null)
        actionListener.actionPerformed(
          new ActionEvent(BangBean.this,
            ActionEvent.ACTION_PERFORMED, null));
    }
  }
  class MML extends MouseMotionAdapter {
    public void mouseMoved(MouseEvent e) {
      xm = e.getX();
      ym = e.getY();
      repaint();
    }
  }
  public Dimension getPreferredSize() {
    return new Dimension(200, 200);
  }
  // Testing the BangBean:
  public static void main(String[] args) {
    BangBean bb = new BangBean();
    try {
      bb.addActionListener(new BBL());
    } catch(TooManyListenersException e) {}
    Frame aFrame = new Frame("BangBean Test");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(bb, BorderLayout.CENTER);
    aFrame.setSize(300,300);
    aFrame.setVisible(true);
  }
  // During testing, send action information
  // to the console:
  static class BBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("BangBean action");
    }
  }
} ///:~

The first thing you’ll notice is that BangBean implements the Serializable interface. This means that the application builder tool can “pickle” all the information for the BangBean using serialization after the program designer has adjusted the values of the properties. When the Bean is created as part of the running application, these “pickled” properties are restored so that you get exactly what you designed.

You can see that all the fields are private, which is what you’ll usually do with a Bean – allow access only through methods, usually using the “property” scheme.

When you look at the signature for addActionListener( ), you’ll see that it can throw a TooManyListenersException. This indicates that it is unicast, which means it notifies only one listener when the event occurs. Ordinarily, you’ll use multicast events so that many listeners can be notified of an event. However, that runs into issues that you won’t be ready for until the next chapter, so it will be revisited there (under the heading “Java Beans revisited”). A unicast event sidesteps the problem.

When you press the mouse, the text is put in the middle of the BangBean, and if the actionListener field is not null, its actionPerformed( ) is called, creating a new ActionEvent object in the process. Whenever the mouse is moved, its new coordinates are captured and the canvas is repainted (erasing any text that’s on the canvas, as you’ll see).

The main( ) is added to allow you to test the program from the command line. When a Bean is in a development environment, main( ) will not be used, but it’s helpful to have a main( ) in each of your Beans because it provides for rapid testing. main( ) creates a Frame and places a BangBean within it, attaching a simple ActionListener to the BangBean to print to the console whenever an ActionEvent occurs. Usually, of course, the application builder tool would create most of the code that uses the Bean.

When you run the BangBean through BeanDumper or put the BangBean inside a Bean-enabled development environment, you’ll notice that there are many more properties and actions than are evident from the above code. That’s because BangBean is inherited from Canvas, and Canvas is a Bean, so you’re seeing its properties and events as well.

Packaging a Bean

Before you can bring a Bean into a Bean-enabled visual builder tool, it must be put into the standard Bean container, which is a JAR (Java ARchive) file that includes all the Bean classes as well as a “manifest” file that says “This is a Bean.” A manifest file is simply a text file that follows a particular form. For the BangBean, the manifest file looks like this:

Manifest-Version: 1.0

Name: bangbean/BangBean.class
Java-Bean: True

The first line indicates the version of the manifest scheme, which until further notice from Sun is 1.0. The second line (empty lines are ignored) names the BangBean.class file, and the third says, “It’s a Bean.” Without the third line, the program builder tool will not recognize the class as a Bean.

The only tricky part is that you must make sure that you get the proper path in the “Name:” field. If you look back at BangBean.java, you’ll see it’s in package bangbean (and thus in a subdirectory called “bangbean” that’s off of the classpath), and the name in the manifest file must include this package information. In addition, you must place the manifest file in the directory above the root of your package path, which in this case means placing the file in the directory above the “bangbean” subdirectory. Then you must invoke jar from the same directory as the manifest file, as follows:

jar cfm BangBean.jar BangBean.mf bangbean

This assumes that you want the resulting JAR file to be named BangBean.jar and that you’ve put the manifest in a file called BangBean.mf.

You might wonder “What about all the other classes that were generated when I compiled BangBean.java?” Well, they all ended up inside the bangbean subdirectory, and you’ll see that the last argument for the above jar command line is the bangbean subdirectory. When you give jar the name of a subdirectory, it packages that entire subdirectory into the jar file (including, in this case, the original BangBean.java source-code file – you might not choose to include the source with your own Beans). In addition, if you turn around and unpack the JAR file you’ve just created, you’ll discover that your manifest file isn’t inside, but that jar has created its own manifest file (based partly on yours) called MANIFEST.MF and placed it inside the subdirectory META-INF (for “meta-information”). If you open this manifest file you’ll also notice that digital signature information has been added by jar for each file, of the form:

Digest-Algorithms: SHA MD5 
SHA-Digest: pDpEAG9NaeCx8aFtqPI4udSX/O0=
MD5-Digest: O4NcS1hE3Smnzlp2hj6qeg==

In general, you don’t need to worry about any of this, and if you make changes you can just modify your original manifest file and re-invoke jar to create a new JAR file for your Bean. You can also add other Beans to the JAR file simply by adding their information to your manifest.

One thing to notice is that you’ll probably want to put each Bean in its own subdirectory, since when you create a JAR file you hand the jar utility the name of a subdirectory and it puts everything in that subdirectory into the JAR file. You can see that both Frog and BangBean are in their own subdirectories.

Once you have your Bean properly inside a JAR file you can bring it into a Beans-enabled program-builder environment. The way you do this varies from one tool to the next, but Sun provides a freely-available test bed for Java Beans in their “Beans Development Kit” (BDK) called the “beanbox.” (Download the BDK from www.javasoft.com.) To place your Bean in the beanbox, copy the JAR file into the BDK’s “jars” subdirectory before you start up the beanbox.

More complex Bean support

You can see how remarkably simple it is to make a Bean. But you aren’t limited to what you’ve seen here. The Java Bean design provides a simple point of entry but can also scale to more complex situations. These situations are beyond the scope of this book but they will be briefly introduced here. You can find more details at http://java.sun.com/beans.

One place where you can add sophistication is with properties. The examples above have shown only single properties, but it’s also possible to represent multiple properties in an array. This is called an indexed property. You simply provide the appropriate methods (again following a naming convention for the method names) and the Introspector recognizes an indexed property so your application builder tool can respond appropriately.

Properties can be bound, which means that they will notify other objects via a PropertyChangeEvent. The other objects can then choose to change themselves based on the change to the Bean.

Properties can be constrained, which means that other objects can veto a change to that property if it is unacceptable. The other objects are notified using a PropertyChangeEvent, and they can throw a ProptertyVetoException to prevent the change from happening and to restore the old values.

You can also change the way your Bean is represented at design time:

  1. You can provide a custom property sheet for your particular Bean. The ordinary property sheet will be used for all other Beans, but yours is automatically invoked when your Bean is selected.
  2. You can create a custom editor for a particular property, so the ordinary property sheet is used, but when your special property is being edited, your editor will automatically be invoked.
  3. You can provide a custom BeanInfo class for your Bean that produces information that’s different from the default created by the Introspector.
  4. It’s also possible to turn “expert” mode on and off in all FeatureDescriptors to distinguish between basic features and more complicated ones.

More to Beans

There’s another issue that couldn’t be addressed here. Whenever you create a Bean, you should expect that it will be run in a multithreaded environment. This means that you must understand the issues of threading, which will be introduced in the next chapter. You’ll find a section there called “Java Beans revisited” that will look at the problem and its solution.

Introduction to Swing[57]

After working your way through this chapter and seeing the huge changes that have occurred within the AWT (although, if you can remember back that far, Sun claimed Java was a “stable” language when it first appeared), you might still have the feeling that it’s not quite done. Sure, there’s now a good event model, and JavaBeans is an excellent component-reuse design. But the GUI components still seem rather minimal, primitive, and awkward.

That’s where Swing comes in. The Swing library appeared after Java 1.1 so you might naturally assume that it’s part of Java 1.2. However, it is designed to work with Java 1.1 as an add-on. This way, you don’t have to wait for your platform to support Java 1.2 in order to enjoy a good UI component library. Your users might actually need to download the Swing library if it isn’t part of their Java 1.1 support, and this could cause a few snags. But it works.

Swing contains all the components that you’ve been missing throughout the rest of this chapter: those you expect to see in a modern UI, everything from buttons that contain pictures to trees and grids. It’s a big library, but it’s designed to have appropriate complexity for the task at hand – if something is simple, you don’t have to write much code but as you try to do more your code becomes increasingly complex. This means an easy entry point, but you’ve got the power if you need it.

Swing has great depth. This section does not attempt to be comprehensive, but instead introduces the power and simplicity of Swing to get you started using the library. Please be aware that what you see here is intended to be simple. If you need to do more, then Swing can probably give you what you want if you’re willing to do the research by hunting through the online documentation from Sun.

Benefits of Swing

When you begin to use the Swing library, you’ll see that it’s a huge step forward. Swing components are Beans (and thus use the Java 1.1 event model), so they can be used in any development environment that supports Beans. Swing provides a full set of UI components. For speed, all the components are lightweight (no “peer” components are used), and Swing is written entirely in Java for portability.

Much of what you’ll like about Swing could be called “orthogonality of use;” that is, once you pick up the general ideas about the library you can apply them everywhere. Primarily because of the Beans naming conventions, much of the time I was writing these examples I could guess at the method names and get it right the first time, without looking anything up. This is certainly the hallmark of a good library design. In addition, you can generally plug components into other components and things will work correctly.

Keyboard navigation is automatic – you can use a Swing application without the mouse, but you don’t have to do any extra programming (the old AWT required some ugly code to achieve keyboard navigation). Scrolling support is effortless – you simply wrap your component in a JScrollPane as you add it to your form. Other features such as tool tips typically require a single line of code to implement.

Swing also supports something called “pluggable look and feel,” which means that the appearance of the UI can be dynamically changed to suit the expectations of users working under different platforms and operating systems. It’s even possible to invent your own look and feel.

Easy conversion

If you’ve struggled long and hard to build your UI using Java 1.1, you don’t want to throw it away to convert to Swing. Fortunately, the library is designed to allow easy conversion – in many cases you can simply put a ‘J’ in front of the class names of each of your old AWT components. Here’s an example that should have a familiar flavor to it:

//: JButtonDemo.java
// Looks like Java 1.1 but with J's added
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import javax.swing.*;

public class JButtonDemo extends Applet {
  JButton 
    b1 = new JButton("JButton 1"),
    b2 = new JButton("JButton 2");
  JTextField t = new JTextField(20);
  public void init() {
    ActionListener al = new ActionListener() {
      public void actionPerformed(ActionEvent e){
        String name = 
          ((JButton)e.getSource()).getText();
        t.setText(name + " Pressed");
      }
    };
    b1.addActionListener(al);
    add(b1);
    b2.addActionListener(al);
    add(b2);
    add(t);
  }
  public static void main(String args[]) {
    JButtonDemo applet = new JButtonDemo();
    JFrame frame = new JFrame("TextAreaNew");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e){
        System.exit(0);
      }
    });
    frame.getContentPane().add(
      applet, BorderLayout.CENTER);
    frame.setSize(300,100);
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
} ///:~

There’s a new import statement, but everything else looks like the Java 1.1 AWT with the addition of some J’s. Also, you don’t just add( ) something to a Swing JFrame, but you must get the “content pane” first, as seen above. But you can easily get many of the benefits of Swing with a simple conversion.

Because of the package statement, you’ll have to invoke this program by saying:

java c13.swing.JbuttonDemo

All of the programs in this section will require a similar form to run them.

A display framework

Although the programs that are both applets and applications can be valuable, if used everywhere they become distracting and waste paper. Instead, a display framework will be used for the Swing examples in the rest of this section:

//: Show.java
// Tool for displaying Swing demos
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Show {
  public static void 
  inFrame(JPanel jp, int width, int height) {
    String title = jp.getClass().toString();
    // Remove the word "class":
    if(title.indexOf("class") != -1)
      title = title.substring(6);
    JFrame frame = new JFrame(title);
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e){
        System.exit(0);
      }
    });
    frame.getContentPane().add(
      jp, BorderLayout.CENTER);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
} ///:~

Classes that want to display themselves should inherit from JPanel and then add any visual components to themselves. Finally, they create a main( ) containing the line:

Show.inFrame(new MyClass(), 500, 300);

in which the last two arguments are the display width and height.

Note that the title for the JFrame is produced using RTTI.

Tool tips

Almost all of the classes that you’ll be using to create your user interfaces are derived from JComponent, which contains a method called setToolTipText(String). So, for virtually anything you place on your form, all you need to do is say (for an object jc of any JComponent-derived class):

jc.setToolTipText("My tip");

and when the mouse stays over that JComponent for a predetermined period of time, a tiny box containing your text will pop up next to the mouse.

Borders

JComponent also contains a method called setBorder( ), which allows you to place various interesting borders on any visible component. The following example demonstrates a number of the different borders that are available, using a method called showBorder( ) that creates a JPanel and puts on the border in each case. Also, it uses RTTI to find the name of the border that you’re using (stripping off all the path information), then puts that name in a JLabel in the middle of the panel:

//: Borders.java
// Different Swing borders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;

public class Borders extends JPanel {
  static JPanel showBorder(Border b) {
    JPanel jp = new JPanel();
    jp.setLayout(new BorderLayout());
    String nm = b.getClass().toString();
    nm = nm.substring(nm.lastIndexOf('.') + 1);
    jp.add(new JLabel(nm, JLabel.CENTER), 
      BorderLayout.CENTER);
    jp.setBorder(b);
    return jp;
  }
  public Borders() {
    setLayout(new GridLayout(2,4));
    add(showBorder(new TitledBorder("Title")));
    add(showBorder(new EtchedBorder()));
    add(showBorder(new LineBorder(Color.blue)));
    add(showBorder(
      new MatteBorder(5,5,30,30,Color.green)));
    add(showBorder(
      new BevelBorder(BevelBorder.RAISED)));
    add(showBorder(
      new SoftBevelBorder(BevelBorder.LOWERED)));
    add(showBorder(new CompoundBorder(
      new EtchedBorder(),
      new LineBorder(Color.red))));
  }
  public static void main(String args[]) {
    Show.inFrame(new Borders(), 500, 300);
  }
} ///:~

Most of the examples in this section use TitledBorder, but you can see that the rest of the borders are as easy to use. You can also create your own borders and put them inside buttons, labels, etc. – anything derived from JComponent.

Buttons

Swing adds a number of different types of buttons, and it also changes the organization of the selection components: all buttons, checkboxes, radio buttons, and even menu items are inherited from AbstractButton (which, since menu items are included, would probably have been better named “AbstractChooser” or something equally general). You’ll see the use of menu items shortly, but the following example shows the various types of buttons available:

//: Buttons.java
// Various Swing buttons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;

public class Buttons extends JPanel {
  JButton jb = new JButton("JButton");
  BasicArrowButton
    up = new BasicArrowButton(
      BasicArrowButton.NORTH),
    down = new BasicArrowButton(
      BasicArrowButton.SOUTH),
    right = new BasicArrowButton(
      BasicArrowButton.EAST),
    left = new BasicArrowButton(
      BasicArrowButton.WEST);
  public Buttons() {
    add(jb);
    add(new JToggleButton("JToggleButton"));
    add(new JCheckBox("JCheckBox"));
    add(new JRadioButton("JRadioButton"));
    JPanel jp = new JPanel();
    jp.setBorder(new TitledBorder("Directions"));
    jp.add(up);
    jp.add(down);
    jp.add(left);
    jp.add(right);
    add(jp);
  }
  public static void main(String args[]) {
    Show.inFrame(new Buttons(), 300, 200);
  }
} ///:~

The JButton looks like the AWT button, but there’s more you can do to it (like add images, as you’ll see later). In javax.swing.plaf.basic, there is also a BasicArrowButton that is convenient.

When you run the example, you’ll see that the toggle button holds its last position, in or out. But the check boxes and radio buttons behave identically to each other, just clicking on or off (they are inherited from JToggleButton).

Button groups

If you want radio buttons to behave in an “exclusive or” fashion, you must add them to a button group, in a similar but less awkward way as the old AWT. But as the example below demonstrates, any AbstractButton can be added to a ButtonGroup.

To avoid repeating a lot of code, this example uses reflection to generate the groups of different types of buttons. This is seen in makeBPanel, which creates a button group and a JPanel, and for each String in the array that’s the second argument to makeBPanel( ), it adds an object of the class represented by the first argument:

//: ButtonGroups.java
// Uses reflection to create groups of different
// types of AbstractButton.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import java.lang.reflect.*;

public class ButtonGroups extends JPanel {
  static String[] ids = { 
    "June", "Ward", "Beaver", 
    "Wally", "Eddie", "Lumpy",
  };
  static JPanel 
  makeBPanel(Class bClass, String[] ids) {
    ButtonGroup bg = new ButtonGroup();
    JPanel jp = new JPanel();
    String title = bClass.getName();
    title = title.substring(
      title.lastIndexOf('.') + 1);
    jp.setBorder(new TitledBorder(title));
    for(int i = 0; i < ids.length; i++) {
      AbstractButton ab = new JButton("failed");
      try {
        // Get the dynamic constructor method
        // that takes a String argument:
        Constructor ctor = bClass.getConstructor(
          new Class[] { String.class });
        // Create a new object:
        ab = (AbstractButton)ctor.newInstance(
          new Object[]{ids[i]});
      } catch(Exception ex) {
        System.out.println("can't create " + 
          bClass);
      }
      bg.add(ab);
      jp.add(ab);
    }
    return jp;
  }
  public ButtonGroups() {
    add(makeBPanel(JButton.class, ids));
    add(makeBPanel(JToggleButton.class, ids));
    add(makeBPanel(JCheckBox.class, ids));
    add(makeBPanel(JRadioButton.class, ids));
  }
  public static void main(String args[]) {
    Show.inFrame(new ButtonGroups(), 500, 300);
  }
} ///:~

The title for the border is taken from the name of the class, stripping off all the path information. The AbstractButton is initialized to a JButton that has the label “Failed” so if you ignore the exception message, you’ll still see the problem on screen. The getConstructor( ) method produces a Constructor object that takes the array of arguments of the types in the Class array passed to getConstructor( ). Then all you do is call newInstance( ), passing it an array of Object containing your actual arguments – in this case, just the String from the ids array.

This adds a little complexity to what is a simple process. To get “exclusive or” behavior with buttons, you create a button group and add each button for which you want that behavior to the group. When you run the program, you’ll see that all the buttons except JButton exhibit this “exclusive or” behavior.

Icons

You can use an Icon inside a JLabel or anything that inherits from AbstractButton (including JButton, JCheckbox, JradioButton, and the different kinds of JMenuItem). Using Icons with JLabels is quite straightforward (you’ll see an example later). The following example explores all the additional ways you can use Icons with buttons and their descendants.

You can use any gif files you want, but the ones used in this example are part of the book’s code distribution, available at www.BruceEckel.com. To open a file and bring in the image, simply create an ImageIcon and hand it the file name. From then on, you can use the resulting Icon in your program.

//: Faces.java
// Icon behavior in JButtons
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Faces extends JPanel {
  static Icon[] faces = {
    new ImageIcon("face0.gif"),
    new ImageIcon("face1.gif"),
    new ImageIcon("face2.gif"),
    new ImageIcon("face3.gif"),
    new ImageIcon("face4.gif"),
  };
  JButton 
    jb = new JButton("JButton", faces[3]),
    jb2 = new JButton("Disable");
  boolean mad = false;
  public Faces() {
    jb.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        if(mad) {
          jb.setIcon(faces[3]);
          mad = false;
        } else {
          jb.setIcon(faces[0]);
          mad = true;
        }
        jb.setVerticalAlignment(JButton.TOP);
        jb.setHorizontalAlignment(JButton.LEFT);
      }
    });
    jb.setRolloverEnabled(true);
    jb.setRolloverIcon(faces[1]);
    jb.setPressedIcon(faces[2]);
    jb.setDisabledIcon(faces[4]);
    jb.setToolTipText("Yow!");
    add(jb);
    jb2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        if(jb.isEnabled()) {
          jb.setEnabled(false);
          jb2.setText("Enable");
        } else {
          jb.setEnabled(true);
          jb2.setText("Disable");
        }
      }
    });
    add(jb2);
  }
  public static void main(String args[]) {
    Show.inFrame(new Faces(), 300, 200);
  }
} ///:~

An Icon can be used in many constructors, but you can also use setIcon( ) to add or change an Icon. This example also shows how a JButton (or any AbstractButton) can set the various different sorts of icons that appear when things happen to that button: when it’s pressed, disabled, or “rolled over” (the mouse moves over it without clicking). You’ll see that this gives the button a rather animated feel.

Note that a tool tip is also added to the button.

Menus

Menus are much improved and more flexible in Swing – for example, you can use them just about anywhere, including panels and applets. The syntax for using them is much the same as it was in the old AWT, and this preserves the same problem present in the old AWT: you must hard-code your menus and there isn’t any support for menus as resources (which, among other things, would make them easier to change for other languages). In addition, menu code gets long-winded and sometimes messy. The following approach takes a step in the direction of solving this problem by putting all the information about each menu into a two-dimensional array of Object (that way you can put anything you want into the array). This array is organized so that the first row represents the menu name, and the remaining rows represent the menu items and their characteristics. You’ll notice the rows of the array do not have to be uniform from one to the next – as long as your code knows where everything should be, each row can be completely different.

//: Menus.java
// A menu-building system; also demonstrates
// icons in labels and menu items.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Menus extends JPanel {
  static final Boolean
    bT = new Boolean(true), 
    bF = new Boolean(false);
  // Dummy class to create type identifiers:
  static class MType { MType(int i) {} };
  static final MType
    mi = new MType(1), // Normal menu item
    cb = new MType(2), // Checkbox menu item
    rb = new MType(3); // Radio button menu item
  JTextField t = new JTextField(10);
  JLabel l = new JLabel("Icon Selected", 
    Faces.faces[0], JLabel.CENTER);
  ActionListener a1 = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      t.setText(
        ((JMenuItem)e.getSource()).getText());
    }
  };
  ActionListener a2 = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      JMenuItem mi = (JMenuItem)e.getSource();
      l.setText(mi.getText());
      l.setIcon(mi.getIcon());
    }
  };
  // Store menu data as "resources":
  public Object[][] fileMenu = {
    // Menu name and accelerator:
    { "File", new Character('F') },
    // Name type accel listener enabled
    { "New", mi, new Character('N'), a1, bT },
    { "Open", mi, new Character('O'), a1, bT },
    { "Save", mi, new Character('S'), a1, bF },
    { "Save As", mi, new Character('A'), a1, bF},
    { null }, // Separator
    { "Exit", mi, new Character('x'), a1, bT },
  };
  public Object[][] editMenu = {
    // Menu name:
    { "Edit", new Character('E') },
    // Name type accel listener enabled
    { "Cut", mi, new Character('t'), a1, bT },
    { "Copy", mi, new Character('C'), a1, bT },
    { "Paste", mi, new Character('P'), a1, bT },
    { null }, // Separator
    { "Select All", mi,new Character('l'),a1,bT},
  };
  public Object[][] helpMenu = {
    // Menu name:
    { "Help", new Character('H') },
    // Name type accel listener enabled
    { "Index", mi, new Character('I'), a1, bT },
    { "Using help", mi,new Character('U'),a1,bT},
    { null }, // Separator
    { "About", mi, new Character('t'), a1, bT },
  };
  public Object[][] optionMenu = {
    // Menu name:
    { "Options", new Character('O') },
    // Name type accel listener enabled
    { "Option 1", cb, new Character('1'), a1,bT},
    { "Option 2", cb, new Character('2'), a1,bT},
  };
  public Object[][] faceMenu = {
    // Menu name:
    { "Faces", new Character('a') },
    // Optinal last element is icon
    { "Face 0", rb, new Character('0'), a2, bT, 
      Faces.faces[0] },
    { "Face 1", rb, new Character('1'), a2, bT, 
      Faces.faces[1] },
    { "Face 2", rb, new Character('2'), a2, bT, 
      Faces.faces[2] },
    { "Face 3", rb, new Character('3'), a2, bT, 
      Faces.faces[3] },
    { "Face 4", rb, new Character('4'), a2, bT, 
      Faces.faces[4] },
  };
  public Object[] menuBar = {
    fileMenu, editMenu, faceMenu, 
    optionMenu, helpMenu,
  };
  static public JMenuBar
  createMenuBar(Object[] menuBarData) {
    JMenuBar menuBar = new JMenuBar();
    for(int i = 0; i < menuBarData.length; i++)
      menuBar.add(
        createMenu((Object[][])menuBarData[i]));
    return menuBar;
  }
  static ButtonGroup bgroup;
  static public JMenu 
  createMenu(Object[][] menuData) {
    JMenu menu = new JMenu();
    menu.setText((String)menuData[0][0]);
    menu.setMnemonic(
      ((Character)menuData[0][1]).charValue());
    // Create redundantly, in case there are
    // any radio buttons:
    bgroup = new ButtonGroup();
    for(int i = 1; i < menuData.length; i++) {
      if(menuData[i][0] == null)
        menu.add(new JSeparator());
      else
        menu.add(createMenuItem(menuData[i]));
    }
    return menu;
  }
  static public JMenuItem 
  createMenuItem(Object[] data) {
    JMenuItem m = null;
    MType type = (MType)data[1];
    if(type == mi)
      m = new JMenuItem();
    else if(type == cb)
      m = new JCheckBoxMenuItem();
    else if(type == rb) {
      m = new JRadioButtonMenuItem();
      bgroup.add(m);
    }
    m.setText((String)data[0]);
    m.setMnemonic(
      ((Character)data[2]).charValue());
    m.addActionListener(
      (ActionListener)data[3]);
    m.setEnabled(
      ((Boolean)data[4]).booleanValue());
    if(data.length == 6)
      m.setIcon((Icon)data[5]);
    return m;
  }
  Menus() {
    setLayout(new BorderLayout());
    add(createMenuBar(menuBar), 
      BorderLayout.NORTH);
    JPanel p = new JPanel();
    p.setLayout(new BorderLayout());
    p.add(t, BorderLayout.NORTH);
    p.add(l, BorderLayout.CENTER);
    add(p, BorderLayout.CENTER);
  }
  public static void main(String args[]) {
    Show.inFrame(new Menus(), 300, 200);
  }
} ///:~

The goal is to allow the programmer to simply create tables to represent each menu, rather than typing lines of code to build the menus. Each table produces one menu, and the first row in the table contains the menu name and its keyboard accelerator. The remaining rows contain the data for each menu item: the string to be placed on the menu item, what type of menu item it is, its keyboard accelerator, the actionlistener that is fired when this menu item is selected, and whether this menu item is enabled. If a row starts with null it is treated as a separator.

To prevent wasteful and tedious multiple creations of Boolean objects and type flags, these are created as static final values at the beginning of the class: bT and bF to represent Booleans and different objects of the dummy class MType to describe normal menu items (mi), checkbox menu items (cb), and radio button menu items (rb). Remember that an array of Object may hold only Object handles and not primitive values.

This example also shows how JLabels and JMenuItems (and their descendants) may hold Icons. An Icon is placed into the JLabel via its constructor and changed when the corresponding menu item is selected.

The menuBar array contains the handles to all the file menus in the order that you want them to appear on the menu bar. You pass this array to createMenuBar( ), which breaks it up into individual arrays of menu data, passing each to createMenu( ). This method, in turn, takes the first line of the menu data and creates a JMenu from it, then calls createMenuItem( ) for each of the remaining lines of menu data. Finally, createMenuItem( ) parses each line of menu data and determines the type of menu and its attributes, and creates that menu item appropriately. In the end, as you can see in the Menus( ) constructor, to create a menu from these tables say createMenuBar(menuBar) and everything is handled recursively.

This example does not take care of building cascading menus, but you should have enough of the concept that you can add that capability if you need it.

Popup menus

The most straightforward way to implement a JPopupMenu is to create an inner class that extends MouseAdapter, then add an object of that inner class to each component which should produce popup behavior:

//: Popup.java
// Creating popup menus with Swing
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Popup extends JPanel {
  JPopupMenu popup = new JPopupMenu();
  JTextField t = new JTextField(10);
  public Popup() {
    add(t);
    ActionListener al = new ActionListener() {
      public void actionPerformed(ActionEvent e){
        t.setText(
          ((JMenuItem)e.getSource()).getText());
      }
    };
    JMenuItem m = new JMenuItem("Hither");
    m.addActionListener(al);
    popup.add(m);
    m = new JMenuItem("Yon");
    m.addActionListener(al);
    popup.add(m);
    m = new JMenuItem("Afar");
    m.addActionListener(al);
    popup.add(m);
    popup.addSeparator();
    m = new JMenuItem("Stay Here");
    m.addActionListener(al);
    popup.add(m);
    PopupListener pl = new PopupListener();
    addMouseListener(pl);
    t.addMouseListener(pl);
  }
  class PopupListener extends MouseAdapter {
    public void mousePressed(MouseEvent e) {
      maybeShowPopup(e);
    }
    public void mouseReleased(MouseEvent e) {
      maybeShowPopup(e);
    }
    private void maybeShowPopup(MouseEvent e) {
      if(e.isPopupTrigger()) {
        popup.show(
          e.getComponent(), e.getX(), e.getY());
      }
    }
  }
  public static void main(String args[]) {
    Show.inFrame(new Popup(),200,150);
  }
} ///:~

The same ActionListener is added to each JMenuItem, so that it fetches the text from the menu label and inserts it into the JTextField.

List boxes and combo boxes

List boxes and combo boxes in Swing work much as they do in the old AWT, but they also have increased functionality if you need it. In addition, some conveniences have been added. For example, the JList has a constructor that takes an array of Strings to display (oddly enough this same feature is not available in JComboBox). Here’s a simple example that shows the basic use of each:

//: ListCombo.java
// List boxes & Combo boxes
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ListCombo extends JPanel {
  public ListCombo() {
    setLayout(new GridLayout(2,1));
    JList list = new JList(ButtonGroups.ids);
    add(new JScrollPane(list));
    JComboBox combo = new JComboBox();
    for(int i = 0; i < 100; i++)
      combo.addItem(Integer.toString(i));
    add(combo);
  }
  public static void main(String args[]) {
    Show.inFrame(new ListCombo(),200,200);
  }
} ///:~

Something else that seems a bit odd at first is that JLists do not automatically provide scrolling, even though that’s something you always expect. Adding support for scrolling turns out to be quite easy, as shown above – you simply wrap the JList in a JScrollPane and all the details are automatically managed for you.

Sliders and progress bars

A slider allows the user to input data by moving a point back and forth, which is intuitive in some situations (volume controls, for example). A progress bar displays data in a relative fashion from “full” to “empty” so the user gets a perspective. My favorite example for these is to simply hook the slider to the progress bar so when you move the slider the progress bar changes accordingly:

//: Progress.java
// Using progress bars and sliders
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;

public class Progress extends JPanel {
  JProgressBar pb = new JProgressBar();
  JSlider sb = 
    new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
  public Progress() {
    setLayout(new GridLayout(2,1));
    add(pb);
    sb.setValue(0);
    sb.setPaintTicks(true);
    sb.setMajorTickSpacing(20);
    sb.setMinorTickSpacing(5);
    sb.setBorder(new TitledBorder("Slide Me"));
    pb.setModel(sb.getModel()); // Share model
    add(sb);
  }
  public static void main(String args[]) {
    Show.inFrame(new Progress(),200,150);
  }
} ///:~

The JProgressBar is fairly straightforward, but the JSlider has a lot of options, such as the orientation and major and minor tick marks. Notice how straightforward it is to add a titled border.

Trees

Using a JTree can be as simple as saying:

add(new JTree(
  new Object[] {"this", "that", "other"}));

This displays a primitive tree. The API for trees is vast, however – certainly one of the largest in Swing. It appears that you can do just about anything with trees, but more sophisticated tasks might require quite a bit of research and experimentation.

Fortunately, there is a middle ground provided in the library: the “default” tree components, which generally do what you need. So most of the time you can use these components, and only in special cases will you need to delve in and understand trees more deeply.

The following example uses the “default” tree components to display a tree in an applet. When you press the button, a new subtree is added under the currently-selected node (if no node is selected, the root node is used):

//: Trees.java
// Simple Swing tree example. Trees can be made
// vastly more complex than this.
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;

// Takes an array of Strings and makes the first
// element a node and the rest leaves:
class Branch {
  DefaultMutableTreeNode r;
  public Branch(String[] data) {
    r = new DefaultMutableTreeNode(data[0]);
    for(int i = 1; i < data.length; i++)
      r.add(new DefaultMutableTreeNode(data[i]));
  }
  public DefaultMutableTreeNode node() { 
    return r; 
  }
}  

public class Trees extends JPanel {
  String[][] data = {
    { "Colors", "Red", "Blue", "Green" },
    { "Flavors", "Tart", "Sweet", "Bland" },
    { "Length", "Short", "Medium", "Long" },
    { "Volume", "High", "Medium", "Low" },
    { "Temperature", "High", "Medium", "Low" },
    { "Intensity", "High", "Medium", "Low" },
  };
  static int i = 0;
  DefaultMutableTreeNode root, child, chosen;
  JTree tree;
  DefaultTreeModel model;
  public Trees() {
    setLayout(new BorderLayout());
    root = new DefaultMutableTreeNode("root");
    tree = new JTree(root);
    // Add it and make it take care of scrolling:
    add(new JScrollPane(tree), 
      BorderLayout.CENTER);
    // Capture the tree's model:
    model =(DefaultTreeModel)tree.getModel();
    JButton test = new JButton("Press me");
    test.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        if(i < data.length) {
          child = new Branch(data[i++]).node();
          // What's the last one you clicked?
          chosen = (DefaultMutableTreeNode)
            tree.getLastSelectedPathComponent();
          if(chosen == null) chosen = root;
          // The model will create the 
          // appropriate event. In response, the
          // tree will update itself:
          model.insertNodeInto(child, chosen, 0);
          // This puts the new node on the 
          // currently chosen node.
        }
      }
    });
    // Change the button's colors:
    test.setBackground(Color.blue);
    test.setForeground(Color.white);
    JPanel p = new JPanel();
    p.add(test);
    add(p, BorderLayout.SOUTH);
  }
  public static void main(String args[]) {
    Show.inFrame(new Trees(),200,500);
  }
} ///:~

The first class, Branch, is a tool to take an array of String and build a DefaultMutableTreeNode with the first String as the root and the rest of the Strings in the array as leaves. Then node( ) can be called to produce the root of this “branch.”

The Trees class contains a two-dimensional array of Strings from which Branches can be made and a static int i to count through this array. The DefaultMutableTreeNode objects hold the nodes, but the physical representation on screen is controlled by the JTree and its associated model, the DefaultTreeModel. Note that when the JTree is added to the applet, it is wrapped in a JScrollPane – this is all it takes to provide automatic scrolling.

The JTree is controlled through its model. When you make a change to the model, the model generates an event that causes the JTree to perform any necessary updates to the visible representation of the tree. In init( ), the model is captured by calling getModel( ). When the button is pressed, a new “branch” is created. Then the currently selected component is found (or the root if nothing is selected) and the model’s insertNodeInto( ) method does all the work of changing the tree and causing it to be updated.

Most of the time an example like the one above will give you what you need in a tree. However, trees have the power to do just about anything you can imagine – everywhere you see the word “default” in the example above, you can substitute your own class to get different behavior. But beware: almost all of these classes have a large interface, so you could spend a lot of time struggling to understand the intricacies of trees.

Tables

Like trees, tables in Swing are vast and powerful. They are primarily intended to be the popular “grid” interface to databases via Java Database Connectivity (JDBC, discussed in Chapter 15) and thus they have a tremendous amount of flexibility, which you pay for in complexity. There’s easily enough here to be the basis of a full-blown spreadsheet and could probably justify an entire book. However, it is also possible to create a relatively simple JTable if you understand the basics.

The JTable controls how the data is displayed, but the TableModel controls the data itself. So to create a JTable you’ll typically create a TableModel first. You can fully implement the TableModel interface, but it’s usually simpler to inherit from the helper class AbstractTableModel:

//: Table.java
// Simple demonstration of JTable
package c13.swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
import javax.swing.event.*;

// The TableModel controls all the data:
class DataModel extends AbstractTableModel {
  Object[][] data = {
    {"one", "two", "three", "four"},
    {"five", "six", "seven", "eight"},
    {"nine", "ten", "eleven", "twelve"},
  };
  // Prints data when table changes:
  class TML implements TableModelListener {
    public void tableChanged(TableModelEvent e) {
      for(int i = 0; i < data.length; i++) {
        for(int j = 0; j < data[0].length; j++)
          System.out.print(data[i][j] + " ");
        System.out.println();
      }
    }
  }
  DataModel() {
    addTableModelListener(new TML());
  }
  public int getColumnCount() { 
    return data[0].length; 
  }
  public int getRowCount() { 
    return data.length;
  }
  public Object getValueAt(int row, int col) { 
    return data[row][col]; 
  }
  public void 
  setValueAt(Object val, int row, int col) {
    data[row][col] = val;
    // Indicate the change has happened:
    fireTableDataChanged();
  }
  public boolean 
  isCellEditable(int row, int col) { 
    return true; 
  }
};       

public class Table extends JPanel {
  public Table() {
    setLayout(new BorderLayout());
    JTable table = new JTable(new DataModel());
    JScrollPane scrollpane = 
      JTable.createScrollPaneForTable(table);
    add(scrollpane, BorderLayout.CENTER);
  }
  public static void main(String args[]) {
    Show.inFrame(new Table(),200,200);
  }
} ///:~

DataModel contains an array of data, but you could also get the data from some other source such as a database. The constructor adds a TableModelListener which prints the array every time the table is changed. The rest of the methods follow the Beans naming convention, and are used by JTable when it wants to present the information in DataModel. AbstractTableModel provides default methods for setValueAt( ) and isCellEditable( ) that prevent changes to the data, so if you want to be able to edit the data, you must override these methods.

Once you have a TableModel, you only need to hand it to the JTable constructor. All the details of displaying, editing and updating will be taken care of for you. Notice that this example also puts the JTable in a JScrollPane, which requires a special JTable method.

Tabbed Panes

Earlier in this chapter you were introduced to the positively medieval CardLayout, and saw how you had to manage all the switching of the ugly cards yourself. Someone actually thought this was a good design. Fortunately, Swing remedies this by providing JTabbedPane, which handles all the tabs, the switching, and everything. The contrast between CardLayout and JTabbedPane is breathtaking.

The following example is quite fun because it takes advantage of the design of the previous examples. They are all built as descendants of JPanel, so this example will place each one of the previous examples in its own pane on a JTabbedPane. You’ll notice that the use of RTTI makes the example quite small and elegant:

//: Tabbed.java
// Using tabbed panes
package c13.swing;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;

public class Tabbed extends JPanel {
  static Object[][] q = {
    { "Felix", Borders.class },
    { "The Professor", Buttons.class },
    { "Rock Bottom", ButtonGroups.class },
    { "Theodore", Faces.class },
    { "Simon", Menus.class },
    { "Alvin", Popup.class },
    { "Tom", ListCombo.class },
    { "Jerry", Progress.class },
    { "Bugs", Trees.class },
    { "Daffy", Table.class },
  };
  static JPanel makePanel(Class c) {
    String title = c.getName();
    title = title.substring(
      title.lastIndexOf('.') + 1);
    JPanel sp = null;
    try {
      sp = (JPanel)c.newInstance();
    } catch(Exception e) {
      System.out.println(e);
    }
    sp.setBorder(new TitledBorder(title));
    return sp;
  }
  public Tabbed() {
    setLayout(new BorderLayout());
    JTabbedPane tabbed = new JTabbedPane();
    for(int i = 0; i < q.length; i++)
      tabbed.addTab((String)q[i][0], 
        makePanel((Class)q[i][1]));
    add(tabbed, BorderLayout.CENTER);
    tabbed.setSelectedIndex(q.length/2);
  }
  public static void main(String args[]) {
    Show.inFrame(new Tabbed(),460,350);
  }
} ///:~

Again, you can see the theme of an array used for configuration: the first element is the String to be placed on the tab and the second is the JPanel class that will be displayed inside of the corresponding pane. In the Tabbed( ) constructor, you can see the two important JTabbedPane methods that are used: addTab( ) to put a new pane in, and setSelectedIndex( ) to choose the pane to start with. (One in the middle is chosen just to show that you don’t have to start with the first pane.)

When you call addTab( ) you supply it with the String for the tab and any Component (that is, an AWT Component, not just a JComponent, which is derived from the AWT Component). The Component will be displayed in the pane. Once you do this, no further management is necessary – the JTabbedPane takes care of everything else for you (as it should).

The makePanel( ) method takes the Class object of the class you want to create and uses newInstance( ) to create one, casting it to a JPanel (of course, this assumes that any class you want to add must inherit from JPanel, but that’s been the structure used for the examples in this section). It adds a TitledBorder that contains the name of the class and returns the result as a JPanel to be used in addTab( ).

When you run the program you’ll see that the JTabbedPane automatically stacks the tabs if there are too many of them to fit on one row.

The Swing message box

Windowing environments commonly contain a standard set of message boxes that allow you to quickly post information to the user or to capture information from the user. In Swing, these message boxes are contained in JOptionPane. You have many different possibilities (some quite sophisticated), but the ones you’ll most commonly use are probably the message dialog and confirmation dialog, invoked using the static JOptionPane.showMessageDialog( ) and JOptionPane. showConfirmDialog( ).

More to Swing

This section was meant only to give you an introduction to the power of Swing and to get you started so you could see how relatively simple it is to feel your way through the libraries. What you’ve seen so far will probably suffice for a good portion of your UI design needs. However, there’s a lot more to Swing – it’s intended to be a fully-powered UI design tool kit. If you don’t see what you need here, delve into the online documentation from Sun and search the Web. There’s probably a way to accomplish just about everything you can imagine.

Some of the topics that were not covered in this section include:

Summary

Of all the libraries in Java, the AWT has seen the most dramatic changes from Java 1.0 to Java 1.2. The Java 1.0 AWT was roundly criticized as being one of the worst designs seen, and while it would allow you to create portable programs, the resulting GUI was “equally mediocre on all platforms.” It was also limiting, awkward, and unpleasant to use compared with native application development tools on a particular platform.

When Java 1.1 introduced the new event model and Java Beans, the stage was set – now it was possible to create GUI components that could be easily dragged and dropped inside visual application builder tools. In addition, the design of the event model and Beans clearly shows strong consideration for ease of programming and maintainable code (something that was not evident in the 1.0 AWT). But it wasn’t until the GUI components – the JFC/Swing classes – appeared that the job was finished. With the Swing components, cross-platform GUI programming can be a civilized experience.

Actually, the only thing that’s missing is the application builder tool, and this is where the real revolution lies. Microsoft’s Visual Basic and Visual C++ require their application builder tools, as does Borland’s Delphi and C++ Builder. If you want the application builder tool to get better, you have to cross your fingers and hope the vendor will give you what you want. But Java is an open environment, and so not only does it allow for competing application builder environments, it encourages them. And for these tools to be taken seriously, they must support Java Beans. This means a leveled playing field: if a better application builder tool comes along, you’re not tied to the one you’ve been using – you can pick up and move to the new one and increase your productivity. This kind of competitive environment for GUI application builder tools has not been seen before, and the resulting competition can generate only positive results for the productivity of the programmer.

Exercises

  1. Create an applet with a text field and three buttons. When you press each button, make some different text appear in the text field.
  2. Add a check box to the applet created in Exercise 1, capture the event, and insert different text into the text field.
  3. Create an applet and add all the components that cause action( ) to be called, then capture their events and display an appropriate message for each inside a text field.
  4. Add to Exercise 3 the components that can be used only with events detected by handleEvent( ). Override handleEvent( ) and display appropriate messages for each inside a text field.
  5. Create an applet with a Button and a TextField. Write a handleEvent( ) so that if the button has the focus, characters typed into it will appear in the TextField.
  6. Create an application and add to the main frame all the components described in this chapter, including menus and a dialog box.
  7. Modify TextNew.java so that the characters in t2 retain the original case that they were typed in, instead of automatically being forced to upper case.
  8. Modify CardLayout1.java so that it uses the Java 1.1 event model.
  9. Add Frog.class to the manifest file shown in this chapter and run jar to create a JAR file containing both Frog and BangBean. Now either download and install the BDK from Sun or use your own Beans-enabled program builder tool and add the JAR file to your environment so you can test the two Beans.
  10. Create your own Java Bean called Valve that contains two properties: a Boolean called “on” and an integer called “level.” Create a manifest file, use jar to package your Bean, then load it into the beanbox or into your own Beans-enabled program builder tool so that you can test it.
  11. (Somewhat challenging) Change Menus.java so that it handles cascading menus.

[51] It is assumed that the reader is familiar with the basics of HTML. It’s not too hard to figure out, and there are lots of books and resources.

[52] Because the appletviewer ignores everything but APPLET tags, you can put those tags in the Java source file as comments:
// <applet code=MyApplet.class width=200 height=100></applet>
This way, you can run "appletviewer MyApplet.java" and you don’t need to create tiny HTML files to run tests.

[53] ShowStatus( ) is also a method of Applet, so you can call it directly, without calling getAppletContext( ).

[54] This behavior is apparently a bug and will be fixed in a later version of Java.

[55] There is no MouseMotionEvent even though it seems like there ought to be. Clicking and motion is combined into MouseEvent, so this second appearance of MouseEvent in the table is not an error.

[56] It also solves the problem of “callbacks” without adding any awkward “method pointer” feature to Java.

[57] At the time this section was written, the Swing library had been pronounced “frozen” by Sun, so this code should compile and run without problems as long as you’ve downloaded and installed the Swing library. (You should be able to compile one of Sun’s included demonstration programs to test your installation.) If you do encounter difficulties, check www.BruceEckel.com for updated code.

[ Previous Chapter ] [ Short TOC ] [ Table of Contents ] [ Index ] [ Next Chapter ]
Last Update:02/04/2000