MindView Inc.
[ Viewing Hints ] [ Revision History ] [ Report an Error ]
[ 1st Edition ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Thinking in Java, 2nd edition, Revision 9

©2000 by Bruce Eckel

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

13: Creating Windows
& Applets

[[[ Note: please notice that only the code files have been changed to be Swing-compliant, and the prose is essentially the way it was before, and refers to the old programs—that is to say, more work is going to be done here. So please concentrate on the code listings, and if you see changes that should be made, please include the entire file after you’ve made the improvements, using the correction page attached to this HTML file or at www.BruceEckel.com ]]]

A fundamental design guideline is “make simple things easy, and difficult things possible[59].”

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. The Java 1.0 AWT programming model is also awkward and non-object-oriented. A student in one of my seminars (who had been at Sun during the creation of Java) explained why: the original AWT had been conceptualized, designed and implemented in a month. Certainly a marvel of productivity, and also an object lesson in why design is important.

Much of the situation improved with the Java 1.1 AWT event model, which takes a much clearer, object-oriented approach, along with the addition of Java Beans, a component programming model that is oriented toward the easy creation of visual programming environments. Java 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.

This chapter does not cover anything but the modern, Java 2 Swing library, and makes the reasonable assumption that Swing is the final destination GUI library for Java. If for some reason you need to use the original “old” AWT (because you’re supporting old code or old browsers), you can find that introduction in the online first edition of this book at www.BruceEckel.com.

Early in this chapter, you’ll see how things are different when you want to create an applet vs. a regular application using Swing, 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. Almost all the GUI examples in this book will be runnable as either applets or applications.

Please be aware that this is not a comprehensive glossary of all the methods for the described classes. The Swing library is vast, and the goal of this chapter is only to get you started with the essentials and comfortable with the concepts. When you’re looking for more sophistication, make sure you go to the HTML JDK library documents from http://java.sun.com to look for the javax.swing classes and methods—because of the simplicity of Swing, this will often be enough information to solve your problem. There are numerous (rather thick) books dedicated solely to Swing and you’ll want to go to those if you need to modify the default Swing behavior.

As you learn about Swing you’ll discover:

  1. Swing is a much better programming model than you’ve probably seen in other languages and development environments. Java Beans is the framework for that library.
  2. “GUI builders” (visual programming environments) are de rigeur for all development systems. Java Beans and the Swing allow the GUI builder to write code for you as you place components onto forms using graphical tools. This not only rapidly speeds development during GUI building, but it allows for greater experimentation and thus the ability to try out more designs and presumably come up with a better one.
  3. The simplicity and well-designed nature of Swing means that even if you do use a GUI builder, the resulting code will still be comprehensible—this has been a big problem with GUI builders in the past.

The basic applet

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, applets are a powerful tool that supports 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 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. Swing can also be used to build GUI interfaces for regular applications.

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 ArrayList 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[60].

Applets are built using an application framework. You inherit from class JApplet and override the appropriate methods. There are a few methods that control the creation and execution of an applet on a Web page:

Method

Operation

init( )

Called when the applet is first created to perform first-time initialization of the applet, including component layout. You’ll always override this method.

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( ).

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

With this information you can create a simple applet:

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

public class Applet1 extends JApplet {
  public void init() {
    getContentPane().add(new JLabel("Applet!"));
  }
} ///:~

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( ).

Running applets inside a Web browser

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[61] to tell the page how to load and run the applet.

This process used to be very simple, when Java was simple and everyone was on the same bandwagon and incorporated the same Java support inside their Web browsers. Then you might have been able to get away with a very simple bit of HTML inside your Web page, like this:

<applet code=Applet1 width=100 height=50>
</applet>

Then along came the browser & language wars, and we (programmers and end users alike) lost. After awhile, JavaSoft realized that we could no longer expect browsers to support the correct flavor of Java, and the only solution was to provide some kind of add-on that would conform to a browser’s extension mechanism. By using the extension mechanism (which a browser vendor cannot disable—in an attempt to gain competitive advantage—without breaking all the 3rd party extensions) JavaSoft guarantees that Java cannot be shut out of the Web browser by an antagonistic vendor.

With Internet Explorer, the extension mechanism is the ActiveX control, and with Netscape the extension mechanism is the plug-in. In your html code, you must provide tags to support both. Here’s what the simplest resulting HTML page looks like for Applet1:

//:! c13:Applet1.html
<html><head><title>Applet1</title></head><hr>
<OBJECT 
  classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
  width="100" height="50" align="baseline"  codebase="http://java.sun.com/products/plugin/1.2.2/jinstall-1_2_2-win.cab#Version=1,2,2,0">
<PARAM NAME="code" VALUE="Applet1.class">
<PARAM NAME="codebase" VALUE=".">
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.2.2">
<COMMENT>
  <EMBED type=
    "application/x-java-applet;version=1.2.2" 
    width="200" height="200" align="baseline"
    code="Applet1.class" codebase="."
pluginspage="http://java.sun.com/products/plugin/1.2/plugin-install.html">
  <NOEMBED>
</COMMENT>
   No Java 2 support for APPLET!!
  </NOEMBED>
</EMBED>
</OBJECT>
<hr></body></html>
///:~

Some of these lines were too long and had to be wrapped to fit on the page. The code in this book’s source code (on the book’s CD ROM, and downloadable from www.BruceEckel.com) will work without having to worry about correcting line wraps.

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.

Using Appletviewer

Sun’s JDK (freely downloadable from http://java.sun.com) 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. Because the appletviewer ignores everything but APPLET tags, you can put those tags in the Java source file as comments:

// <applet code=MyApplet 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. Also, you can see from looking at the example the HTML tag that must be inserted into a Web page.

For example, you can add the commented HTML tags to Applet1.java:

//: c13:Applet1b.java
// Embedding the applet tag for Appletviewer.
// <applet code=Applet1b width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;

public class Applet1b extends JApplet {
  public void init() {
    getContentPane().add(new JLabel("Applet!"));
  }
} ///:~

Now you can invoke the applet with the command

Appletviewer Applet1b.java

For “plain” applets in this book, this form will be used for easy testing (later, you’ll see another coding approach which will allow you to execute applets from the command line without the Appletviewer).

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. 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 (since the applet is just a file or set of files, the ISP does not have to provide any special support for Java). 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 available for free or as shareware. 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 on the client machine 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.

Making windows and applets

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.

Often you’ll want to be able to create a class that can be invoked as either a window or an applet. This is especially convenient when you’re testing the applets, since it’s typically much faster and easier to run the resulting applet-application from the command line than it is to start up a Web browser or the Appletviewer.

To create an applet that can be run from the console command line, 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 Applet1b.java modified to work as both an application and an applet:

//: c13:Applet1c.java
// An application and an applet.
// <applet code=Applet1c width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Applet1c extends JApplet {
  public void init() {
    getContentPane().add(new JLabel("Applet!"));
  }
  // A main() for the application:
  public static void main(String[] args) {
    JApplet applet = new Applet1b();
    JFrame frame = new JFrame("Applet1b");
    // To close the application:
    Console.setupClosing(frame);
    frame.getContentPane().add(applet);
    frame.setSize(100,50);
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
} ///:~

main( ) is the only element added to the applet, and the rest of the applet is untouched. In fact, you can usually copy and paste main( ) into your own applets with little modification. 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 reference 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:

frame.setVisible(true);

Without this, you won’t see anything on the screen.

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 examples in the rest of this chapter:

//: com:bruceeckel:swing:Console.java
// Tool for running Swing demos from the
// console, both applets and JFrames.
package com.bruceeckel.swing;
import javax.swing.*;
import java.awt.event.*;

public class Console {
  // Create a title string from the class name:
  public static String title(Object o) {
    String t = o.getClass().toString();
    // Remove the word "class":
    if(t.indexOf("class") != -1)
      t = t.substring(6);
    return t;
  }
  public static void setupClosing(JFrame frame) {
    // The JDK 1.2 Solution as an 
    // anonymous inner class:
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        System.exit(0);
      }
    });
    // The improved solution in JDK 1.3:
    // frame.setDefaultCloseOperation(
    //     EXIT_ON_CLOSE);
  }
  public static void 
  run(JFrame frame, int width, int height) {
    setupClosing(frame);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
  public static void 
  run(JApplet applet, int width, int height) {
    JFrame frame = new JFrame(title(applet));
    setupClosing(frame);
    frame.getContentPane().add(applet);
    frame.setSize(width, height);
    applet.init();
    applet.start();
    frame.setVisible(true);
  }
  public static void 
  run(JPanel panel, int width, int height) {
    JFrame frame = new JFrame(title(panel));
    setupClosing(frame);
    frame.getContentPane().add(panel);
    frame.setSize(width, height);
    frame.setVisible(true);
  }
} ///:~

The common base class between JApplet and JPanel is Container. Only if it’s a JApplet is init( ) and start( ) called.

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

// The BorderLayout argument will become clear later in this chapter.

Closing the window

The method setupClosing( ) adds a window listener which is a static object that is responsible for closing the window.

You’ll see on the lines below this a more succinct form to close the window:

//#frame.setDefaultCloseOperation(EXIT_ON_CLOSE);

This works with JDK 1.3, but is commented out here for backward compatibility with JDK 1.2.

Now any applet can be run from the command line by creating a main( ) containing the a line like this:

Console.run(new MyClass(), 500, 300);

in which the last two arguments are the display width and height. Here’s Applet1c.java modified to use Console:

//: c13:Applet1d.java
// Console runs applets from the command line.
// <applet code=Applet1d width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Applet1d extends JApplet {
  public void init() {
    getContentPane().add(new JLabel("Applet!"));
  }
  public static void main(String[] args) {
    Console.run(new Applet1c(), 100, 50);
  }
} ///:~

This allows the elimination of repeated code while providing the greatest flexibility in running the examples.

Using the Windows Explorer

If you’re using Windows, you can simplify the process of running a command-line Java program by configuring the Windows Explorer (this is the file browser in Windows, not the Internet Explorer) so that you can simply double-click on a .class file to execute it. There are several steps in this process.

First, download and install the Perl programming language from www.Perl.org. You’ll find the instructions on that site.

Next, create the following script (it is part of this book’s source-code package):

//:! c13:RunJava.bat
@rem = '--*-Perl-*--
@echo off
perl -x -S "%0" %1 %2 %3 %4 %5 %6 %7 %8 %9
goto endofperl
@rem ';
#!perl
$file = $ARGV[0];
$file =~ s/(.*)\..*/\1/;
$file =~ s/(.*\\)*(.*)/$+/;
´java $file´;
__END__
:endofperl
///:~

Now, open the Windows Explorer, select “View,” “Folder Options,” then click on the “File Types” tab. Press the “New Type” button. For “Description of Type” enter “Java class file.” Under “Actions” press the “New” button. For “Action” enter “Open,” and for “Application used to perform action” enter a line like this:

"c:\aaa\Perl\RunJava.bat" "%L"

Where the path to “RunJava.bat” must be customized to wherever you place the batch file.

Making a button

Making a button is quite simple: you just call the JButton 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 reference for the button so you can refer to it later.

The JButton 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( ):

//: c13:Button1.java
// Putting buttons on an applet.
// <applet code=Button1 width=200 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Button1 extends JApplet {
  JButton 
    b1 = new JButton("Button 1"), 
    b2 = new JButton("Button 2");
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
  }
  public static void main(String[] args) {
    Console.run(new Button1(), 200, 50);
  }
} ///:~

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 this 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.

Swing 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:

//: c13:Button2.java
// Capturing button presses.
// <applet code=Button2 width=200 height=50>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Button2 extends JApplet {
  JButton 
    b1 = new JButton("Button 1"), 
    b2 = new JButton("Button 2");
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e){
      String name = 
        ((JButton)e.getSource()).getText();
      getAppletContext().showStatus(name);
    }
  }
  BL al = new BL();
  public void init() {
    b1.addActionListener(al);
    b2.addActionListener(al);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
  }
  public static void main(String[] args) {
    Console.run(new Button2(), 200, 50);
  }
} ///:~

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 reference 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.[62] You can print 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.

It is often more convenient to code the ActionListener as an anonymous inner class, especially since you tend to only use a single instance of each listener class. Button2.java can be modified to use an anonymous inner class as follows:

//: c13:Button2b.java
// Using anonymous inner classes.
// <applet code=Button2b width=200 height=50>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Button2b extends JApplet {
  JButton 
    b1 = new JButton("Button 1"), 
    b2 = new JButton("Button 2");
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      String name = 
        ((JButton)e.getSource()).getText();
      getAppletContext().showStatus(name);
    }
  };
  public void init() {
    b1.addActionListener(al);
    b2.addActionListener(al);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
  }
  public static void main(String[] args) {
    Console.run(new Button2b(), 200, 50);
  }
} ///:~

The approach of using an anonymous inner class will be preferred for the examples in this book.

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:

//: c13:TextField1.java
// Using the JTextField control.
// <applet code=TextField1 width=350 height=75>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class TextField1 extends JApplet {
  JButton
    b1 = new JButton("Get Text"),
    b2 = new JButton("Set Text");
  JTextField 
    t = new JTextField("Starting text: ", 30);
  String s = new String();
  ActionListener a1 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      getAppletContext().showStatus(t.getText());
      s = t.getSelectedText();
      if(s == null)
        s = t.getText();
      t.setEditable(true);
    }
  };
  ActionListener a2 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      t.setText("Inserted by Button 2: " + s);
      t.setEditable(false);
    }
  };
  public void init() {
    b1.addActionListener(a1);
    b2.addActionListener(a2);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
    cp.add(t);
  }
  public static void main(String[] args) {
    Console.run(new TextField1(), 350, 75);
  }
} ///:~

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.

//: c13:TextArea1.java
// Using the JTextArea control.
// <applet code=TextArea1 width=350 height=200>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class TextArea1 extends JApplet {
  JButton
    b1 = new JButton("Text Area 1"),
    b2 = new JButton("Text Area 2"),
    b3 = new JButton("Replace Text"),
    b4 = new JButton("Insert Text");
  JTextArea 
    t1 = new JTextArea("t1", 1, 30),
    t2 = new JTextArea("t2", 4, 30);
  ActionListener a1 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      getAppletContext().showStatus(t1.getText());
    }
  };
  ActionListener a2 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      t2.setText("Inserted by Button 2");
      t2.append(": " + t1.getText());
      getAppletContext().showStatus(t2.getText());
    }
  };
  ActionListener a3 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      String s = " Replacement ";
      t2.replaceRange(s, 3, 3 + s.length());
    }
  };
  ActionListener a4 = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      t2.insert(" Inserted ", 10);
    }
  };
  public void init() {
    b1.addActionListener(a1);
    b2.addActionListener(a2);
    b3.addActionListener(a3);
    b4.addActionListener(a4);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(t1);
    cp.add(b2);
    cp.add(t2);
    cp.add(b3);
    cp.add(b4);
  }
  public static void main(String[] args) {
    Console.run(new TextArea1(), 350, 200);
  }
} ///:~

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 JLabel 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 JLabel 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 or a label with initial text in it (which is what you’ll typically do). Less frequently used options include changing the alignment of labels. You can also change the label with setText( ) and and read the value with getText( ). This example shows things you might commonly do with labels:

//: c13:Label1.java
// Using JLabels.
// <applet code=Label1 width=200 height=100>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Label1 extends JApplet {
  JTextField t1 = new JTextField("t1", 10);
  JLabel 
    labl1 = new JLabel("TextField t1"),
    labl2 = new JLabel("                   ");
  JButton 
    b1 = new JButton("Test 1"),
    b2 = new JButton("Test 2");
  ActionListener a1 = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      t1.setText("Button 1");
      labl2.setText("Text set into Label");
    }
  };
  ActionListener a2 = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      t1.setText("Button 2");
      labl1.setText("Hello");
    }
  };
  public void init() {
    b1.addActionListener(a1);
    b2.addActionListener(a2);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(labl1); 
    cp.add(t1);
    cp.add(b1); 
    cp.add(labl2);
    cp.add(b2);
  }
  public static void main(String[] args) {
    Console.run(new Label1(), 200, 100);
  }
} ///:~

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.

HTML text on Swing components

Any component that can take text can also take HTML text, which it will reformat according to HTML rules. This means you can very easily add fancy text to a Swing component. For example,

JButton bigButton = 
  new JButton( "<html><b><font size=+2>" +
    "Hello!<br><i>Press me now!" )); 

This produces [[ etc. ]]

[[rewrite]] The same trick works for JLabel, JTabbedPane, JMenuItem, and JToolTip. Future releases of Swing may also add HTML support to JRadioButton and JCheckBox

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:

//: c13:CheckBox1.java
// Using JCheckBoxes.
// <applet code=CheckBox1 width=200 height=200>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class CheckBox1 extends JApplet {
  JTextArea t = new JTextArea(6, 15);
  JCheckBox 
    cb1 = new JCheckBox("Check Box 1"),
    cb2 = new JCheckBox("Check Box 2"),
    cb3 = new JCheckBox("Check Box 3");
  public void init() {
    cb1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        trace("1", cb1);
      }
    });
    cb2.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        trace("2", cb2);
      }
    });
    cb3.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        trace("3", cb3);
      }
    });
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(new JScrollPane(t));
    cp.add(cb1); 
    cp.add(cb2); 
    cp.add(cb3);
  }
  void trace(String b, JCheckBox cb) {
    if(cb.isSelected())
      t.append("Box " + b + " Set\n");
    else
      t.append("Box " + b + " Cleared\n");
  }
  public static void main(String[] args) {
    Console.run(new CheckBox1(), 200, 200);
  }
} ///:~

Notice that in this example, the anonymous inner class that implements ActionListener is created inline, so no intermediate variable is used.

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.

Swing 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:

//: c13:RadioButton1.java
// Using JRadioButtons.
// <applet code=RadioButton1 
// width=200 height=75> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class RadioButton1 extends JApplet {
  JTextField t = 
    new JTextField("Radio button 2", 15);
  ButtonGroup g = new ButtonGroup();
  JRadioButton 
    rb1 = new JRadioButton("one", false),
    rb2 = new JRadioButton("two", true),
    rb3 = new JRadioButton("three",false);
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      t.setText("Radio button " + 
        ((JRadioButton)e.getSource()).getText());
    }
  };
  public void init() {
    rb1.addActionListener(al);
    rb2.addActionListener(al);
    rb3.addActionListener(al);
    g.add(rb1); g.add(rb2); g.add(rb3);
    t.setEditable(false);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t); 
    cp.add(rb1); 
    cp.add(rb2); 
    cp.add(rb3); 
  }
  public static void main(String[] args) {
    Console.run(new RadioButton1(), 200, 75);
  }
} ///:~

To display the state, a 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 two” 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:

//: c13:Choice1.java
// Using drop-down lists.
// <applet code=Choice1
// width=200 height=100> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Choice1 extends JApplet {
  String[] description = { "Ebullient", "Obtuse",
    "Recalcitrant", "Brilliant", "Somnescent",
    "Timorous", "Florid", "Putrescent" };
  JTextField t = new JTextField(15);
  JComboBox c = new JComboBox();
  JButton b = new JButton("Add items");
  int count = 0;
  public void init() {
    for(int i = 0; i < 4; i++)
      c.addItem(description[count++]);
    t.setEditable(false);
    b.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        if(count < description.length)
          c.addItem(description[count++]);
      }
    });
    c.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        t.setText("index: "+ c.getSelectedIndex()
          + "   " + ((JComboBox)e.getSource())
          .getSelectedItem());
      }
    });
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    cp.add(c);
    cp.add(b);
  }
  public static void main(String[] args) {
    Console.run(new Choice1(), 200, 100);
  }
} ///:~

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[63] (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:

//: c13:List1.java
// Using JLists.
// <applet code=List1
// width=200 height=350> </applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class List1 extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  JList list = new JList(flavors);
  JTextArea t = 
    new JTextArea(flavors.length + 1, 15);
  public void init() {
    t.setEditable(false);
    list.addListSelectionListener(
      new ListSelectionListener() {
      public void
      valueChanged(ListSelectionEvent e) {
        t.setText(""); // Erase the text area
        Object[] items= list.getSelectedValues();
        for(int i = 0; i < items.length; i++)
          t.append(items[i] + "\n");
      }
    });
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    cp.add(list);
  }
  public static void main(String[] args) {
    Console.run(new List1(), 200, 350);
  }
} ///:~

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.

Tabbed panes

The JTabbedPane allows you to create a “tabbed dialog,” which has file-folder tabs running across one edge, and all you have to do is press a tab to bring forward a different dialog.

//: c13:TabbedPane1.java
// Demonstrates the Tabbed Pane.
// <applet code=TabbedPane1 
// width=350 height=200> </applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class TabbedPane1 extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  JTabbedPane tabs = new JTabbedPane();
  public void init() {
    for(int i = 0; i < flavors.length; i++)
      tabs.addTab(flavors[i], 
        new JButton("Tabbed pane " + i));
    tabs.addChangeListener(new ChangeListener(){
      public void stateChanged(ChangeEvent e) {
        getAppletContext().showStatus(
          "Tab selected: " + 
            tabs.getSelectedIndex());
      }
    });
    getContentPane().add(tabs);
  }
  public static void main(String[] args) {
    Console.run(new TabbedPane1(), 350, 200);
  }
} ///:~

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.

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. You can see this by resizing the window when you run the program from the console command line.

Message boxes

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( ).

//: c13:MessageBoxes.java
// Demonstrates JoptionPane.
// <applet code=MessageBoxes 
// width=200 height=100> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class MessageBoxes extends JApplet {
  JButton[] b = { new JButton("alert"), 
    new JButton("yes/no"), new JButton("Color"),
    new JButton("input"), new JButton("3 vals")
  };
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      String id = 
        ((JButton)e.getSource()).getText();
      if(id.equals("alert"))
        JOptionPane.showMessageDialog(null, 
          "There's a bug on you!", "Hey!", 
          JOptionPane.ERROR_MESSAGE);
      else if(id.equals("yes/no"))
        JOptionPane.showConfirmDialog(null, 
          "or no", "choose yes", 
          JOptionPane.YES_NO_OPTION);
      else if(id.equals("Color")) {
        Object[] options = { "Red", "Green" };
        int sel = JOptionPane.showOptionDialog(
          null, "Choose a Color!", "Warning", 
          JOptionPane.DEFAULT_OPTION, 
          JOptionPane.WARNING_MESSAGE, null, 
          options, options[0]);
          if(sel != JOptionPane.CLOSED_OPTION)
            getAppletContext().showStatus(
              "Color Selected: " + options[sel]);
      } else if(id.equals("input")) {
        String val = JOptionPane.showInputDialog(
            "How many fingers do you see?"); 
        getAppletContext().showStatus(val);
      } else if(id.equals("3 vals")) {
        Object[] selections = {
          "First", "Second", "Third" };
        Object val = JOptionPane.showInputDialog(
          null, "Choose one", "Input",
          JOptionPane.INFORMATION_MESSAGE, 
          null, selections, selections[0]);
        if(val != null)
          getAppletContext().showStatus(
            val.toString());
      }
    }
  };
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < b.length; i++) {
      b[i].addActionListener(al);
      cp.add(b[i]);
    }
  }
  public static void main(String[] args) {
    Console.run(new MessageBoxes(), 200, 100);
  }
} ///:~


Menus

//: c13:SimpleMenus.java
// <applet code=SimpleMenus 
// width=200 height=75> </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class SimpleMenus extends JApplet {
  JTextField t = 
    new JTextField(15);
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      t.setText(
        ((JMenuItem)e.getSource()).getText());
    }
  };
  JMenu[] menus = { new JMenu("Winken"), 
    new JMenu("Blinken"), new JMenu("Nod") };
  JMenuItem[] items = {
    new JMenuItem("Fee"), new JMenuItem("Fi"),
    new JMenuItem("Fo"),  new JMenuItem("Zip"),
    new JMenuItem("Zap"), new JMenuItem("Zot"), 
    new JMenuItem("Olly"), new JMenuItem("Oxen"),
    new JMenuItem("Free") };
  public void init() {
    JMenuBar mb = new JMenuBar();
    setJMenuBar(mb);
    for(int i = 0; i < items.length; i++) {
      items[i].addActionListener(al);
      menus[i%3].add(items[i]);
    }
    for(int i = 0; i < menus.length; i++)
      mb.add(menus[i]);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t); 
  }
  public static void main(String[] args) {
    Console.run(new SimpleMenus(), 200, 75);
  }
} ///:~


Dialog Boxes

//: c13:DialogDemo.java
// Creating and using Dialog Boxes.
// <applet code=DialogDemo width=125 height=50>
// </applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

class MyDialog extends JDialog {
  public MyDialog(JFrame parent) {
    super(parent, "My dialog", true);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(new JLabel("Here is my dialog"));
    JButton ok = new JButton("OK");
    ok.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        setVisible(false); // Closes the dialog
      }
    });
    cp.add(ok);
    setSize(150,125);
  }
}

public class DialogDemo extends JApplet {
  JButton b1 = new JButton("Dialog Box");
  MyDialog dlg = new MyDialog(null);
  public void init() {
    b1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        dlg.show();
      }
    });
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
  }
  public static void main(String[] args) {
    Console.run(new DialogDemo(), 125, 50);
  }
} ///:~

If you’ve been running these programs, you’ll note that anything that pops up out of an applet, including dialog boxes, is “untrusted.” That is, you get a warning in the window that’s been popped up. 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. 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 is not so common to see an applet that uses a dialog box.

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.

//: c13:FlowLayout1.java
// Demonstrates FlowLayout.
// <applet code=FlowLayout1 
// width=300 height=250> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class FlowLayout1 extends JApplet {
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < 20; i++)
      cp.add(new JButton("Button " + i));
  }
  public static void main(String[] args) {
    Console.run(new FlowLayout1(), 300, 250);
  }
} ///:~

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:

//: c13:BorderLayout1.java
// Demonstrates BorderLayout.
// <applet code=BorderLayout1 
// width=300 height=250> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class BorderLayout1 extends JApplet {
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new BorderLayout());
    cp.add(BorderLayout.NORTH, 
      new JButton("North"));
    cp.add(BorderLayout.SOUTH, 
      new JButton("South"));
    cp.add(BorderLayout.EAST, 
      new JButton("East"));
    cp.add(BorderLayout.WEST, 
      new JButton("West"));
    cp.add(BorderLayout.CENTER, 
      new JButton("Center"));
  }
  public static void main(String[] args) {
    Console.run(new BorderLayout1(), 300, 250);
  }
} ///:~

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.

//: c13:GridLayout1.java
// Demonstrates GridLayout.
// <applet code=GridLayout1 
// width=300 height=250> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class GridLayout1 extends JApplet {
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(7,3));
    for(int i = 0; i < 20; i++)
      cp.add(new JButton("Button " + i));
  }
  public static void main(String[] args) {
    Console.run(new GridLayout1(), 300, 250);
  }
} ///:~

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

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 2 by Horstmann & Cornell (Prentice-Hall, 1999) to get you started.

BoxLayout

Because so many people had so much trouble understanding and working with GridBagLayout, Javasoft introduced BoxLayout in Java 2, which gives you many of the benefits of GridBagLayout without the grief. BoxLayout allows you to control the placement of components either vertically or horizontally, and control the space between the components using something call “Struts and Glue.” First, let’s look at using the BoxLayout directly, as all the other layout managers are used:

//: c13:BoxLayout1.java
// Vertical and horizontal BoxLayouts.
// <applet code=BoxLayout1 
// width=450 height=200> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class BoxLayout1 extends JApplet {
  public void init() {
    JPanel jpv = new JPanel();
    jpv.setLayout(
      new BoxLayout(jpv, BoxLayout.Y_AXIS));
    for(int i = 0; i < 5; i++)
      jpv.add(new JButton("" + i));
    JPanel jph = new JPanel();
    jph.setLayout(
      new BoxLayout(jph, BoxLayout.X_AXIS));
    for(int i = 0; i < 5; i++)
      jph.add(new JButton("" + i));
    Container cp = getContentPane();
    cp.add(BorderLayout.EAST, jpv);
    cp.add(BorderLayout.SOUTH, jph);
  }
  public static void main(String[] args) {
    Console.run(new BoxLayout1(), 450, 200);
  }
} ///:~

The constructor for BoxLayout is a bit different than the other layout managers—you provide the Container that is to be controlled by the BoxLayout as the first argument, and the direction of the layout as the second argument.

To simplify matters, there’s a special container called Box that uses BoxLayout as its native manager. The following example lays out components horizontally and vertically using Box, which has two static methods to create boxes with vertical and horizontal alignment:

//: c13:Box1.java
// Vertical and horizontal BoxLayouts.
// <applet code=Box1 
// width=450 height=200> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Box1 extends JApplet {
  public void init() {
    Box bv = Box.createVerticalBox();
    for(int i = 0; i < 5; i++)
      bv.add(new JButton("" + i));
    Box bh = Box.createHorizontalBox();
    for(int i = 0; i < 5; i++)
      bh.add(new JButton("" + i));
    Container cp = getContentPane();
    cp.add(BorderLayout.EAST, bv);
    cp.add(BorderLayout.SOUTH, bh);
  }
  public static void main(String[] args) {
    Console.run(new Box1(), 450, 200);
  }
} ///:~
//: c13:Box2.java
// Adding struts.
// <applet code=Box2 
// width=450 height=300> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Box2 extends JApplet {
  public void init() {
    Box bv = Box.createVerticalBox();
    for(int i = 0; i < 5; i++) {
      bv.add(new JButton("" + i));
      bv.add(Box.createVerticalStrut(i*10));
    }
    Box bh = Box.createHorizontalBox();
    for(int i = 0; i < 5; i++) {
      bh.add(new JButton("" + i));
      bh.add(Box.createHorizontalStrut(i*10));
    }
    Container cp = getContentPane();
    cp.add(BorderLayout.EAST, bv);
    cp.add(BorderLayout.SOUTH, bh);
  }
  public static void main(String[] args) {
    Console.run(new Box2(), 450, 300);
  }
} ///:~
//: c13:Box3.java
// Using Glue.
// <applet code=Box3 
// width=450 height=300> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Box3 extends JApplet {
  public void init() {
    Box bv = Box.createVerticalBox();
    bv.add(new JLabel("Hello"));
    bv.add(Box.createVerticalGlue());
    bv.add(new JLabel("Applet"));
    bv.add(Box.createVerticalGlue());
    bv.add(new JLabel("World"));
    Box bh = Box.createHorizontalBox();
    bh.add(new JLabel("Hello"));
    bh.add(Box.createHorizontalGlue());
    bh.add(new JLabel("Applet"));
    bh.add(Box.createHorizontalGlue());
    bh.add(new JLabel("World"));
    bv.add(Box.createVerticalGlue());
    bv.add(bh);
    bv.add(Box.createVerticalGlue());
    getContentPane().add(bv);
  }
  public static void main(String[] args) {
    Console.run(new Box3(), 450, 300);
  }
} ///:~
//: c13:Box4.java
// Rigid Areas are like pairs of struts.
// <applet code=Box4 
// width=450 height=300> </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Box4 extends JApplet {
  public void init() {
    Box bv = Box.createVerticalBox();
    bv.add(new JButton("Top"));
    bv.add(Box.createRigidArea(
      new Dimension(120, 90)));
    bv.add(new JButton("Bottom"));
    Box bh = Box.createHorizontalBox();
    bh.add(new JButton("Left"));
    bh.add(Box.createRigidArea(
      new Dimension(160, 80)));
    bh.add(new JButton("Right"));
    bv.add(bh);
    getContentPane().add(bv);
  }
  public static void main(String[] args) {
    Console.run(new Box4(), 450, 300);
  }
} ///:~

You should be aware that rigid areas are a bit controversial. Since they use absolute values, some people feel that they cause more trouble than they are worth.

Absolute positioning

It is possible to set the absolute position of the graphical components in your Frame or Applet in this way:

  1. Set a null layout manager for your Container: setLayout(null).
  2. Call setBounds( ) or reshape (depending on the language version) for each component, passing a bounding rectangle in pixel coordinates. You can do it in the constructor, or in paint( ), depending on what you want to achieve.

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 a TrackEvent “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 TrackEvent. 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.

//: c13:TrackEvent.java
// Show events as they happen.
// <applet code=TrackEvent
//  width=700 height=500></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

class MyButton extends JButton {
  HashMap h;
  public MyButton(TrackEvent parent, 
    Color color, String label) {
    super(label);
    h = parent.h;
    setBackground(color);
    addFocusListener(fl);
    addKeyListener(kl);
    addMouseListener(ml);
    addMouseMotionListener(mml);
  }
  void report(String field, String msg) {
    ((JTextField)h.get(field)).setText(msg);
  }    
  FocusListener fl = new FocusListener() {
    public void focusGained(FocusEvent e) {
      report("focusGained", e.paramString());
    }
    public void focusLost(FocusEvent e) {
      report("focusLost", e.paramString());
    }
  };
  KeyListener kl = new KeyListener() {
    public void keyPressed(KeyEvent e) {
      report("keyPressed", e.paramString());
    }
    public void keyReleased(KeyEvent e) {
      report("keyReleased", e.paramString());
    }
    public void keyTyped(KeyEvent e) {
      report("keyTyped", e.paramString());
    }
  };
  MouseListener ml = new MouseListener() {
    public void mouseClicked(MouseEvent e) {
      report("mouseClicked", e.paramString());
    }
    public void mouseEntered(MouseEvent e) {
      report("mouseEntered", e.paramString());
    }
    public void mouseExited(MouseEvent e) {
      report("mouseExited", e.paramString());
    }
    public void mousePressed(MouseEvent e) {
      report("mousePressed", e.paramString());
    }
    public void mouseReleased(MouseEvent e) {
      report("mouseReleased", e.paramString());
    }
  };
  MouseMotionListener mml = 
    new MouseMotionListener() {
    public void mouseDragged(MouseEvent e) {
      report("mouseDragged", e.paramString());
    }
    public void mouseMoved(MouseEvent e) {
      report("mouseMoved", e.paramString());
    }
  };
}  

public class TrackEvent extends JApplet {
  HashMap h = new HashMap();
  String[] event = {
    "focusGained", "focusLost", "keyPressed",
    "keyReleased", "keyTyped", "mouseClicked",
    "mouseEntered", "mouseExited","mousePressed",
    "mouseReleased", "mouseDragged", "mouseMoved"
  };
  MyButton
    b1 = new MyButton(this, Color.blue, "test1"),
    b2 = new MyButton(this, Color.red, "test2");
  public void init() {
    Container c = getContentPane();
    c.setLayout(new GridLayout(event.length+1,2));
    for(int i = 0; i < event.length; i++) {
      JTextField t = new JTextField();
      t.setEditable(false);
      c.add(new JLabel(event[i], JLabel.RIGHT));
      c.add(t);
      h.put(event[i], t);
    }
    c.add(b1);
    c.add(b2);
  }
  public static void main(String[] args) {
    Console.run(new TrackEvent(), 700, 500);
  }
} ///:~

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 TrackEvent class. This contains a HashMap 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 HashMap, 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 TrackEvent, 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 reference to reach back to the parent window. Since that parent is an TrackEvent it contains the HashMap h, and the get( ) method, when provided with the appropriate String, will produce a reference 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.

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

[Probably remove this, or move the example]

Here’s an example of a windowed application:

//: c13:ButtonApp.java
// Creating an application.
// <applet code=ButtonApp
//  width=200 height=200></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class ButtonApp extends JApplet {
  JButton 
    b1 = new JButton("Hello"), 
    b2 = new JButton("Howdy");
  JTextField t = new JTextField(15);
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e){
      String name = 
        ((JButton)e.getSource()).getText();
      t.setText(name);
    }
  };
  public void init() {
    b1.addActionListener(al);
    b2.addActionListener(al);
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
    cp.add(t);
  }
  public static void main(String[] args) {
    Console.run(new ButtonApp(), 200, 200);
  }
} ///:~


Menus

There are four different types inherited from the abstract class MenuComponent: 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 Swing you must hand assemble all the menus in source code. Here are the ice cream flavors again, used to create menus:

//: c13:Menu1.java
// Shows submenus, checkbox menu 
// items, and swapping menus
// <applet code=Menu1
//  width=300 height=100> </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Menu1 extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" 
  };
  JTextField t = new JTextField("No flavor", 30);
  JMenuBar mb1 = new JMenuBar();
  JMenu 
    f = new JMenu("File"),
    m = new JMenu("Flavors"),
    s = new JMenu("Safety");
  // Alternative approach:
  JCheckBoxMenuItem[] safety = {
    new JCheckBoxMenuItem("Guard"),
    new JCheckBoxMenuItem("Hide")
  };
  JMenuItem[] file = {
    new JMenuItem("Open"),
    new JMenuItem("Exit")
  };
  // A second menu bar to swap to:
  JMenuBar mb2 = new JMenuBar();
  JMenu fooBar = new JMenu("fooBar");
  JMenuItem[] other = {
    new JMenuItem("Foo"),
    new JMenuItem("Bar"),
    new JMenuItem("Baz"),
  };
  JButton b = new JButton("Swap Menus");
  public void init() {
    for(int i = 0; i < flavors.length; i++) {
      JMenuItem mi = new JMenuItem(flavors[i]);
      mi.addActionListener(al);
      m.add(mi);
      // Add separators at intervals:
      if((i+1) % 3 == 0) 
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++) {
      safety[i].addActionListener(al);
      s.add(safety[i]);
    }
    f.add(s);
    for(int i = 0; i < file.length; i++) {
      file[i].addActionListener(al);
      f.add(file[i]);
    }
    mb1.add(f);
    mb1.add(m);
    t.setEditable(false);
    Container cp = getContentPane();
    cp.add(t, BorderLayout.CENTER);
    // Set up the system for swapping menus:
    b.addActionListener(al);
    cp.add(b, BorderLayout.NORTH);
    for(int i = 0; i < other.length; i++) {
      other[i].addActionListener(al);
      fooBar.add(other[i]);
    }
    mb2.add(fooBar);
    setJMenuBar(mb1);
  }
  ActionListener al = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      String arg = e.getActionCommand();
      Object source = e.getSource();
      if(source.equals(b)) {
        JMenuBar m = getJMenuBar();
        setJMenuBar(m == mb1 ? mb2 : mb1);
        validate(); // Refresh the frame
      } else if(source instanceof JMenuItem) {
        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(source.equals(file[1]))
          System.exit(0);
        // CheckboxMenuItems cannot use String 
        // matching; you must match getSource():
        else if(source.equals(safety[0]))
          t.setText("Guard the Ice Cream! " +
            "Guarding is "+safety[0].getState());
        else if(source.equals(safety[1]))
          t.setText("Hide the Ice Cream! " +
            "Is it cold? "+safety[1].getState());
        else 
          t.setText(arg);
      }
    }
  };
  public static void main(String[] args) {
    Console.run(new Menu1(), 300, 100);
  }
} ///:~

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 references 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 reference. 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.

//: c13:TicTacToe.java
// Demonstration of dialog boxes
// and creating your own components.
// <applet code=TicTacToe
//  width=200 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

class ToeButton extends JPanel {
  int state = ToeDialog.BLANK;
  ToeDialog p;
  public ToeButton(ToeDialog parent) {
    p = parent;
    addMouseListener(ml);
  }
  MouseListener ml = new MouseAdapter() {
    public void mousePressed(MouseEvent e) {
      if(state == ToeDialog.BLANK) {
        state = p.turn;
        p.turn = (p.turn == ToeDialog.XX ?
          ToeDialog.OO : ToeDialog.XX);
      } else
        state = (state == ToeDialog.XX ? 
          ToeDialog.OO : ToeDialog.XX);
      repaint();
    }
  };
  public void paintComponent(Graphics g) {
    super.paintComponent(g);
    int x1 = 0;
    int y1 = 0;
    int x2 = getWidth() - 1;
    int y2 = getHeight() - 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);
    }
  }
}

class ToeDialog extends JDialog {
  static final int BLANK = 0, XX = 1, OO = 2;
  int turn = XX; // Start with x's turn
  // w = number of cells wide
  // h = number of cells high
  public ToeDialog(int w, int h) {
    setTitle("The game itself");
   // JDK 1.3 close dialog:
   //#setDefaultCloseOperation(DISPOSE_ON_CLOSE);
    // JDK 1.2 close dialog:
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e){
        dispose();
      }
    });    
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(w, h));
    for(int i = 0; i < w * h; i++)
      cp.add(new ToeButton(this));
    setSize(w * 50, h * 50);
  }
}

public class TicTacToe extends JApplet {
  JTextField 
    rows = new JTextField("3"),
    cols = new JTextField("3");
  JButton go = new JButton("go");
  public void init() {
    JPanel p = new JPanel();
    p.setLayout(new GridLayout(2,2));
    p.add(new JLabel("Rows", JLabel.CENTER));
    p.add(rows);
    p.add(new JLabel("Columns", JLabel.CENTER));
    p.add(cols);
    Container cp = getContentPane();
    cp.add(p, BorderLayout.NORTH);
    cp.add(go, BorderLayout.SOUTH);
    go.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e){
        JDialog d = new ToeDialog(
          Integer.parseInt(rows.getText()),
          Integer.parseInt(cols.getText()));
        d.setVisible(true);
      }
    });
  }
  public static void main(String[] args) {
    Console.run(new TicTacToe(), 200, 100);
  }
} ///:~

The ToeButton class keeps a reference 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.

TicTacToe 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 reference 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 reference 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 JFileChooser encapsulates these for easy use.

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

//: c13:FileChooserTest.java
// Demonstration of File dialog boxes.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class FileChooserTest extends JFrame {
  JTextField 
    filename = new JTextField(),
    dir = new JTextField();
  JButton 
    open = new JButton("Open"),
    save = new JButton("Save");
  public FileChooserTest() {
    setTitle("File Dialog Test");
    JPanel p = new JPanel();
    open.addActionListener(new OpenL());
    p.add(open);
    save.addActionListener(new SaveL());
    p.add(save);
    Container cp = getContentPane();
    cp.add(p, BorderLayout.SOUTH);
    dir.setEditable(false);
    filename.setEditable(false);
    p = new JPanel();
    p.setLayout(new GridLayout(2,1));
    p.add(filename);
    p.add(dir);
    cp.add(p, BorderLayout.NORTH);
  }
  class OpenL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JFileChooser c = new JFileChooser();
      // Demonstrate "Open" dialog:
      int rVal = 
        c.showOpenDialog(FileChooserTest.this);
      if(rVal == JFileChooser.APPROVE_OPTION) {
        filename.setText(
          c.getSelectedFile().getName());
          dir.setText(
            c.getCurrentDirectory().toString());
      }
      if(rVal == JFileChooser.CANCEL_OPTION) {
        filename.setText("You pressed cancel");
        dir.setText("");
      }
    }
  }
  class SaveL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JFileChooser c = new JFileChooser();
      // Demonstrate "Save" dialog:
      int rVal = 
        c.showSaveDialog(FileChooserTest.this);
      if(rVal == JFileChooser.APPROVE_OPTION) {
        filename.setText(
          c.getSelectedFile().getName());
          dir.setText(
            c.getCurrentDirectory().toString());
      }
      if(rVal == JFileChooser.CANCEL_OPTION) {
        filename.setText("You pressed cancel");
        dir.setText("");
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new FileChooserTest(), 250, 110);
  }
} ///:~

Note that there are many variations you can apply to JFileChooser, including filters to narrow the file names that you will allow.

For an “open file” dialog, you use the constructor that takes two arguments; the first is the parent window reference 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 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 reference 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.

//: c13:Button2New.java
// Capturing button presses.
// <applet code=Button2New 
// width=200 height=50> </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*; // Must add this
import com.bruceeckel.swing.*;

public class Button2New extends JApplet {
  JButton
    b1 = new JButton("Button 1"),
    b2 = new JButton("Button 2");
  public void init() {
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.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");
    }
  }
  public static void main(String[] args) {
    Console.run(new Button2New(), 200, 50);
  }
} ///:~

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 Swing 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[64] (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.

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.

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( ):

//: c13:Applet3.java
// Shows init(), start() and stop() activities.
// <applet code=Applet3 width=250 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import com.bruceeckel.swing.*;

public class Applet3 extends JApplet {
  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);
  }
  public static void main(String[] args) {
    Console.run(new Applet3 (), 250, 50);
  }
} ///:~

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.

Text fields

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

//: c13:TextNew.java
// Text fields and Java events.
// <applet code=TextNew width=375
// height=125></applet>
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class TextNew extends JApplet {
  JButton
    b1 = new JButton("Get Text"),
    b2 = new JButton("Set Text");
  JTextField
    t1 = new JTextField(30),
    t2 = new JTextField(30),
    t3 = new JTextField(30);
  String s = new String();
  UpperCaseDocument
    ucd = new UpperCaseDocument();
  public void init() {
    t1.setDocument(ucd);
    ucd.addDocumentListener(new T1());
    b1.addActionListener(new B1());
    b2.addActionListener(new B2());
    DocumentListener dl = new T1();
    t1.addActionListener(new T1A());
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(b1);
    cp.add(b2);
    cp.add(t1);
    cp.add(t2);
    cp.add(t3);
  }
  class T1 implements DocumentListener {
    public void changedUpdate(DocumentEvent e){}
    public void insertUpdate(DocumentEvent e){
      t2.setText(t1.getText());
      t3.setText("Text: "+ t1.getText());
    }
    public void removeUpdate(DocumentEvent 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 B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(t1.getSelectedText() == null)
        s = t1.getText();
      else
        s = t1.getSelectedText();
      t1.setEditable(true);
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      ucd.setUpperCase(false);
      t1.setText("Inserted by Button 2: " + s);
      ucd.setUpperCase(true);
      t1.setEditable(false);
    }
  }
  public static void main(String[] args) {
    Console.run(new TextNew(), 375, 125);
  }
}

class UpperCaseDocument extends PlainDocument {
  boolean upperCase = true;
  public void setUpperCase(boolean flag) {
    upperCase = flag;
  }
  public void insertString(int offset, 
    String string, AttributeSet attributeSet)
    throws BadLocationException {
      if(upperCase)
        string = string.toUpperCase();
      super.insertString(offset, 
        string, attributeSet);
  }
} ///:~

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 reference 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.[65]

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:

//: c13:TextAreaNew.java
// Controlling scrollbars with JtextArea.
// <applet code=TextAreaNew width=300 height=725>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;

public class TextAreaNew extends JApplet {
  JButton 
    b1 = new JButton("Text Area 1"),
    b2 = new JButton("Text Area 2"),
    b3 = new JButton("Replace Text"),
    b4 = new JButton("Insert Text");
  JTextArea 
    t1 = new JTextArea("t1", 1, 20),
    t2 = new JTextArea("t2", 4, 20),
    t3 = new JTextArea("t3", 1, 20),
    t4 = new JTextArea("t4", 10, 10),
    t5 = new JTextArea("t5", 4, 20),
    t6 = new JTextArea("t6", 10, 10);
  JScrollPane 
    sp3 = new JScrollPane(t3,
      JScrollPane.VERTICAL_SCROLLBAR_NEVER,
      JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
    sp4 = new JScrollPane(t4,
      JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
      JScrollPane.HORIZONTAL_SCROLLBAR_NEVER),
    sp5 = new JScrollPane(t5,
      JScrollPane.VERTICAL_SCROLLBAR_NEVER,
      JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS),
    sp6 = new JScrollPane(t6,
      JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
      JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
  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 void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    // Create Borders for components:
    Border brd = BorderFactory.createMatteBorder(
      1, 1, 1, 1, Color.black);
    t1.setBorder(brd);
    t2.setBorder(brd);
    sp3.setBorder(brd);
    sp4.setBorder(brd);
    sp5.setBorder(brd);
    sp6.setBorder(brd);
    // Initialize listeners and add components:
    b1.addActionListener(new B1L());
    cp.add(b1);
    cp.add(t1);
    b2.addActionListener(new B2L());
    cp.add(b2);
    cp.add(t2);
    b3.addActionListener(new B3L());
    cp.add(b3);
    b4.addActionListener(new B4L());
    cp.add(b4);
    cp.add(sp3); 
    cp.add(sp4); 
    cp.add(sp5);
    cp.add(sp6);
  }
  public static void main(String[] args) {
    Console.run(new TextAreaNew(), 300, 725);
  }
} ///:~

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:

//: c13:RadioCheckNew.java
// Radio buttons and Check Boxes.
// <applet code=RadioCheckNew 
// width=325 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class RadioCheckNew extends JApplet {
  JTextField t = new JTextField(20);
  JCheckBox[] cb = {
    new JCheckBox("Check Box 1"),
    new JCheckBox("Check Box 2"),
    new JCheckBox("Check Box 3") };
  ButtonGroup group = new ButtonGroup();
  JRadioButton 
    cb4 = new JRadioButton("four"),
    cb5 = new JRadioButton("five"),
    cb6 = new JRadioButton("six");
  // 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 void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    t.setEditable(false);
    cp.add(t); 
    ILCheck il = new ILCheck();
    for(int i = 0; i < cb.length; i++) {
      cb[i].addItemListener(il);
      cp.add(cb[i]);
    }
    group.add(cb4);
    group.add(cb5);
    group.add(cb6);
    cb4.addItemListener(new IL4());
    cb5.addItemListener(new IL5());
    cb6.addItemListener(new IL6());
    cp.add(cb4); cp.add(cb5); cp.add(cb6); 
  }
  public static void main(String[] args) {
    Console.run(new RadioCheckNew(), 325, 100);
  }
} ///:~

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:

//: c13:ChoiceNew.java
// Drop-down lists (combo boxes).
// <applet code=ChoiceNew 
// width=450 height=175></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class ChoiceNew extends JApplet {
  String[] descriptions1 = { "Ebullient", 
    "Obtuse", "Recalcitrant", "Brilliant" };
  String[] descriptions2 = { "Somnescent",
    "Timorous", "Florid", "Putrescent" };
  JTextArea t = new JTextArea(7, 40);
  JComboBox c = new JComboBox(descriptions1);
  JButton b = new JButton("Add items");
  int count = 0;
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    t.setLineWrap(true);
    t.setEditable(false);
    cp.add(t);
    cp.add(c);
    cp.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);
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < descriptions2.length)
        c.addItem(descriptions2[count++]);
      if(count >=descriptions2.length)
        b.setEnabled(false);
    }
  }
  public static void main(String[] args) {
    Console.run(new ChoiceNew(), 450, 175);
  }
} ///:~

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:

//: c13:ListNew.java
// <applet code=ListNew width=250
// height=325> </applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;

public class ListNew extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip",
    "Mocha Almond Fudge", "Rum Raisin",
    "Praline Cream", "Mud Pie" };
  DefaultListModel lItems=new DefaultListModel();
  JList lst = new JList(lItems);
  JTextArea t = new JTextArea(flavors.length,20);
  JButton b = new JButton("Add Item");
  ActionListener bl = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      if(count < flavors.length) {
        lItems.add(0, flavors[count++]);
      } else {
        // Disable, since there are no more
        // flavors left to be added to the List
        b.setEnabled(false);
      }
    }
  };
  ListSelectionListener ll =
    new ListSelectionListener() {
      public void valueChanged(
        ListSelectionEvent e) {
          t.setText("");
          Object[] items=lst.getSelectedValues();
          for(int i = 0; i < items.length; i++)
            t.append(items[i] + "\n");
        }
    };
  int count = 0;
  public void init() {
    Container cp = getContentPane();
    t.setEditable(false);
    cp.setLayout(new FlowLayout());
    // Create Borders for components:
    Border brd = BorderFactory.createMatteBorder(
      1, 1, 2, 2, Color.black);
    lst.setBorder(brd);
    t.setBorder(brd);
    // Add the first four items to the List
    for(int i = 0; i < 4; i++)
      lItems.addElement(flavors[count++]);
    // Add items to the Content Pane for Display
    cp.add(t);
    cp.add(lst);
    cp.add(b);
    // Register event listeners
    lst.addListSelectionListener(ll);
    b.addActionListener(bl);
  }
  public static void main(String[] args) {
    Console.run(new ListNew(), 250, 325);
  }
} ///:~

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.

//: c13:MenuNew.java
// Menu shortcuts and action commands.
// <applet code=MenuNew width=300
// height=100> </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class MenuNew extends JApplet {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip", 
    "Mocha Almond Fudge", "Rum Raisin", 
    "Praline Cream", "Mud Pie" };
  JTextField t = new JTextField("No flavor", 30);
  JMenuBar mb1 = new JMenuBar();
  JMenu 
    f = new JMenu("File"),
    m = new JMenu("Flavors"),
    s = new JMenu("Safety");
  // Alternative approach:
  JCheckBoxMenuItem[] safety = {
    new JCheckBoxMenuItem("Guard"),
    new JCheckBoxMenuItem("Hide")
  };
  JMenuItem[] file = {
    // No menu shortcut:
    new JMenuItem("Open"),
    // Adding a menu shortcut is very simple:
    new JMenuItem("Exit", KeyEvent.VK_E)
  };
  // A second menu bar to swap to:
  JMenuBar mb2 = new JMenuBar();
  JMenu fooBar = new JMenu("fooBar");
  JMenuItem[] other = {
    new JMenuItem("Foo"),
    new JMenuItem("Bar"),
    new JMenuItem("Baz"),
  };
  JButton b = new JButton("Swap Menus");
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuBar m = getJMenuBar();
      setJMenuBar(m == mb1 ? mb2 : mb1);
      validate(); // Refresh the frame ///////// Necessary???
    }
  }
  class ML implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuItem target = (JMenuItem)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")) {
        // This trick won't work with applets
        // because MenuNew.this doesn't produce
        // a Window if this is a JApplet:
        //dispatchEvent(
        //  new WindowEvent(MenuNew.this, 
        //    WindowEvent.WINDOW_CLOSING));
      }
    }
  }
  class FL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      JMenuItem target = (JMenuItem)e.getSource();
      t.setText(target.getText());
    }
  }
  // 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) {
      JCheckBoxMenuItem target = 
        (JCheckBoxMenuItem)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 void init() {
    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());
    FL fl = new FL();
    for(int i = 0; i < flavors.length; i++) {
      JMenuItem mi = new JMenuItem(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);
    setJMenuBar(mb1);
    t.setEditable(false);
    Container cp = getContentPane();
    cp.add(t, BorderLayout.CENTER);
    // Set up the system for swapping menus:
    b.addActionListener(new BL());
    cp.add(b, BorderLayout.NORTH);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  public static void main(String[] args) {
    Console.run(new MenuNew(), 300, 100);
  }
} ///:~

This code is similar to the previous (Java 1.0) version, until you get to the init( ) method. 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 reference 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 TicTacToe.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 TicTacToe.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.

//: c13:TicTacToeInner.java
// TicTacToe.java with heavy use of inner classes.
// <applet code=TicTacToeInner
//  width=200 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class TicTacToeInner extends JApplet {
  JTextField 
    rows = new JTextField("3"),
    cols = new JTextField("3");
  static final int BLANK = 0, XX = 1, OO = 2;
  class ToeDialog extends JDialog {
    int turn = XX; // Start with x's turn
    // w = number of cells wide
    // h = number of cells high
    public ToeDialog(int w, int h) {
      setTitle("The game itself");
      Container cp = getContentPane();
      cp.setLayout(new GridLayout(w, h));
      for(int i = 0; i < w * h; i++)
        cp.add(new ToeButton());
      setSize(w * 50, h * 50);
      addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          dispose();
        }
      });    
    }
    class ToeButton extends JPanel {
      int state = BLANK;
      public ToeButton() {
        addMouseListener(new ML());
      }
      public void paintComponent(Graphics g) {
        super.paintComponent(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) {
      JDialog d = new ToeDialog(
        Integer.parseInt(rows.getText()),
        Integer.parseInt(cols.getText()));
      d.setVisible(true);
    }
  }
  public void init() {
    JPanel p = new JPanel();
    p.setLayout(new GridLayout(2,2));
    p.add(new JLabel("Rows", JLabel.CENTER));
    p.add(rows);
    p.add(new JLabel("Columns", JLabel.CENTER));
    p.add(cols);
    Container cp = getContentPane();
    cp.add(p, BorderLayout.NORTH);
    JButton b = new JButton("go");
    b.addActionListener(new BL());
    cp.add(b, BorderLayout.SOUTH);
  }
  public static void main(String[] args) {
    Console.run(new TicTacToeInner(), 200, 100);
  }
} ///:~

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

Selecting Look & Feel

One of the very interesting aspects of Swing is the “Pluggable Look & Feel.” This allows your program to emulate the look and feel of various operating environments. You can even do all sorts of fancy things like dynamically changing the look and feel while the program is executing. However, you generally just want to do one of two things, either select the “cross platform” look and feel (which is Swing’s “metal”) or select the look and feel for the system you are currently on, so your Java program looks like it was created specifically for that system. The code to select either of these behaviors is quite simple, but you must execute it before you create any visual components because the components will be made based on the current look and feel and will not be changed just because you happen to change the look and feel midway during the program (that process is more complicated and uncommon, and is relegated to Swing-specific books).

Actually, if you want to use the cross-platform (“metal”) look and feel that is characteristic of Swing programs, you don’t have to do anything—it’s the default. But if you want instead to use the current operating environment’s look and feel, you just insert the following code, typically at the beginning of your main( ) but somehow before any components are added:

try {
  UIManager.setLookAndFeel(UIManager.
    getSystemLookAndFeelClassName());
} catch (Exception ex) { }

You don’t need anything in the catch clause because the UIManager will default to the cross-platform look and feel if your attempts to set up any of the alternatives fail. However, during debugging the exception can be quite useful.

Here is a program that takes a command-line argument to select a look and feel, and shows how a selection of components look:

//: c13:LookAndFeel.java
// Selecting different looks & feels.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

public class LookAndFeel extends JFrame {
  String[] choices = { 
    "eeny", "meeny", "minie", "moe", "toe", "you"
  };
  Component[] samples = {
    new JButton("JButton"),
    new JTextField("JTextField"),
    new JLabel("JLabel"),
    new JCheckBox("JCheckBox"),
    new JRadioButton("Radio"),
    new JComboBox(choices),
    new JList(choices),
  };
  public LookAndFeel() {
    super("Look And Feel");
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    for(int i = 0; i < samples.length; i++)
      cp.add(samples[i]);
  }
  private static void usageError() {
    System.out.println(
      "Usage:LookAndFeel [cross|system|motif]");
    System.exit(1);
  }
  public static void main(String[] args) {
    if(args.length == 0) usageError();
    if(args[0].equals("cross")) {
      try {
        UIManager.setLookAndFeel(UIManager.
          getCrossPlatformLookAndFeelClassName());
      } catch (Exception ex) {
          ex.printStackTrace();
      }
    } else if(args[0].equals("system")) {
      try {
        UIManager.setLookAndFeel(UIManager.
          getSystemLookAndFeelClassName());
      } catch (Exception ex) {
          ex.printStackTrace();
      }
    } else if(args[0].equals("motif")) {
      try {
        UIManager.setLookAndFeel("com.sun.java."+
          "swing.plaf.motif.MotifLookAndFeel");
      } catch (Exception ex) {
          ex.printStackTrace();
      }
    } else usageError();
    // Note the look & feel must be set before
    // any components are created.
    Console.run(new LookAndFeel(), 300, 200);
  }
} ///:~

You can see that one option is to explicitly specify a string for a look and feel, as seen with MotifLookAndFeel. However, that one and the default “metal” look and feel are the only ones that can legally be used on any platform; even though there are strings for Windows and Macintosh look and feels, those can only be used on their respective platforms (these are produced when you call getSystemLookAndFeelClassName( ) and you’re on that particular platform).

It is also possible to create a custom look and feel package, for example if you are building a framework for a company that wants a distinctive appearance. This is a big job and is far beyond the scope of this book (in fact, you’ll discover it is beyond the scope of many dedicated Swing books!).

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:

//: c13:DynamicEvents.java
// You can change event behavior dynamically.
// Also shows multiple actions for an event.
// <applet code=DynamicEvents
//  width=200 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

public class DynamicEvents extends JApplet {
  ArrayList v = new ArrayList();
  int i = 0;
  JButton
    b1 = new JButton("Button1"),
    b2 = new JButton("Button2");
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    b1.addActionListener(new B());
    b1.addActionListener(new B1());
    b2.addActionListener(new B());
    b2.addActionListener(new B2());
    cp.add(b1);
    cp.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.add(a);
      b2.addActionListener(a);
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button2 pressed");
      int end = v.size() - 1;
      if(end >= 0) {
        b2.removeActionListener(
          (ActionListener)v.get(end));
        v.remove(end);
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new DynamicEvents(), 200, 100);
  }
} ///:~

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:

//: c13:Separation.java
// Separating GUI logic and business objects.
// <applet code=Separation
// width=250 height=100> </applet>
import javax.swing.*;
import java.awt.*;
import javax.swing.event.*;
import java.awt.event.*;
import java.applet.*;
import com.bruceeckel.swing.*;

class BusinessLogic {
  private int modifier;
  public 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 JApplet {
  JTextField 
    t = new JTextField(15),
    mod = new JTextField(15);
  BusinessLogic bl = new BusinessLogic(2);
  JButton
    calc1 = new JButton("Calculation 1"),
    calc2 = new JButton("Calculation 2");
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(t);
    calc1.addActionListener(new Calc1L());
    calc2.addActionListener(new Calc2L());
    JPanel p1 = new JPanel();
    p1.add(calc1); 
    p1.add(calc2);
    cp.add(p1);
    mod.getDocument().
      addDocumentListener(new ModL());
    JPanel p2 = new JPanel();
    p2.add(new JLabel("Modifier:"));
    p2.add(mod);
    cp.add(p2);
  }
  static int getValue(JTextField 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))));
    }
  }
  // If you want something to happen whenever
  // a JTextField changes, add this listener:
  class ModL implements DocumentListener {
    public void changedUpdate(DocumentEvent e) {}
    public void insertUpdate(DocumentEvent e) {
      bl.setModifier(getValue(mod));
    }
    public void removeUpdate(DocumentEvent e) {
      bl.setModifier(getValue(mod));
    }
  }
  public static void main(String[] args) {
    Console.run(new Separation(), 250, 100);
  }
} ///:~

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:

//: c13:GoodIdea.java
// The best way to design classes using the
// Java event model: use an inner class for
// each different event. This maximizes 
// flexibility and modularity.
// <applet code=GoodIdea
//  width=200 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;

public class GoodIdea extends JApplet {
  JButton 
    b1 = new JButton("Button 1"), 
    b2 = new JButton("Button 2");
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    b1.addActionListener(new B1L());
    b2.addActionListener(new B2L());
    cp.add(b1);
    cp.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) {
    Console.run(new GoodIdea(), 200, 100);
  }
} ///:~

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.

JFC APIs

JFC includes important functionality, including focus traversal, desktop color access, printing “inside the sandbox,” the beginnings of clipboard support, and drag-and-drop.

Focus traversal is quite easy, since it’s transparently present in Swing 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).

The clipboard

The JFC 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. 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.

//: c13:CutAndPaste.java
// Using the clipboard.
// <applet code=CutAndPaste
//  width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import com.bruceeckel.swing.*;

public class CutAndPaste extends JApplet  {
  JMenuBar mb = new JMenuBar();
  JMenu edit = new JMenu("Edit");
  JMenuItem
    cut = new JMenuItem("Cut"),
    copy = new JMenuItem("Copy"),
    paste = new JMenuItem("Paste");
  JTextArea text = new JTextArea(20, 20);
  Clipboard clipbd = 
    getToolkit().getSystemClipboard();
  public void init()  {
    cut.addActionListener(new CutL());
    copy.addActionListener(new CopyL());
    paste.addActionListener(new PasteL());
    edit.add(cut);
    edit.add(copy);
    edit.add(paste);
    mb.add(edit);
    setJMenuBar(mb);
    getContentPane().add(text);
  }
  class CopyL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      if (selection == null)
        return;
      StringSelection clipString =
        new StringSelection(selection);
      clipbd.setContents(clipString,clipString);
    }
  }
  class CutL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String selection = text.getSelectedText();
      if (selection == null)
        return;
      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 evt) {
        System.out.println("not String flavor");
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new CutAndPaste(), 300, 200);
  }
} ///:~

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 palette 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 Thinking in Patterns with Java, downloadable at www.BruceEckel.com) 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:

//: frogbean: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 12: 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 reference 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):

//: c13: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);
      Method writeMethod = 
        properties[i].getWriteMethod();
      if(writeMethod != null)
        System.out.println(
          "Write method:\n  " + writeMethod);
      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());
    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());
      Method addListener = 
        events[i].getAddListenerMethod();
      System.out.println(
          "Add Listener Method:\n  " +
          addListener);
      Method removeListener =
        events[i].getRemoveListenerMethod();
      System.out.println(
        "Remove Listener Method:\n  " +
        removeListener);
      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 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:BangBean.java
// A graphical Bean.
package bangbean;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import com.bruceeckel.swing.*;

public class BangBean extends JPanel
     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 paintComponent(Graphics g) {
    super.paintComponent(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);
  }
  // During testing, send action 
  // information to the console:
  static class BBL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("BangBean action");
    }
  }
  // Testing the BangBean:
  public static void main(String[] args) {
    BangBean bb = new BangBean();
    try {
      bb.addActionListener(new BBL());
    } catch(TooManyListenersException e) {}
    Console.run(bb, 300, 300);
  }
} ///:~

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 reinvoke 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 PropertyVetoException 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[66]

After working your way through this chapter and seeing the huge changes that have occurred within Swing (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 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 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:

//: c13:ButtonDemo.java
// Looks like Java 1.1 but with J's added.
// <applet code=ButtonDemo width=250 height=75>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import com.bruceeckel.swing.*;

public class ButtonDemo extends JApplet {
  JButton 
    b1 = new JButton("JButton 1"),
    b2 = new JButton("JButton 2");
  JTextField t = new JTextField(20);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    ActionListener al = new ActionListener() {
      public void actionPerformed(ActionEvent e){
        String name = 
          ((JButton)e.getSource()).getText();
        t.setText(name + " Pressed");
      }
    };
    b1.addActionListener(al);
    cp.add(b1);
    b2.addActionListener(al);
    cp.add(b2);
    cp.add(t);
  }
  public static void main(String[] args) {
    Console.run(new ButtonDemo(), 250, 75);
  }
} ///:~

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.

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:

//: c13:Borders.java
// Different Swing borders.
// <applet code=Borders
//  width=500 height=300></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;

public class Borders extends JApplet {
  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 void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.setLayout(new GridLayout(2,4));
    cp.add(showBorder(new TitledBorder("Title")));
    cp.add(showBorder(new EtchedBorder()));
    cp.add(showBorder(new LineBorder(Color.blue)));
    cp.add(showBorder(
      new MatteBorder(5,5,30,30,Color.green)));
    cp.add(showBorder(
      new BevelBorder(BevelBorder.RAISED)));
    cp.add(showBorder(
      new SoftBevelBorder(BevelBorder.LOWERED)));
    cp.add(showBorder(new CompoundBorder(
      new EtchedBorder(),
      new LineBorder(Color.red))));
  }
  public static void main(String[] args) {
    Console.run(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:

//: c13:Buttons.java
// Various Swing buttons.
// <applet code=Buttons
//  width=350 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;

public class Buttons extends JApplet {
  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 void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(jb);
    cp.add(new JToggleButton("JToggleButton"));
    cp.add(new JCheckBox("JCheckBox"));
    cp.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);
    cp.add(jp);
  }
  public static void main(String[] args) {
    Console.run(new Buttons(), 350, 100);
  }
} ///:~

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:

//: c13:ButtonGroups.java
// Uses reflection to create groups 
// of different types of AbstractButton.
// <applet code=ButtonGroups
//  width=500 height=300></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.border.*;
import java.lang.reflect.*;
import com.bruceeckel.swing.*;

public class ButtonGroups extends JApplet {
  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 void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.add(makeBPanel(JButton.class, ids));
    cp.add(makeBPanel(JToggleButton.class, ids));
    cp.add(makeBPanel(JCheckBox.class, ids));
    cp.add(makeBPanel(JRadioButton.class, ids));
  }
  public static void main(String[] args) {
    Console.run(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 this 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.

//: c13:Faces.java
// Icon behavior in Jbuttons.
// <applet code=Faces
//  width=400 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Faces extends JApplet {
  // The following path information is necessary
  // to run via an applet directly from the disk:
  static String path = 
    "C:/aaa-TIJ2-distribution/code/c13/";
  static Icon[] faces = {
    new ImageIcon(path + "face0.gif"),
    new ImageIcon(path + "face1.gif"),
    new ImageIcon(path + "face2.gif"),
    new ImageIcon(path + "face3.gif"),
    new ImageIcon(path + "face4.gif"),
  };
  JButton 
    jb = new JButton("JButton", faces[3]),
    jb2 = new JButton("Disable");
  boolean mad = false;
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    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!");
    cp.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");
        }
      }
    });
    cp.add(jb2);
  }
  public static void main(String[] args) {
    Console.run(new Faces(), 400, 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.

//: c13:Menus.java
// A menu-building system; also demonstrates
// icons in labels and menu items.
// <applet code=Menus
//  width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Menus extends JApplet {
  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;
  }
  public void init() {
    Container cp = getContentPane();
    cp.add(createMenuBar(menuBar), 
      BorderLayout.NORTH);
    JPanel p = new JPanel();
    p.setLayout(new BorderLayout());
    p.add(t, BorderLayout.NORTH);
    p.add(l, BorderLayout.CENTER);
    cp.add(p, BorderLayout.CENTER);
  }
  public static void main(String[] args) {
    Console.run(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 references 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 references 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:

//: c13:Popup.java
// Creating popup menus with Swing.
// <applet code=Popup
//  width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class Popup extends JApplet {
  JPopupMenu popup = new JPopupMenu();
  JTextField t = new JTextField(10);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    cp.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) {
    Console.run(new Popup(), 300, 200);
  }
} ///:~

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:

//: c13:ListCombo.java
// List boxes & Combo boxes.
// <applet code=ListCombo
//  width=300 height=125></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;

public class ListCombo extends JApplet {
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(2,1));
    JList list = new JList(ButtonGroups.ids);
    cp.add(new JScrollPane(list));
    JComboBox combo = new JComboBox();
    for(int i = 0; i < 100; i++)
      combo.addItem(Integer.toString(i));
    cp.add(combo);
  }
  public static void main(String[] args) {
    Console.run(new ListCombo(), 300, 125);
  }
} ///:~

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:

//: c13:Progress.java
// Using progress bars and sliders.
// <applet code=Progress
//  width=300 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.border.*;
import com.bruceeckel.swing.*;

public class Progress extends JApplet {
  JProgressBar pb = new JProgressBar();
  JSlider sb = 
    new JSlider(JSlider.HORIZONTAL, 0, 100, 60);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(2,1));
    cp.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
    cp.add(sb);
  }
  public static void main(String[] args) {
    Console.run(new Progress(), 300, 200);
  }
} ///:~

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):

//: c13:Trees.java
// Simple Swing tree example. Trees can 
// be made vastly more complex than this.
// <applet code=Trees
//  width=250 height=250></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.tree.*;
import com.bruceeckel.swing.*;

// 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 JApplet {
  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 void init() {
    Container cp = getContentPane();
    root = new DefaultMutableTreeNode("root");
    tree = new JTree(root);
    // Add it and make it take care of scrolling:
    cp.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);
    cp.add(p, BorderLayout.SOUTH);
  }
  public static void main(String[] args) {
    Console.run(new Trees(), 250, 250);
  }
} ///:~

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:

//: c13:Table.java
// Simple demonstration of JTable.
// <applet code=Table
//  width=350 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.table.*;
import javax.swing.event.*;
import com.bruceeckel.swing.*;

// 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();
      }
    }
  }
  public 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 JApplet {
  public void init() {
    Container cp = getContentPane();
    JTable table = new JTable(new DataModel());
    JScrollPane scrollpane = 
      JTable.createScrollPaneForTable(table);
    cp.add(scrollpane, BorderLayout.CENTER);
  }
  public static void main(String[] args) {
    Console.run(new Table(), 350, 100);
  }
} ///:~

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.

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:

Using URLs from within an applet

It’s possible for an applet to cause the display of any URL through the Web browser the applet is running within. You can do this with the following line:

getAppletContext().showDocument(u);

in which u is the URL object. Here’s a simple example that redirects you to another Web page. The page happens to be the output of a CGI program, but you can as easily go to an ordinary HTML page, so you could build on this applet to produce a password-protected gateway to a particular portion of your Web site:

//: c13:ShowHTML.java
// <applet code=ShowHTML width=100 height=50>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class ShowHTML extends JApplet {
  static final String CGIProgram = "MyCGIProgram";
  JButton send = new JButton("Go");
  JLabel l = new JLabel();
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    send.addActionListener(new Al());
    cp.add(send);
    cp.add(l);
  }
  class Al implements ActionListener {
    public void actionPerformed(ActionEvent ae) {
      try {
        // This could be an HTML page instead of
        // a CGI program. Notice that this CGI 
        // program doesn't use arguments, but 
        // you can add them in the usual way.
        URL u = new URL(
          getDocumentBase(), 
          "cgi-bin/" + CGIProgram);
        // Display the output of the URL using
        // the Web browser, as an ordinary page:
        getAppletContext().showDocument(u);
      } catch(Exception e) {
        l.setText(e.toString());
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new ShowHTML(), 100, 50);
  }
} ///:~

The beauty of the URL class is how much it shields you from. You can connect to Web servers without knowing much at all about what’s going on under the covers.

Reading a file from the server

A variation on the above program reads a file located on the server. The file is specified by the client:

//: c13:Fetcher.java
// <applet code=Fetcher width=500 height=300>
// </applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class Fetcher extends JApplet {
  JButton fetchIt= new JButton("Fetch the Data");
  JTextField f = 
    new JTextField("Fetcher.java", 20);
  JTextArea t = new JTextArea(10,40);
  public void init() {
    Container cp = getContentPane();
    cp.setLayout(new FlowLayout());
    fetchIt.addActionListener(new FetchL());
    cp.add(new JScrollPane(t));
    cp.add(f); cp.add(fetchIt);
  }
  public class FetchL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      try {
        URL url = 
          new URL(getDocumentBase(),f.getText());
        t.setText(url + "\n");
        InputStream is = url.openStream();
        BufferedReader in = new BufferedReader(
          new InputStreamReader(is));
        String line;
        while ((line = in.readLine()) != null)
          t.append(line + "\n");
      } catch(Exception ex) {
        t.append(ex.toString());
      }
    }
  }
  public static void main(String[] args) {
    Console.run(new Fetcher(), 500, 300);
  }
} ///:~

A method lookup tool

To finish the chapter, we’ll develop a useful application that allows you to quickly look up methods for classes, as a programming aid.

Chapter 12 introduced the Java 1.1 concept of reflection and used that feature to look up methods for a particular class—either the entire list of methods or a subset of those whose names match a keyword you provide. The magic of this is that it can automatically show you all the methods for a class without forcing you to walk up the inheritance hierarchy examining the base classes at each level. Thus, it provides a valuable timesaving tool for programming: because the names of most Java method names are made nicely verbose and descriptive, you can search for the method names that contain a particular word of interest. When you find what you think you’re looking for, check the online documentation.

However, by Chapter 12 you hadn’t seen Swing, so that tool was developed as a command-line application. Here is the more useful GUI version, which dynamically updates the output as you type and also allows you to cut and paste from the output:

//: c13:DisplayMethods.java
// Display the methods of any class inside
// a window. Dynamically narrows your search.
// <applet code = DisplayMethods 
// width=650 height=700></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.io.*;
import com.bruceeckel.swing.*;

public class DisplayMethods extends JApplet {
  Class cl;
  Method[] m;
  Constructor[] ctor;
  String[] n = new String[0];
  JTextField 
    name = new JTextField(40),
    searchFor = new JTextField(30);
  JCheckBox strip = 
    new JCheckBox("Strip Qualifiers", true);
  JTextArea results = new JTextArea(40, 65);
  class NameL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      String nm = name.getText().trim();
      if(nm.length() == 0) {
        results.setText("No match");
        n = new String[0];
        return;
      }
      try {
        cl = Class.forName(nm);
      } catch (ClassNotFoundException ex) {
        results.setText("No match");
        return;
      }
      m = cl.getMethods();
      ctor = cl.getConstructors();
      // Convert to an array of Strings:
      n = new String[m.length + ctor.length];
      for(int i = 0; i < m.length; i++)
        n[i] = m[i].toString();
      for(int i = 0; i < ctor.length; i++)
        n[i + m.length] = ctor[i].toString();
      reDisplay();
    }
  }
  class StripL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      reDisplay();
    }
  }
  class SearchForL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      reDisplay();
    }
  }
  void reDisplay() {
    // Create the result set:
    String[] rs = new String[n.length];
    String find = searchFor.getText();
    int j = 0;
    // Select from the list if find exists:
    for (int i = 0; i < n.length; i++) {
      if(find == null)
        rs[j++] = n[i];
      else if(n[i].indexOf(find) != -1)
          rs[j++] = n[i];
    }
    results.setText("");
    if(strip.isSelected())
      for (int i = 0; i < j; i++)
        results.append(
          StripQualifiers.strip(rs[i]) + "\n");
    else // Leave qualifiers on
      for (int i = 0; i < j; i++)
        results.append(rs[i] + "\n");
  }
  public void init() {
    name.addActionListener(new NameL());
    searchFor.addActionListener(new SearchForL());
    strip.addItemListener(new StripL());
    JPanel 
      top = new JPanel(),
      lower = new JPanel(),
      p = new JPanel(new BorderLayout());
    top.add(new JLabel("Qualified class name:"));
    top.add(name);
    lower.add(
      new JLabel("String to search for:"));
    lower.add(searchFor);
    lower.add(strip);
    p.add(top, BorderLayout.NORTH);
    p.add(lower, BorderLayout.SOUTH);
    Container cp = getContentPane();
    cp.add(p, BorderLayout.NORTH);
    cp.add(results, BorderLayout.CENTER);
  }
  public static void main(String[] args) {
    Console.run(new DisplayMethods(), 650, 700);
  }
}

class StripQualifiers {
  private StreamTokenizer st;
  public StripQualifiers(String qualified) {
      st = new StreamTokenizer(
        new StringReader(qualified));
      st.ordinaryChar(' ');
  }
  public String getNext() {
    String s = null;
    try {
      if(st.nextToken() !=
            StreamTokenizer.TT_EOF) {
        switch(st.ttype) {
          case StreamTokenizer.TT_EOL:
            s = null;
            break;
          case StreamTokenizer.TT_NUMBER:
            s = Double.toString(st.nval);
            break;
          case StreamTokenizer.TT_WORD:
            s = new String(st.sval);
            break;
          default: // single character in ttype
            s = String.valueOf((char)st.ttype);
        }
      }
    } catch(IOException e) {
      System.out.println(e);
    }
    return s;
  }
  public static String strip(String qualified) {
    StripQualifiers sq = 
      new StripQualifiers(qualified);
    String s = "", si;
    while((si = sq.getNext()) != null) {
      int lastDot = si.lastIndexOf('.');
      if(lastDot != -1)
        si = si.substring(lastDot + 1);
      s += si;
    }
    return s;
  }
} ///:~

Some things you’ve seen before. As with many of the GUI programs in this book, this is created to perform both as an application and as an applet. Also, the StripQualifiers class is exactly the same as it was in Chapter 12.

The GUI contains a TextField name in which you can enter the fully-qualified class name you want to look up, and another one, searchFor, in which you can enter the optional text to search for within the list of methods. The Checkbox allows you to say whether you want to use the fully-qualified names in the output or if you want the qualification stripped off. Finally, the results are displayed in a TextArea.

You’ll notice that there are no buttons or other components by which to indicate that you want the search to start. That’s because both of the TextFields and the Checkbox are monitored by their listener objects. Whenever you make a change, the list is immediately updated. If you change the text within the name field, the new text is captured in class NameL. If the text isn’t empty, it is used inside Class.forName( ) to try to look up the class. As you’re typing, of course, the name will be incomplete and Class.forName( ) will fail, which means that it throws an exception. This is trapped and the TextArea is set to “No match”. But as soon as you type in a correct name (capitalization counts), Class.forName( ) is successful and getMethods( ) and getConstructors( ) will return arrays of Method and Constructor objects, respectively. Each of the objects in these arrays is turned into a String via toString( ) (this produces the complete method or constructor signature) and both lists are combined into n, a single String array. The array n is a member of class DisplayMethods and is used in updating the display whenever reDisplay( ) is called.

If you change the Checkbox or searchFor components, their listeners simply call reDisplay( ). reDisplay( ) creates a temporary array of String called rs (for “result set”). The result set is either copied directly from n if there is no find word, or conditionally copied from the Strings in n that contain the find word. Finally, the strip Checkbox is interrogated to see if the user wants the names to be stripped (the default is “yes”). If so, StripQualifiers.strip( ) does the job; if not, the list is simply displayed.

In init( ), you might think that there’s a lot of busy work involved in setting up the layout. In fact, it is possible to lay out the components with less work, but the advantage of using BorderLayouts this way is that it allows the user to resize the window and make—in particular—the TextArea larger, which means you can resize to allow you to see longer names without scrolling.

You might find that you’ll keep this tool running while you’re programming, since it provides one of the best “first lines of attack” when you’re trying to figure out what method to call.

Summary

Of all the libraries in Java, the GUI library has seen the most dramatic changes from Java 1.0 to Java 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 referenceEvent( ) 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. 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.
  9. Create your own Java Bean called Valve that contains two properties: a boolean called “on” and an int 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.
  10. (Somewhat challenging) Change Menus.java so that it handles cascading menus.

[59] A varation on this is called “the principle of least astonishment,” which essentially says: “don’t surprise the user.”

[60] This is an example of the design pattern called the template method.

[61] 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.

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

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

[64] 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.

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

[66] 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:03/13/2000