| Thinking in Java source ref: work2.html |
A fundamental design guideline is make simple things easy, and
difficult things possible. [74]
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) produced a GUI that looked equally mediocre on all systems. In addition,
it was restrictive; you could use only four fonts and you couldnt 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. .
The situation improved with the Java 1.1 AWT event model, which takes a much clearer,
object-oriented approach, along with the addition of JavaBeans, a component programming
model that is oriented toward the easy creation of visual programming environments. Java 2
(JDK 1.2) finished the transformation away from the old Java 1.0 AWT by essentially
replacing everything with 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 JavaBeans 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 isnt 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.[75] If for some reason you
need to use the original old AWT (because youre supporting old code or
you have browser limitations), you can find that introduction in the first edition of this
book, downloadable at www.BruceEckel.com (also included on the CD ROM bound with
this book) Note that some AWT components remain in Java, and in some situations you must
use them. .
Early in this chapter, youll see how things are different when you want to create
an applet versus 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 executable as both applets
and applications. .
Please be aware that this is not a comprehensive glossary of either all the Swing
components or all the methods for the described classes. What you see here is intended to
be simple. 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. If you need to do more,
then Swing can probably give you what you want if youre willing to do the research. .
I assume here that you have downloaded and installed the JDK library documents in HTML
format from java.sun.com and will browse the javax.swing classes in that
documentation to see the full details and methods of the Swing library. Because of the
simplicity of the Swing design, this will often be enough information to solve your
problem. There are numerous (rather thick) books dedicated solely to Swing, and
youll want to go to those if you need more depth, or if you want to modify the
default Swing behavior. .
As you learn about Swing, youll discover: .
Swing contains all the components that you expect to see in a modern UI: everything
from buttons that contain pictures to trees and tables. Its a big library, but
its designed to have appropriate complexity for the task at hand; if something is
simple, you dont have to write much code, but as you try to do more complex things,
your code becomes proportionally more complex. This means an easy entry point, but
youve got the power if you need it. .
Much of what youll 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 standard naming conventions, much of the time
that 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. .
For speed, all the components are lightweight, and Swing is written
entirely in Java for portability. .
Keyboard
navigation is automatic; you can run a Swing application without using the mouse, and this
doesnt require any extra programming. Scrolling support is effortless; you simply
wrap your component in a JScrollPane as you add it to your form. Features such as
tool tips typically require a single line of code to use. .
Swing also supports a rather radical feature 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.
Its even possible (albeit difficult) to invent your own look and feel. .
Java has the ability 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 support client-side
programming, a major issue for the Web. .
Programming
within an applet is so restrictive that its often referred to as being inside
the sandbox, since you always have someonethat is, the Java run-time security
systemwatching over you. .
However, you can also step outside the sandbox and write regular applications rather
than applets, in which case you can access the other features of your OS. Weve been
writing regular applications all along in this book, but theyve been console
applications without any graphical components. Swing can be used to build GUI
interfaces for regular applications. .
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 youll
notice are probably: .
If you can live within the restrictions, applets have definite advantages, especially
when building client/server or other networked applications: .
Because applets are automatically integrated with HTML, you have a built-in
platform-independent documentation system to support the applet. Its an interesting
twist, since were used to having the documentation part of the program rather than
vice versa. .
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 create other classes. A certain category 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 frameworks 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.[76] .
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( ) |
Automatically
called to perform first-time initialization of the applet, including component layout.
Youll 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 are ready to create a simple applet:
//: c14: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( ). Thats all wired
into the application framework; you put any startup code in init( ). .
In this program, the only activity is putting a text label on the applet, via the JLabel class (the old AWT appropriated the name Label
as well as other names of components, so you will often see a leading J
used with Swing components). The constructor for this class takes a String and uses
it to create the label. In the preceding program this label is placed on the form. .
The init( ) method is responsible for putting all
the components on the form using the add( ) method. You might think that you
ought to be able to simply call add( ) by itself, and in fact thats the
way it used to be in the old AWT. However, Swing requires that you add all components to
the content pane of a form, so you must call getContentPane( ) as
part of the add( ) process. .
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[77] to tell the page how to load and run the applet. .
This process used to be very simple, when Java itself 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 and language wars, and we (programmers and end users alike)
lost. After awhile, Sun 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 browsers extension mechanism. By using the extension mechanism
(which a browser vendor cannot disablein an attempt to gain competitive
advantagewithout breaking all the third-party extensions), Sun 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, it is the plug-in. In your HTML code, you must provide tags to support both, but
you can automatically generate the necessary tags with the HTMLconverter tool that
comes with the JDK download. Heres what the simplest resulting HTML page looks like
for Applet1 after running HTMLconverter on the preceding applet tag:
<OBJECT
classid = "clsid:CAFEEFAC-0014-0001-0000-ABCDEFFEDCBA"
codebase = "http://java.sun.com/products/plugin/autodl/jinstall-1_4_1-windows-i586.cab#Version=1,4,1,0"
WIDTH = 100 HEIGHT = 50 >
<PARAM NAME = CODE VALUE = Applet1 >
<PARAM NAME = "type" VALUE = "application/x-java-applet;jpi-version=1.4.1">
<PARAM NAME = "scriptable" VALUE = "false">
<COMMENT>
<EMBED
type = "application/x-java-applet;jpi-version=1.4.1"
CODE = Applet1
WIDTH = 100
HEIGHT = 50
scriptable = false
pluginspage = "http://java.sun.com/products/plugin/index.html#download">
<NOEMBED>
</NOEMBED>
</EMBED>
</COMMENT>
</OBJECT>
Some of these lines were too long and had to be wrapped to fit on the page. The code in
this books source code (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. .
The source code package for this book (freely downloadable at www.BruceEckel.com)
provides an HTML page for each of the applets in this book, and thus many examples of
the applet tag, all driven from the index.html file corresponding to this
chapters source code. You can find a full and current description of the details of
placing applets in Web pages at java.sun.com. .
Suns JDK 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 dont
need to create tiny HTML files to run tests. For example, you can add the commented HTML
tags to Applet1.java:
//: c14: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
In this book, this form will be used for easy testing of applets. Shortly, youll
see another coding approach that will allow you to execute applets from the command line
without the Appletviewer. .
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 isnt in the CLASSPATH, then it will give an error
message on the status line of the browser to the effect that it couldnt 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. You must also
have a way to move the HTML files and the .class
files from your site to the correct 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? .
Heres 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 during your testing process because the browser finds it on your
machine. When someone else connects, however, his or her browser cant find it. So
when youre testing, make sure you erase the relevant .class files (or .jar
file) on your local machine to verify that they exist in the proper location on the
server. .
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. Its important to specify the full class name including the package in the
CODE parameter of your applet tag. In many published applet examples, the applet is not
put inside a package, but its generally best to use packages in production code. .
There are times when youd like to
make a windowed program do something else other than sit on a Web page. Perhaps youd
also like it to do some of the things a regular application can do, but still
have the vaunted instant portability provided by Java. In previous chapters in this book
weve made command-line applications, but in some operating environments (the
Macintosh, for example) there isnt a command line. So for any number of reasons,
youd like to build a windowed, non-applet program using Java. This is certainly a
reasonable desire. .
The 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[78] only if you can use the
latest version of Java and associated tools so you can deliver applications that
wont confound your users. If for some reason youre forced to use an older
version of Java, think hard before committing to building a significant windowed
application. .
Often youll want to be able to
create a class that can be invoked as either a window or an applet. This is especially
convenient when youre testing the applets, since its 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 Jframe.[79] As a simple example, lets look at Applet1b.java
modified to work as both an application and an applet:
//: c14:Applet1c.java
// An application and an applet.
// <applet code=Applet1c width=100 height=50></applet>
import javax.swing.*;
import java.awt.*;
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 Applet1c();
JFrame frame = new JFrame("Applet1c");
// To close the application:
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
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. The applet is created and added to a JFrame so that it can be
displayed. .
You can see that in main( ), the applet is explicitly initialized and
started because in this case the browser isnt available to do it for you. Of course,
this doesnt provide the full behavior of the browser, which also calls stop( )
and destroy( ), but for most situations its acceptable. If its a
problem, you can force the calls yourself.[80]
.
Notice the last line:
frame.setVisible(true);
Without this, you wont see anything on the screen. .
Although the code that turns programs
into both applets and applications produces valuable results, if used everywhere it
becomes distracting and wastes paper. Instead, the following display framework will be
used for the Swing examples in the rest of this book:
//: 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
run(JFrame frame, int width, int height) {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(width, height);
frame.setVisible(true);
}
public static void
run(JApplet applet, int width, int height) {
JFrame frame = new JFrame(title(applet));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
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));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.setSize(width, height);
frame.setVisible(true);
}
} ///:~
This is a tool you may want to use yourself, so its placed in the library com.bruceeckel.swing. The Console class consists
entirely of static methods. The first is used to extract the class name (using
RTTI) from any object and to remove the word class, which is typically
prepended by getClass( ). This uses the String methods indexOf( )
to determine whether the word class is there, and substring( ) to
produce the new string without class or the trailing space. This name is used
to label the window that is displayed by the run( ) methods. .
setDefaultCloseOperation( ) causes a JFrame to exit a program when
that JFrame is closed. The default behavior is to do nothing, so if you dont
call setDefaultCloseOperation( ) or write the equivalent code for your JFrame, the application wont close. .
The run( ) method is overloaded to work with JApplets, JPanels,
and JFrames. Note that only if its a JApplet are init( )
and start( ) called. .
Now any applet can be run from the console by creating a main( ) containing
a line like this:
Console.run(new MyClass(), 500, 300);
in which the last two arguments are the display width and height. Heres Applet1c.java
modified to use Console:
//: c14: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 Applet1d(), 100, 50);
}
} ///:~
This allows the elimination of repeated code while providing the greatest flexibility
in running the examples. .
Making a button is quite simple: you
just call the JButton constructor with the label you want on the button.
Youll see later that you can do fancier things, like putting graphic images on
buttons. .
Usually, youll want to create a field for the button inside your class so that
you can refer to it later. .
The JButton is a componentits own little windowthat will
automatically get repainted as part of an update. This means that you dont
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 do it inside init( ):
//: c14: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 {
private 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);
}
} ///:~
Something new has been added here: Before any elements are placed on the content pane,
it is given a new layout manager, of type FlowLayout. The layout
manager is the way that the pane implicitly decides where to place the control on the
form. The normal behavior of an applet is to use the BorderLayout, but that
wont work here because (as you will learn later in this chapter when controlling the
layout of a form is examined in more detail) it defaults to covering each control entirely
with every new one that is added. However, FlowLayout causes the controls to flow
evenly onto the form, left to right and top to bottom. .
Youll notice that if you compile and run the preceding
applet, 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. .
The way that this is accomplished in
Swing is by cleanly separating the interface (the graphical components) and the
implementation (the code that you want to run when an event happens to a component). Each
Swing component can report all the events that might happen to it, and it can report each
kind of event individually. So if youre not interested in, for example, whether the
mouse is being moved over your button, you dont register your interest in that
event. Its a very straightforward and elegant way to handle event-driven
programming, and once you understand the basic concepts, you can easily use Swing
components that you havent seen beforein fact, this model extends to anything
that can be classified as a JavaBean (discussed later in the chapter). .
At first, we will just focus on the main event of interest for the components being
used. In the case of a JButton, this event of interest is that the
button is pressed. To register your interest in when a button is pressed, you call
the JButtons addActionListener( ) method. This method expects an
argument that is an object that implements the ActionListener interface, which
contains a single method called actionPerformed( ). So all you have to do to
attach code to a JButton is to implement the ActionListener interface in a
class, and register an object of that class with the JButton via addActionListener( ).
The method will be called when the button is pressed (this is normally referred to as a callback). .
But what should the result of pressing that button be? Wed like to see something
change on the screen, so a new Swing component will be introduced: the JTextField.
This is a place where text can be typed, or in this case, inserted by the program.
Although there are a number of ways to create a JTextField, the simplest is just to
tell the constructor how wide you want that field to be. Once the JTextField is
placed on the form, you can modify its contents by using the setText( ) method
(there are many other methods in JTextField, but you must look these up in the HTML
documentation for the JDK from java.sun.com). Here is what it looks like:
//: c14:Button2.java
// Responding to button presses.
// <applet code=Button2 width=200 height=75></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class Button2 extends JApplet {
private JButton
b1 = new JButton("Button 1"),
b2 = new JButton("Button 2");
private JTextField txt = new JTextField(10);
class ButtonListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String name = ((JButton)e.getSource()).getText();
txt.setText(name);
}
}
private ButtonListener bl = new ButtonListener();
public void init() {
b1.addActionListener(bl);
b2.addActionListener(bl);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(b1);
cp.add(b2);
cp.add(txt);
}
public static void main(String[] args) {
Console.run(new Button2(), 200, 75);
}
} ///:~
Creating a JTextField and placing it on the canvas takes the same steps as for Jbuttons
or for any Swing component. The difference in the preceding program is in the creation of
the aforementioned ActionListener class ButtonListener. The argument to actionPerformed( )
is of type ActionEvent, which contains all the information about the event and
where it came from. In this case, I wanted to describe the button that was pressed; getSource( )
produces the object where the event originated, and I assumed (using a cast) that the
object is a JButton. getText( ) returns the text thats on the
button, and this is placed in the JTextField to prove that the code was actually
called when the button was pressed. .
In init( ), addActionListener( ) is used to register the ButtonListener
object with both the buttons. .
It is often more convenient to code the ActionListener as an anonymous inner
class, especially since you tend to use only a single instance of each listener class. Button2.java
can be modified to use an anonymous inner class as follows: .
//: c14:Button2b.java
// Using anonymous inner classes.
// <applet code=Button2b width=200 height=75></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class Button2b extends JApplet {
private JButton
b1 = new JButton("Button 1"),
b2 = new JButton("Button 2");
private JTextField txt = new JTextField(10);
private ActionListener bl = new ActionListener() {
public void actionPerformed(ActionEvent e) {
String name = ((JButton)e.getSource()).getText();
txt.setText(name);
}
};
public void init() {
b1.addActionListener(bl);
b2.addActionListener(bl);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(b1);
cp.add(b2);
cp.add(txt);
}
public static void main(String[] args) {
Console.run(new Button2b(), 200, 75);
}
} ///:~
The approach of using an anonymous inner class will be preferred (when possible) for
the examples in this book. .
A JTextArea is like a JTextField except that it
can have multiple lines and has more functionality. A particularly useful method is append( );
with this you can easily pour output into the JTextArea, thus making a Swing
program an improvement (since you can scroll backward) over what has been accomplished
thus far using command-line programs that print to standard output. As an example, the
following program fills a JTextArea with the output from the geography
generator in Chapter 11:
//: c14:TextArea.java
// Using the JTextArea control.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.util.*;
import com.bruceeckel.swing.*;
import com.bruceeckel.util.*;
public class TextArea extends JFrame {
private JButton
b = new JButton("Add Data"),
c = new JButton("Clear Data");
private JTextArea t = new JTextArea(20, 40);
private Map m = new HashMap();
public TextArea() {
// Use up all the data:
Collections2.fill(m, Collections2.geography,
CountryCapitals.pairs.length);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Iterator it = m.entrySet().iterator();
while(it.hasNext()) {
Map.Entry me = (Map.Entry)(it.next());
t.append(me.getKey() + ": "+ me.getValue()+"\n");
}
}
});
c.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText("");
}
});
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(new JScrollPane(t));
cp.add(b);
cp.add(c);
}
public static void main(String[] args) {
Console.run(new TextArea(), 475, 425);
}
} ///:~
This is a JFrame rather than a JApplet because it reads from the local
disk, and therefore cannot be run as an applet in an HTML page. .
In init( ), the Map is filled with all the countries and their
capitals. Note that for both buttons, the ActionListener is
created and added without defining an intermediate variable, since you never need to refer
to that listener again during the program. The Add Data button formats and
appends all the data, and the Clear Data button uses setText( ) to
remove all the text from the JTextArea. .
As the JTextArea is added to the applet, it is wrapped in a JScrollPane to control scrolling when too much text is placed
on the screen. Thats all you must do in order to produce full scrolling
capabilities. Having tried to figure out how to do the equivalent in some other GUI
programming environments, I am very impressed with the simplicity and good design of
components like JScrollPane. .
The way that you place components on a form in Java is probably
different from any other GUI system youve used. First, its all code; there are
no resources that control placement of components. Second, the way components
are placed on a form is controlled not by absolute positioning but 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 the window dimension is changed, the size, shape,
and placement of the components can change in response. .
JApplet, JFrame JWindow,
and JDialog can all produce a Container with getContentPane( ) that
can contain and display Components. In Container, theres a method
called setLayout( ) that allows you to choose a
different layout manager. Other classes, such as JPanel,
contain and display components directly, so you also set the layout manager directly,
without using the content pane. .
In this section well explore the various layout managers by placing buttons in
them (since thats the simplest thing to do). There wont be any capturing of
button events because these examples are just intended to show how the buttons are laid
out. .
Applets use a default layout scheme: the BorderLayout (a
number of the previous examples have changed the layout manager to FlowLayout).
Without any other instruction, this takes whatever you add( ) to it and places
it in the center, stretching the object all the way out to the edges. .
However, theres more to the BorderLayout. This layout manager has the
concept of four border regions and a center area. When you add something to a panel
thats using a BorderLayout, you can use the overloaded add( )
method that takes a constant value as its first argument. This value can be any of the
following:
| Top |
|
| BorderLayout.
SOUTH |
Bottom |
| BorderLayout.
EAST |
Right |
| BorderLayout.
WEST |
Left |
| BorderLayout.CENTER |
Fill the
middle, up to the other components or to the edges |
If you dont specify an area to place the object, it
defaults to CENTER. .
Heres a simple example. The default layout is used, since JApplet defaults
to BorderLayout:
//: c14: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.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 in both dimensions to
occupy the middle. .
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. .
Heres an example that sets the layout manager to FlowLayout and then
places buttons on the form. Youll notice that with FlowLayout, the components
take on their natural size. A JButton, for example, will be the size of
its string. .
//: c14: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, because a JLabel will
be the size of its string, attempting to right-justify its text yields an unchanged
display when using FlowLayout. .
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. .
//: c14: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
because no balancing goes on with a GridLayout. .
The GridBagLayout provides you with tremendous control
in deciding exactly how the regions of your window will lay themselves out and reformat
themselves when the window is resized. However, its also the most complicated layout
manager, and is quite difficult to understand. It is intended primarily for automatic code
generation by a GUI builder (GUI builders might use GridBagLayout instead of
absolute placement). If your design is so complicated that you feel you need to use GridBagLayout,
then you should be using a GUI builder tool to generate that design. If you feel you must
know the intricate details, Ill refer you to Core Java 2, Volume 1, by
Horstmann & Cornell (Prentice Hall, 2001), or a dedicated Swing book as a starting
point. .
It is also possible to set the absolute position of the graphical components in this
way:
Some GUI builders use this approach extensively, but this is usually not the best way
to generate code. .
Because people had so much trouble understanding and working
with GridBagLayout, Swing also includes BoxLayout, which gives you many of
the benefits of GridBagLayout without the complexity, so you can often use it when
you need to do hand-coded layouts (again, if your design becomes too complex, use a GUI
builder that generates layouts for you). BoxLayout allows you to control the
placement of components either vertically or horizontally, and to control the space
between the components using something called struts and
glue. First, lets see how to use the BoxLayout
directly, in the same way that the other layout managers have been demonstrated:
//: c14: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("jpv " + i));
JPanel jph = new JPanel();
jph.setLayout(new BoxLayout(jph, BoxLayout.X_AXIS));
for(int i = 0; i < 5; i++)
jph.add(new JButton("jph " + 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
managersyou 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, theres 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:
//: c14: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("bv " + i));
Box bh = Box.createHorizontalBox();
for(int i = 0; i < 5; i++)
bh.add(new JButton("bh " + 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);
}
} ///:~
Once you have a Box, you pass it as a second argument when adding components to
the content pane. .
Struts add space, measured in pixels, between components. To use a strut, you simply
add it between the addition of the components that you want spaced apart:
//: c14: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("bv " + i));
bv.add(Box.createVerticalStrut(i * 10));
}
Box bh = Box.createHorizontalBox();
for(int i = 0; i < 5; i++) {
bh.add(new JButton("bh " + 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);
}
} ///:~
Struts separate components by a fixed amount, but glue is the opposite; it separates
components by as much as possible. Thus its more of a spring than
glue (and the design on which this was based was called springs and
struts, so the choice of the term is a bit mysterious). .
//: c14: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);
}
} ///:~
A strut works in one direction, but a rigid area fixes the spacing between components
in both directions:
//: c14: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. .
Swing is powerful; it can get a lot done with a few lines of code. The examples shown
in this book are reasonably simple, and for learning purposes it makes sense to write them
by hand. You can actually accomplish quite a bit by combining simple layouts. At some
point, however, it stops making sense to hand-code GUI forms; it becomes too complicated
and is not a good use of your programming time. The Java and Swing designers oriented the
language and libraries to support GUI building tools, which have been created for the
express purpose of making your programming experience easier. As long as you understand
whats going on with layouts and how to deal with the events (described next),
its not particularly important that you actually know the details of how to lay out
components by hand; let the appropriate tool do that for you (Java is, after all, designed
to increase programmer productivity). .
In the Swing 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. Since you typically use Swing components as they are, but need to write
code that is called when the components receive an event, this is an excellent example of
the separation of interface and implementation. .
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 thats firing the
event. This registration is performed by calling an 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, youll discover your mistake at compile time. Youll see later in the
chapter that JavaBeans also use the names of the addListener methods to
determine what events a Bean can handle. .
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 also because (as you
shall see later) an inner-class object keeps a reference to its parent object, which
provides a nice way to call across class and subsystem boundaries. .
All the examples so far in this chapter
have been using the Swing event model, but the remainder of this section will fill out the
details of that model. .
All Swing components include addXXXListener( ) and removeXXXListener( )
methods so that the appropriate types of listeners can be added and removed from each
component. Youll notice that the XXX in each case also represents
the argument for the method, for example, addMyListener(MyListener m). The
following table includes the basic associated events, listeners, and methods, along with
the basic components that support those particular events by providing the addXXXListener( ) and removeXXXListener( )
methods. You should keep in mind that the event model is designed to be extensible, so you
may encounter other events and listener types that are not covered in this table. .
| Components
supporting this event |
|
|---|---|
| ActionEvent |
JButton,
JList, JTextField, JMenuItem and its derivatives including
JCheckBoxMenuItem, JMenu, and JpopupMenu |
| AdjustmentEvent |
JScrollbar |
| ComponentEvent |
*Component
and its derivatives, including JButton, JCheckBox, JComboBox, Container, JPanel,
JApplet, JScrollPane, Window, JDialog, JFileDialog, JFrame, JLabel, JList, JScrollbar,
JTextArea, and JTextField |
| ContainerEvent |
Container and
its derivatives, including JPanel, JApplet, JScrollPane, Window, JDialog, JFileDialog, and
JFrame |
| FocusEvent |
Component and
derivatives* |
| KeyEvent |
Component and
derivatives* |
| MouseEvent
(for both clicks and motion) |
Component and
derivatives* |
| MouseEvent[81] (for both clicks and motion) |
Component and
derivatives* |
| WindowEvent |
Window and
its derivatives, including JDialog, JFileDialog, and JFrame |
| ItemEvent |
JCheckBox,
JCheckBoxMenuItem, JComboBox, JList, and anything that implements the ItemSelectable
interface |
| TextEvent |
Anything
derived from JTextComponent, including JTextArea and JTextField |
You can see that each type of component supports only certain
types of events. It turns out to be rather difficult to look up all the events supported
by each component. A simpler approach is to modify the ShowMethods.java program
from Chapter 10 so that it displays all the event listeners supported by any Swing
component that you enter. .
Chapter 10 introduced reflection
and used that feature to look up methods for a particular classeither the entire
list of methods or a subset of those whose names match a keyword that you provide. The
magic of reflection 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 methods 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
youre looking for, check the JDK documentation. .
However, by Chapter 10 you hadnt seen Swing, so the tool in that chapter was
developed as a command-line application. Here is the more useful GUI version, specialized
to look for the addListener methods in Swing components:
//: c14:ShowAddListeners.java
// Display the "addXXXListener" methods of any Swing class.
// <applet code=ShowAddListeners
// width=500 height=400></applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.util.regex.*;
import com.bruceeckel.swing.*;
public class ShowAddListeners extends JApplet {
private JTextField name = new JTextField(25);
private JTextArea results = new JTextArea(40, 65);
private static Pattern addListener =
Pattern.compile("(add\\w+?Listener\\(.*?\\))");
private static Pattern qualifier =
Pattern.compile("\\w+\\.");
class NameL implements ActionListener {
public void actionPerformed(ActionEvent e) {
String nm = name.getText().trim();
if(nm.length() == 0) {
results.setText("No match");
return;
}
Class klass;
try {
klass = Class.forName("javax.swing." + nm);
} catch(ClassNotFoundException ex) {
results.setText("No match");
return;
}
Method[] methods = klass.getMethods();
results.setText("");
for(int i = 0; i < methods.length; i++) {
Matcher matcher =
addListener.matcher(methods[i].toString());
if(matcher.find())
results.append(qualifier.matcher(
matcher.group(1)).replaceAll("") + "\n");
}
}
}
public void init() {
NameL nameListener = new NameL();
name.addActionListener(nameListener);
JPanel top = new JPanel();
top.add(new JLabel("Swing class name (press ENTER):"));
top.add(name);
Container cp = getContentPane();
cp.add(BorderLayout.NORTH, top);
cp.add(new JScrollPane(results));
// Initial data and test:
name.setText("JTextArea");
nameListener.actionPerformed(
new ActionEvent("", 0 ,""));
}
public static void main(String[] args) {
Console.run(new ShowAddListeners(), 500,400);
}
} ///:~
You enter the Swing class name that you want to look up in the name JTextField.
The results are extracted using regular expressions, and displayed in a JTextArea. .
Youll notice that there are no buttons or other components to indicate that you
want the search to begin. Thats because the JTextField is monitored by an ActionListener.
Whenever you make a change and press ENTER, the list is immediately updated. If the text
field isnt empty, it is used inside Class.forName( )
to try to look up the class. If the name is incorrect, Class.forName( ) will
fail, which means that it throws an exception. This is trapped, and the JTextArea
is set to No match. But if you type in a correct name (capitalization counts),
Class.forName( ) is successful, and getMethods( ) will return an
array of Method objects. .
Two regular expressions are used here. The first, addListener, looks for
add followed by any word characters, followed by Listener and the
argument list in parentheses. Notice that this whole regular expression is surrounded by
non-escaped parentheses, which means it will be accessible as a regular expression
group when it matches. Inside NameL.ActionPerformed( ), a Matcher
is created by passing each Method object to the Pattern.matcher( )
method. When find( ) is called for this Matcher object, it returns true
only if a match occurs, and in that case you can select the first matching parenthesized
group by calling group(1). This string still contains qualifiers, so to strip them
off the qualifier Pattern object is used just as it was in c09:ShowMethods.java.
.
At the end of init( ), an initial value is placed in name and the
action event is run to provide a test with initial data.
This program is a convenient way to investigate the capabilities of a Swing component.
Once you know which events a particular component supports, you dont need to look
anything up to react to that event. You simply:
Here are some of the listener interfaces:
| Methods in
interface |
|
|---|---|
| ActionListener |
actionPerformed(ActionEvent) |
| AdjustmentListener |
adjustmentValueChanged( |
| ComponentListener |
componentHidden(ComponentEvent) |
| ContainerListener |
componentAdded(ContainerEvent) |
| FocusListener |
focusGained(FocusEvent) |
| KeyListener |
keyPressed(KeyEvent) |
| MouseListener |
mouseClicked(MouseEvent) |
| MouseMotionListener |
mouseDragged(MouseEvent) |
| WindowListener |
windowOpened(WindowEvent) |
| ItemListener |
itemStateChanged(ItemEvent) |
This is not an exhaustive listing, partly because the event model
allows you to create your own event types and associated listeners. Thus, youll
regularly come across libraries that have invented their own events, and the knowledge
gained in this chapter will allow you to figure out how to use these events. .
In the table above, you can see that
some listener interfaces have only one method. These are trivial to implement, because
youll implement them only when you want to write that particular method. However,
the listener interfaces that have multiple methods can be less pleasant to use. For
example, if you want to capture a mouse click (that isnt already captured for you,
for example, by a button), then you need to write a method for mouseClicked( ).
But since MouseListener is an interface, you must implement all of the other
methods even if they dont do anything. This can be annoying. .
To solve the problem, some (but not all) 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 empty methods for each of the interface methods. Then
all you need to do is inherit from the adapter and override only the methods you need to
change. For example, the typical MouseListener youll use looks like this:
class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
// Respond to mouse click...
}
}
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
MouseAdapter like the previous one:
class MyMouseListener extends MouseAdapter {
public void MouseClicked(MouseEvent e) {
// Respond to mouse click...
}
}
This doesnt work, but it will drive you crazy trying to figure out why, since
everything will compile and run fineexcept that your method wont be called for
a mouse click. Can you see the problem? Its in the name of the method: MouseClicked( )
instead of mouseClicked ( ). A simple slip in capitalization results in the
addition of a completely new method. However, this is not the method thats called
when the window is closing, so you dont get the desired results. Despite the
inconvenience, an interface will guarantee that the methods are properly
implemented. .
To prove to yourself that these events are in fact being fired, and as an interesting
experiment, its worth creating an applet that tracks extra behavior in a JButton (in
addition to whether it has been pressed). This example also shows you how to inherit your
own button object because thats what is used as the target of all the events of
interest. To do so, you can just inherit from Jbutton.[82] .
The MyButton class is an inner class of TrackEvent, so MyButton
can reach into the parent window and manipulate its text fields, which is whats
necessary to be able to write the status information into the fields of the parent. Of
course, this is a limited solution, since MyButton can be used only in conjunction
with TrackEvent. This kind of code is sometimes called highly coupled:
//: c14: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.*;
public class TrackEvent extends JApplet {
private HashMap h = new HashMap();
private String[] event = {
"focusGained", "focusLost", "keyPressed",
"keyReleased", "keyTyped", "mouseClicked",
"mouseEntered", "mouseExited", "mousePressed",
"mouseReleased", "mouseDragged", "mouseMoved"
};
private MyButton
b1 = new MyButton(Color.BLUE, "test1"),
b2 = new MyButton(Color.RED, "test2");
class MyButton extends JButton {
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 MyButton(Color color, String label) {
super(label);
setBackground(color);
addFocusListener(fl);
addKeyListener(kl);
addMouseListener(ml);
addMouseMotionListener(mml);
}
}
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);
}
} ///:~
In the MyButton constructor, the buttons color is set with a call to SetBackground( ).
The listeners are all installed with simple method calls. .
The TrackEvent class contains a HashMap to hold
the strings representing the type of event and JTextFields 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 youll agree that its 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
arrayeverything else happens automatically. .
When report( ) is called, it is given the name of the event and the
parameter string from the event. It uses the HashMap h in the outer class to look
up the actual JTextField associated with that event name and then places the
parameter string into that field. .
This example is fun to play with because you can really see whats going on with
the events in your program. .
Now that you understand layout managers and the event model,
youre ready to see how Swing components can be used. This section is a
non-exhaustive tour of the Swing components and features that youll probably use
most of the time. Each example is intended to be reasonably small so that you can easily
lift the code and use it in your own programs. .
Keep in mind:
Swing includes a number of different types of buttons. All buttons, check boxes, radio
buttons, and even menu items are inherited from AbstractButton (which, since menu items are included, would
probably have been better named AbstractSelector or something equally
general). Youll see the use of menu items shortly, but the following example shows
the various types of buttons available: .
//: c14: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 {
private JButton jb = new JButton("JButton");
private 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);
}
} ///:~
This begins with the BasicArrowButton from javax.swing.plaf.basic,
then continues with the various specific types of buttons. When you run the example,
youll 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). .
If you want radio buttons to behave in an exclusive
or fashion, you must add them to a button group. But, as the following
example 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. The second
argument to makeBPanel( ) is an array of String. For each String,
a button of the class represented by the first argument is added to the JPanel:
//: c14: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 {
private static String[] ids = {
"June", "Ward", "Beaver",
"Wally", "Eddie", "Lumpy",
};
static JPanel makeBPanel(Class klass, String[] ids) {
ButtonGroup bg = new ButtonGroup();
JPanel jp = new JPanel();
String title = klass.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 =
klass.getConstructor(new Class[]{String.class});
// Create a new object:
ab = (AbstractButton)
ctor.newInstance(new Object[] { ids[i] });
} catch(Exception ex) {
System.err.println("can't create " + klass);
}
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, youll 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 argumentsin 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, youll see that all
the buttons except JButton exhibit this exclusive or behavior. .
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 (youll 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 books 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. .
//: c14:Faces.java
// Icon behavior in Jbuttons.
// <applet code=Faces width=400 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class Faces extends JApplet {
private static Icon[] faces;
private JButton jb, jb2 = new JButton("Disable");
private boolean mad = false;
public void init() {
faces = new Icon[] {
new ImageIcon(getClass().getResource("Face0.gif")),
new ImageIcon(getClass().getResource("Face1.gif")),
new ImageIcon(getClass().getResource("Face2.gif")),
new ImageIcon(getClass().getResource("Face3.gif")),
new ImageIcon(getClass().getResource("Face4.gif")),
};
jb = new JButton("JButton", faces[3]);
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 as an argument for many different Swing component
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 its pressed, disabled, or rolled over (the mouse moves over
it without clicking). Youll see that this gives the button a nice animated feel. .
The previous example added a tool tip to the
button. Almost all of the classes that youll 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. .
This example shows the extra behavior that JTextFields
are capable of:
//: c14:TextFields.java
// Text fields and Java events.
// <applet code=TextFields 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 TextFields extends JApplet {
private JButton
b1 = new JButton("Get Text"),
b2 = new JButton("Set Text");
private JTextField
t1 = new JTextField(30),
t2 = new JTextField(30),
t3 = new JTextField(30);
private String s = new String();
private 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 TextFields(), 375, 125);
}
}
class UpperCaseDocument extends PlainDocument {
private boolean upperCase = true;
public void setUpperCase(boolean flag) {
upperCase = flag;
}
public void
insertString(int offset, String str, AttributeSet attSet)
throws BadLocationException {
if(upperCase) str = str.toUpperCase();
super.insertString(offset, str, attSet);
}
} ///:~
The JTextField t3 is included as a place to report when the action listener for
the JTextField t1 is fired. Youll see that the action listener for a
JTextField is fired only when you press the enter key. .
The JTextField t1 has several listeners attached to it. The T1 listener
is a DocumentListener that responds to any change in the document (the
contents of the JTextField, in this case). It automatically copies all text
from t1 into t2. In addition, t1s document is set to a derived
class of PlainDocument, called UpperCaseDocument, which forces all
characters to uppercase. It automatically detects backspaces and performs the deletion,
adjusting the caret and handling everything as you would expect. .
JComponent 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 youre
using (stripping off all the path information), then puts that name in a JLabel in the middle of the panel:
//: c14: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 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);
}
} ///:~
You can also create your own borders and put them inside buttons, labels,
etc.anything derived from JComponent. .
Most of the time youll just want to let a JScrollPane
do its job, but you can also control which scroll bars are allowedvertical,
horizontal, both, or neither:
//: c14:JScrollPanes.java
// Controlling the scrollbars in a JScrollPane.
// <applet code=JScrollPanes 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 JScrollPanes extends JApplet {
private JButton
b1 = new JButton("Text Area 1"),
b2 = new JButton("Text Area 2"),
b3 = new JButton("Replace Text"),
b4 = new JButton("Insert Text");
private 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);
private 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 JScrollPanes(), 300, 725);
}
} ///:~
Using different arguments in the JScrollPane constructor controls the scrollbars
that are available. This example also dresses things up a bit using borders. .
The JTextPane control provides a
great deal of support for editing, without much effort. The following example makes very
simple use of this component, ignoring the bulk of the functionality of the class:
//: c14:TextPane.java
// The JTextPane control is a little editor.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
import com.bruceeckel.util.*;
public class TextPane extends JFrame {
private JButton b = new JButton("Add Text");
private JTextPane tp = new JTextPane();
private static Generator sg =
new Arrays2.RandStringGenerator(7);
public TextPane() {
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
for(int i = 1; i < 10; i++)
tp.setText(tp.getText() + sg.next() + "\n");
}
});
Container cp = getContentPane();
cp.add(new JScrollPane(tp));
cp.add(BorderLayout.SOUTH, b);
}
public static void main(String[] args) {
Console.run(new TextPane(), 475, 425);
}
} ///:~
The button just adds randomly generated text. The intent of the JTextPane is to
allow text to be edited in place, so you will see that there is no append( )
method. In this case (admittedly, a poor use of the capabilities of JTextPane), the
text must be captured, modified, and placed back into the pane using setText( ).
.
As mentioned before, the default layout behavior of an applet is to use the BorderLayout.
If you add something to the pane without specifying any details, it just fills the center
of the pane out to the edges. However, if you specify one of the surrounding regions
(NORTH, SOUTH, EAST, or WEST) as is done here, the component will fit itself into that
region; in this case, the button will nest down at the bottom of the screen. .
Notice the built-in features of JTextPane, such as automatic line wrapping.
There are lots of other features that you can look up using the JDK documentation. .
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. .
Youll normally create a JCheckBox
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 JCheckBox
has been created. .
Whenever a JCheckBox is set or cleared, an event occurs, which you can capture
the same way you do a button: by using an ActionListener. The following example
uses a JTextArea to enumerate all the check boxes that have been checked:
//: c14:CheckBoxes.java
// Using JCheckBoxes.
// <applet code=CheckBoxes width=200 height=200></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class CheckBoxes extends JApplet {
private JTextArea t = new JTextArea(6, 15);
private 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);
}
private 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 CheckBoxes(), 200, 200);
}
} ///:~
The trace( ) method sends the name of the selected JCheckBox and its
current state to the JTextArea using append( ), so youll see a
cumulative list of the checkboxes that were selected and what their state is. .
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. .
All you need to do to set up an
associated group of JRadioButtons is to add them to a ButtonGroup (you can have any number of ButtonGroups on
a form). One of the buttons can optionally have its starting state set to true
(using the second argument in the constructor). If you try to set more than one radio
button to true, then only the final one set will be true. .
Heres a simple example of the use of radio buttons. Note that you capture radio
button events like all others:
//: c14:RadioButtons.java
// Using JRadioButtons.
// <applet code=RadioButtons width=200 height=100></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class RadioButtons extends JApplet {
private JTextField t = new JTextField(15);
private ButtonGroup g = new ButtonGroup();
private JRadioButton
rb1 = new JRadioButton("one", false),
rb2 = new JRadioButton("two", false),
rb3 = new JRadioButton("three", false);
private 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 RadioButtons(), 200, 100);
}
} ///:~
To display the state, a text field is used. This field is set to non-editable because
its used only to display data, not to collect it. Thus it is an alternative to using
a JLabel. .
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, its a more compact way to
accomplish this, and its 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). .
By default, JComboBox box is not like the combo box in Windows, which lets
you select from a list or type in your own selection. To produce this behavior you
must call setEditable( ).
With a JComboBox box, you choose one and only one element from the list. In the
following example, the JComboBox box starts with a certain number of entries, and
then new entries are added to the box when a button is pressed. .
//: c14:ComboBoxes.java
// Using drop-down lists.
// <applet code=ComboBoxes width=200 height=125></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class ComboBoxes extends JApplet {
private String[] description = {
"Ebullient", "Obtuse", "Recalcitrant", "Brilliant",
"Somnescent", "Timorous", "Florid", "Putrescent"
};
private JTextField t = new JTextField(15);
private JComboBox c = new JComboBox();
private JButton b = new JButton("Add items");
private 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 ComboBoxes(), 200, 125);
}
} ///:~
The JTextField displays the selected index, which is the sequence
number of the currently selected element, as well as the text of the selected item in the
combo box. .
List boxes are significantly different
from JComboBox boxes, and not just in appearance. While a JComboBox box
drops down when you activate it, a JList occupies some
fixed number of lines on a screen all the time and doesnt change. If you want to see
the items in a list, you simply call getSelectedValues( ),
which produces an array of String of the items that have been selected. .
A JList allows multiple selection; if you control-click on more than one item
(holding down the control key while performing additional mouse clicks), the
original item stays highlighted and you can select as many as you want. If you select an
item, then shift-click on another item, all the items in the span between the two are
selected. To remove an item from a group, you can control-click it. .
//: c14:List.java
// <applet code=List width=250 height=375></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 List extends JApplet {
private String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
private DefaultListModel lItems=new DefaultListModel();
private JList lst = new JList(lItems);
private JTextArea t =
new JTextArea(flavors.length, 20);
private JButton b = new JButton("Add Item");
private 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);
}
}
};
private ListSelectionListener ll =
new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if(e.getValueIsAdjusting()) return;
t.setText("");
Object[] items=lst.getSelectedValues();
for(int i = 0; i < items.length; i++)
t.append(items[i] + "\n");
}
};
private 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 List(), 250, 375);
}
} ///:~
You can see that borders have also been added to the lists. .
If you just want to put an array of Strings into a JList, theres a
much simpler solution; you pass the array to the JList constructor, and it builds
the list automatically. The only reason for using the list model in the
preceding example is so that the list could be manipulated during the execution of the
program. .
JLists do not automatically provide direct support for scrolling. Of course, all
you need to do is wrap the JList in a JScrollPane,
and the details are automatically managed for you. .
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.
//: c14: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 {
private String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
private JTabbedPane tabs = new JTabbedPane();
private JTextField txt = new JTextField(20);
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) {
txt.setText("Tab selected: " +
tabs.getSelectedIndex());
}
});
Container cp = getContentPane();
cp.add(BorderLayout.SOUTH, txt);
cp.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 in applet programming the use of pop-up dialogs is discouraged by automatically
adding a little warning to any dialog that pops up out of an applet. .
When you run the program, youll 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. .
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 youll
most commonly use are probably the message dialog and confirmation dialog, invoked using
the static JOptionPane.showMessageDialog( ) and JOptionPane.
showConfirmDialog( ). The following example shows a subset of the message boxes
available with JOptionPane:
//: c14:MessageBoxes.java
// Demonstrates JoptionPane.
// <applet code=MessageBoxes width=200 height=150></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class MessageBoxes extends JApplet {
private JButton[] b = {
new JButton("Alert"), new JButton("Yes/No"),
new JButton("Color"), new JButton("Input"),
new JButton("3 Vals")
};
private JTextField txt = new JTextField(15);
private 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)
txt.setText("Color Selected: " + options[sel]);
} else if(id.equals("Input")) {
String val = JOptionPane.showInputDialog(
"How many fingers do you see?");
txt.setText(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)
txt.setText(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]);
}
cp.add(txt);
}
public static void main(String[] args) {
Console.run(new MessageBoxes(), 200, 200);
}
} ///:~
To be able to write a single ActionListener, Ive used the somewhat risky
approach of checking the String labels on the buttons. The problem with this is
that its easy to get the label a little bit wrong, typically in capitalization, and
this bug can be hard to spot. .
Note that showOptionDialog( ) and showInputDialog( ) provide
return objects that contain the value entered by the user. .
Each
component capable of holding a menu, including JApplet, JFrame, JDialog,
and their descendants, has a setJMenuBar( ) method that accepts a JMenuBar
(you can have only one JMenuBar on a particular component). You add JMenus
to the JMenuBar, and JMenuItems to the JMenus. Each JMenuItem
can have an ActionListener attached to it, to be fired when 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 is a very simple menu example:
//: c14: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 {
private JTextField t = new JTextField(15);
private ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.setText(((JMenuItem)e.getSource()).getText());
}
};
private JMenu[] menus = {
new JMenu("Winken"), new JMenu("Blinken"),
new JMenu("Nod")
};
private 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() {
for(int i = 0; i < items.length; i++) {
items[i].addActionListener(al);
menus[i % 3].add(items[i]);
}
JMenuBar mb = new JMenuBar();
for(int i = 0; i < menus.length; i++)
mb.add(menus[i]);
setJMenuBar(mb);
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(t);
}
public static void main(String[] args) {
Console.run(new SimpleMenus(), 200, 75);
}
} ///:~
The use of the modulus operator in i%3 distributes the menu items
among the three JMenus. Each JMenuItem must have an ActionListener
attached to it; here, the same ActionListener is used everywhere, but youll
usually need an individual one for each JMenuItem. .
JMenuItem inherits AbstractButton, so it has some
button-like behaviors. By itself, it provides an item that can be placed on a drop-down
menu. There are also three types inherited from JMenuItem: JMenu to hold
other JMenuItems (so you can have cascading menus); JCheckBoxMenuItem, which
produces a checkmark to indicate whether that menu item is selected; and JRadioButtonMenuItem,
which contains a radio button. .
As a more sophisticated example, here are the ice cream flavors again, used to create
menus. This example also shows cascading menus, keyboard mnemonics, JCheckBoxMenuItems,
and the way you can dynamically change menus:
//: c14:Menus.java
// Submenus, checkbox menu items, swapping menus,
// mnemonics (shortcuts) and action commands.
// <applet code=Menus width=300 height=100></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
public class Menus extends JApplet {
private String[] flavors = {
"Chocolate", "Strawberry", "Vanilla Fudge Swirl",
"Mint Chip", "Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie"
};
private JTextField t = new JTextField("No flavor", 30);
private JMenuBar mb1 = new JMenuBar();
private JMenu
f = new JMenu("File"),
m = new JMenu("Flavors"),
s = new JMenu("Safety");
// Alternative approach:
private JCheckBoxMenuItem[] safety = {
new JCheckBoxMenuItem("Guard"),
new JCheckBoxMenuItem("Hide")
};
private JMenuItem[] file = { new JMenuItem("Open") };
// A second menu bar to swap to:
private JMenuBar mb2 = new JMenuBar();
private JMenu fooBar = new JMenu("fooBar");
private JMenuItem[] other = {
// Adding a menu shortcut (mnemonic) is very
// simple, but only JMenuItems can have them
// in their constructors:
new JMenuItem("Foo", KeyEvent.VK_F),
new JMenuItem("Bar", KeyEvent.VK_A),
// No shortcut:
new JMenuItem("Baz"),
};
private 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
}
}
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!");
}
}
}
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 hidden? " + target.getState());
}
}
public void init() {
ML ml = new ML();
CMIL cmil = new CMIL();
safety[0].setActionCommand("Guard");
safety[0].setMnemonic(KeyEvent.VK_G);
safety[0].addItemListener(cmil);
safety[1].setActionCommand("Hide");
safety[1].setMnemonic(KeyEvent.VK_H);
safety[1].addItemListener(cmil);
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]);
s.setMnemonic(KeyEvent.VK_A);
f.add(s);
f.setMnemonic(KeyEvent.VK_F);
for(int i = 0; i < file.length; i++) {
file[i].addActionListener(fl);
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());
b.setMnemonic(KeyEvent.VK_S);
cp.add(b, BorderLayout.NORTH);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
fooBar.setMnemonic(KeyEvent.VK_B);
mb2.add(fooBar);
}
public static void main(String[] args) {
Console.run(new Menus(), 300, 100);
}
} ///:~
In this program I placed the menu items into arrays and then stepped through each array
calling add( ) for each JMenuItem. This makes adding or subtracting a
menu item somewhat less tedious. .
This program creates not one but two JMenuBars to demonstrate that menu bars can
be actively swapped while the program is running. You can see how a JMenuBar is
made up of JMenus, and each JMenu is made up of JMenuItems,
JCheckBoxMenuItems, or even other JMenus (which produce submenus). When a
JMenuBar is assembled, it can be installed into the current program with the setJMenuBar( )
method. Note that when the button is pressed, it checks to see which menu is currently
installed by calling getJMenuBar( ), then it 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 source of programming errors. .
The checking and unchecking of the menu items is taken care of automatically. The code
handling the JCheckBoxMenuItems shows two different ways to determine what was
checked: string matching (which, as mentioned above, isnt a very safe approach
although youll see it used) and matching on the event target object. As shown, the getState( ) method can be used to reveal the state. You
can also change the state of a JCheckBoxMenuItem with setState( ). .
The events for menus are a bit inconsistent and can lead to confusion: JMenuItems use ActionListeners, but JCheckBoxMenuItems
use ItemListeners. The JMenu objects can also
support ActionListeners, but thats not usually helpful. In general,
youll attach listeners to each JMenuItem, JCheckBoxMenuItem, or JRadioButtonMenuItem,
but the example shows ItemListeners and ActionListeners attached to the
various menu components. .
Swing supports
mnemonics, or keyboard shortcuts, so you can select anything derived from AbstractButton
(button, menu item, etc.) by using the keyboard instead of the mouse. These are quite
simple; for JmenuItem,you can use the overloaded constructor that takes as a second
argument the identifier for the key. However, most AbstractButtons do not have
constructors like this, so the more general way to solve the problem is to use the setMnemonic( )
method. The preceding example adds mnemonics to the button and
some of the menu items; shortcut indicators automatically appear on the components. .
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 change the code
(which would 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, but the menu label can change. All the code works with the action
command, so its 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
arent do not have their action command set. .
The bulk of the work happens in the listeners. BL performs the JMenuBar swapping. In ML, the figure out who
rang approach is taken by getting the source of the ActionEvent
and casting it to a JMenuItem, then getting the action
command string to pass it through a cascaded if statement. .
The FL listener is simple even though its handling all the different
flavors in the flavor menu. This approach is useful if you have enough simplicity in your
logic, but in general, youll want to take the approach used with FooL, BarL,
and BazL, in which each is 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. .
You can see that menu code quickly gets long-winded and messy. This is another case
where the use of a GUI builder is the appropriate solution. A good tool will also handle
the maintenance of the 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 that you want to produce pop-up
behavior:
//: c14: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 {
private JPopupMenu popup = new JPopupMenu();
private 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(((JApplet)e.getComponent())
.getContentPane(), 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.
.
In a good GUI
framework, drawing should be reasonably easyand it is, in the Swing library. The
problem with any drawing example is that the calculations that determine where things go
are typically a lot more complicated that the calls to the drawing routines, and these
calculations are often mixed together with the drawing calls, so it can seem that the
interface is more complicated than it actually is. .
For simplicity, consider the problem of representing data on the screenhere, the
data will be provided by the built-in Math.sin( ) method, that produces a
mathematical sine function. To make things a little more interesting, and to further
demonstrate how easy it is to use Swing components, a slider will be placed at the bottom
of the form to dynamically control the number of sine wave cycles that are displayed. In
addition, if you resize the window, youll see that the sine wave refits itself to
the new window size. .
Although any JComponent may be painted and thus used as
a canvas, if you just want a straightforward drawing surface, you will typically inherit
from a JPanel. The only method you need to override is paintComponent( ), which is called whenever that
component must be repainted (you normally dont need to worry about this, because the
decision is managed by Swing). When it is called, Swing passes a Graphics
object to the method, and you can then use this object to draw or paint on the surface. .
In the following example, all the intelligence concerning painting is in the SineDraw
class; the SineWave class simply configures the program and the slider control.
Inside SineDraw, the setCycles( ) method provides a hook to allow
another objectthe slider control, in this caseto control the number of cycles.
//: c14:SineWave.java
// Drawing with Swing, using a JSlider.
// <applet code=SineWave width=700 height=400></applet>
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
class SineDraw extends JPanel {
private static final int SCALEFACTOR = 200;
private int cycles;
private int points;
private double[] sines;
private int[] pts;
public SineDraw() { setCycles(5); }
public void setCycles(int newCycles) {
cycles = newCycles;
points = SCALEFACTOR * cycles * 2;
sines = new double[points];
for(int i = 0; i < points; i++) {
double radians = (Math.PI/SCALEFACTOR) * i;
sines[i] = Math.sin(radians);
}
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int maxWidth = getWidth();
double hstep = (double)maxWidth/(double)points;
int maxHeight = getHeight();
pts = new int[points];
for(int i = 0; i < points; i++)
pts[i] =
(int)(sines[i] * maxHeight/2 * .95 + maxHeight/2);
g.setColor(Color.RED);
for(int i = 1; i < points; i++) {
int x1 = (int)((i - 1) * hstep);
int x2 = (int)(i * hstep);
int y1 = pts[i-1];
int y2 = pts[i];
g.drawLine(x1, y1, x2, y2);
}
}
}
public class SineWave extends JApplet {
private SineDraw sines = new SineDraw();
private JSlider adjustCycles = new JSlider(1, 30, 5);
public void init() {
Container cp = getContentPane();
cp.add(sines);
adjustCycles.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
sines.setCycles(
((JSlider)e.getSource()).getValue());
}
});
cp.add(BorderLayout.SOUTH, adjustCycles);
}
public static void main(String[] args) {
Console.run(new SineWave(), 700, 400);
}
} ///:~
All of the fields and arrays are used in the calculation of the sine wave points; cycles
indicates the number of complete sine waves desired, points contains the total
number of points that will be graphed, sines contains the sine function values, and
pts contains the y-coordinates of the points that will be drawn on the JPanel.
The setCycles( ) method creates the arrays according to the number of points
needed and fills the sines array with numbers. By calling repaint( ) , setCycles( )
forces paintComponent( ) to be called so the rest of the calculation and
redraw will take place. .
The first thing you must do when you override paintComponent( ) is to call
the base-class version of the method. Then you are free to do whatever you like; normally,
this means using the Graphics methods that you can find in the documentation for java.awt.Graphics
(in the JDK documentation from java.sun.com) to draw and paint pixels onto the JPanel.
Here, you can see that almost all the code is involved in performing the calculations; the
only two method calls that actually manipulate the screen are setColor( ) and drawLine( ).
You will probably have a similar experience when creating your own program that displays
graphical data; youll spend most of your time figuring out what it is you want to
draw, but the actual drawing process will be quite simple. .
When I created this program, the bulk of my time was spent in getting the sine wave to
display. Once I did that, I thought it would be nice to be able to dynamically change the
number of cycles. My programming experiences when trying to do such things in other
languages made me a bit reluctant to try this, but it turned out to be the easiest part of
the project. I created a JSlider (the arguments are the left-most value of the JSlider,
the right-most value, and the starting value, respectively, but there are other
constructors as well) and dropped it into the JApplet. Then I looked at the JDK
documentation and noticed that the only listener was the addChangeListener,
which was triggered whenever the slider was changed enough for it to produce a different
value. The only method for this was the obviously named stateChanged( ),
which provided a ChangeEvent object so that I could look backward to the source of
the change and find the new value. By calling the sines objects setCycles( ),
the new value was incorporated and the JPanel redrawn. .
In general, you will find that most of your Swing problems can be solved by following a
similar process, and youll find that its generally quite simple, even if you
havent used a particular component before. .
If your problem is more complex, there are other more sophisticated alternatives for
drawing, including third-party JavaBeans components and the Java 2D API. These solutions
are beyond the scope of this book, but you should look them up if your drawing code
becomes too onerous. .
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 less frequently used in applets. .
To create a dialog box, you inherit from JDialog, which is just another kind of Window,
like a JFrame. A JDialog has a layout manager (which defaults to BorderLayout),
and you add event listeners to deal with events. One significant difference when the
dialog window is closed is that you dont want to shut down the application. Instead,
you release the resources used by the dialogs window by calling dispose( ). Heres a very simple example:
//: c14:Dialogs.java
// Creating and using Dialog Boxes.
// <applet code=Dialogs width=125 height=75></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) {
dispose(); // Closes the dialog
}
});
cp.add(ok);
setSize(150,125);
}
}
public class Dialogs extends JApplet {
private JButton b1 = new JButton("Dialog Box");
private MyDialog dlg = new MyDialog(null);
public void init() {
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
dlg.show();
}
});
getContentPane().add(b1);
}
public static void main(String[] args) {
Console.run(new Dialogs(), 125, 75);
}
} ///:~
Once the JDialog is created, the show( )
method must be called to display and activate it. For the dialog to close, it must call dispose( ).
.
Youll see that anything that pops up out of an applet, including dialog boxes, is
untrusted. That is, you get a warning in the window thats been popped
up. This is because, in concept, it would be possible to fool users into thinking that
theyre 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 detachedso
theoretically, its possible. As a result, it is not so common to see an applet that
uses a dialog box. .
The following example is more complex; 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.
//: c14:TicTacToe.java
// 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.*;
public class TicTacToe extends JApplet {
private JTextField
rows = new JTextField("3"),
cols = new JTextField("3");
private static final int BLANK = 0, XX = 1, OO = 2;
class ToeDialog extends JDialog {
private int turn = XX; // Start with x's turn
ToeDialog(int cellsWide, int cellsHigh) {
setTitle("The game itself");
Container cp = getContentPane();
cp.setLayout(new GridLayout(cellsWide, cellsHigh));
for(int i = 0; i < cellsWide * cellsHigh; i++)
cp.add(new ToeButton());
setSize(cellsWide * 50, cellsHigh * 50);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
}
class ToeButton extends JPanel {
private int state = BLANK;
public ToeButton() { addMouseListener(new ML()); }
public void paintComponent(Graphics g) {
super.paintComponent(g);
int
x1 = 0, y1 = 0,
x2 = getSize().width - 1,
y2 = getSize().height - 1;
g.drawRect(x1, y1, x2, y2);
x1 = x2/4;
y1 = y2/4;
int wide = x2/2, 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 TicTacToe(), 200, 100);
}
} ///:~
Because statics can only be at the outer level of the class, inner classes
cannot have static data or nested classes. .
The paintComponent( )
method draws the square around the panel and the x or the o. This
is full of tedious calculations, but its straightforward. .
A mouse click is captured by the MouseListener, which first checks to see if the
panel has anything written on it. If not, the parent window is queried to find out whose
turn it is, which establishes the state of the ToeButton. Via the inner class
mechanism, the ToeButton 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 state change, the ToeButton 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. .
TicTacToe sets up the whole application by creating the JTextFields (for
inputting the rows and columns of the button grid) and the go button with its ActionListener.
When the button is pressed, the data in the JTextFields must be fetched, and, since
they are in String form, turned into ints using the static Integer.parseInt( ) method. .
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, so Javas 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 action listeners for the two different button clicks:
//: c14: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 {
private JTextField
filename = new JTextField(),
dir = new JTextField();
private JButton
open = new JButton("Open"),
save = new JButton("Save");
public FileChooserTest() {
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 call showOpenDialog( ), and for a
save file dialog, you call showSaveDialog( ). These commands
dont return until the dialog is closed. The JFileChooser object still exists,
so you can read data from it. The methods getSelectedFile( ) and getCurrentDirectory( )
are two ways you can interrogate the results of the operation. If these return null,
it means the user canceled out of the dialog. .
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:
//: c14:HTMLButton.java
// Putting HTML text on Swing components.
// <applet code=HTMLButton width=250 height=500></applet>
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import com.bruceeckel.swing.*;
public class HTMLButton extends JApplet {
private JButton b = new JButton(
"<html><b><font size=+2>" +
"<center>Hello!<br><i>Press me now!");
public void init() {
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
getContentPane().add(new JLabel("<html>" +
"<i><font size=+4>Kapow!"));
// Force a re-layout to include the new label:
validate();
}
});
Container cp = getContentPane();
cp.setLayout(new FlowLayout());
cp.add(b);
}
public static void main(String[] args) {
Console.run(new HTMLButton(), 200, 500);
}
} ///:~
You must start the text with <html>, and then you can use normal HTML
tags. Note that you are not forced to include the normal closing tags. .
The ActionListener adds a new JLabel to the form, which also contains
HTML text. However, this label is not added during init( ), so you must call
the containers validate( ) method in order to force a re-layout of the
components (and thus the display of the new label). .
You can also use HTML text for JTabbedPane, JMenuItem, JToolTip, JradioButton,
and JCheckBox. .
A slider (which has already been used in SineWave.java)
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:
//: c14: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 {
private JProgressBar pb = new JProgressBar();
private 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 key to hooking the two components together is in sharing their model, in the line: .
pb.setModel(sb.getModel());
Of course, you could also control the two using a listener, but this is more
straightforward for simple situations.
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. .
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, howevercertainly 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): .
//: c14:Trees.java
// Simple Swing tree. Trees can be vastly more complex.
// <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 {
private 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 {
private 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" },
};
private static int i = 0;
private DefaultMutableTreeNode root, child, chosen;
private JTree tree;
private 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);
// Puts the new node on the 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 JScrollPanethis
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 is used if nothing is selected) and the models insertNodeInto( ) method does all the work of changing
the tree and causing it to be updated. .
An example like the preceding one may give you what you need in a tree. However, trees
have the power to do just about anything you can imagineeverywhere you see the word
default in the preceding example, 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. Despite this,
its a good design, and the alternatives are usually much worse. .
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 Thinking in Enterprise Java), and thus they
have a tremendous amount of flexibility, which you pay for in complexity. Theres
easily enough here to allow the creation of a full-blown spreadsheet application 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, youll typically create a TableModel
first. You can fully implement the TableModel interface, but its simpler to
inherit from the helper class AbstractTableModel:
//: c14:JTableDemo.java
// Simple demonstration of JTable.
// <applet code=Table width=350 height=200></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.table.*;
import javax.swing.event.*;
import com.bruceeckel.swing.*;
public class JTableDemo extends JApplet {
private JTextArea txt = new JTextArea(4, 20);
// 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) {
txt.setText(""); // Clear it
for(int i = 0; i < data.length; i++) {
for(int j = 0; j < data[0].length; j++)
txt.append(data[i][j] + " ");
txt.append("\n");
}
}
}
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 void init() {
Container cp = getContentPane();
JTable table = new JTable(new DataModel());
cp.add(new JScrollPane(table));
cp.add(BorderLayout.SOUTH, txt);
}
public static void main(String[] args) {
Console.run(new JTableDemo(), 350, 200);
}
} ///:~
DataModel contains an array of data, but you could also get the data from some
other source such as a database. The constructor adds a TableModelListener that
prints the array every time the table is changed. The rest of the methods follow the Beans
naming convention (using get and set methods, which will be
described later in this chapter) 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. This example also puts the JTable in a JScrollPane. .
Pluggable Look & Feel 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 Swings 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 (this is almost certainly the best choice in most cases, to
avoid confounding the user). 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 dont have to do anythingits the
default. But if you want instead to use the current operating environments look and
feel, you just insert the following code, typically at the beginning of your main( ),
but at least before any components are added:
try {
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
} catch(Exception e) {
throw new RuntimeException(e);
}
You dont 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, so you may
at least want see some results via the catch clause. .
Here is a program that takes a command-line argument to select a look and feel, and
shows how several different components look under the chosen look and feel:
//: c14: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 {
private String[] choices = {
"eeny","meeny","Minnie","Mickey","Moe","Larry","Curly"
};
private 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 e) {
e.printStackTrace();
}
} else if(args[0].equals("system")) {
try {
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
} catch(Exception e) {
e.printStackTrace();
}
} else if(args[0].equals("motif")) {
try {
UIManager.setLookAndFeel("com.sun.java."+
"swing.plaf.motif.MotifLookAndFeel");
} catch(Exception e) {
e.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 youre 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, youll discover it is beyond the
scope of many dedicated Swing books!). .
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 youre interested in. .
The following program is a simple demonstration of cut, copy,
and paste with String data in a JTextArea. One thing
youll notice is that the keyboard sequences you normally use for cutting, copying,
and pasting also work. But if you look at any JTextField or JTextArea in any
other program, youll 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 something other than a JTextComponent.
//: c14:CutAndPaste.java
// Using the clipboard.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.datatransfer.*;
import com.bruceeckel.swing.*;
public class CutAndPaste extends JFrame {
private JMenuBar mb = new JMenuBar();
private JMenu edit = new JMenu("Edit");
private JMenuItem
cut = new JMenuItem("Cut"),
copy = new JMenuItem("Copy"),
paste = new JMenuItem("Paste");
private JTextArea text = new JTextArea(20, 20);
private Clipboard clipbd =
getToolkit().getSystemClipboard();
public CutAndPaste() {
cut.addActionListener(new CutL());
copy.addActionListener(new CopyL());
paste.addActionListener(new PasteL());
edit.add(cut);
edit.add(copy);
edit.add(paste);
mb.add(edit);
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 ex) {
System.err.println("Not String flavor");
}
}
}
public static void main(String[] args) {
Console.run(new CutAndPaste(), 300, 200);
}
} ///:~
The creation and addition of the menu and JTextArea should by now seem a
pedestrian activity. Whats 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 thats
been copied. The special two lines are the creation of a StringSelection
object from the String and the call to setContents( )
with this StringSelection. Thats 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 dont 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 youre interested in. Here, however, the bold approach is
taken: getTransferData( ) is called, assuming that the
contents supports the String flavor, and if it doesnt, the problem is sorted
out in the exception handler. .
In the future you can expect more data flavors to be supported.
An important use of the JAR utility 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 wasnt as fast as it
could have been. .
JAR files solve the problem by compressing all of your .class files into a
single file that is downloaded by the browser. Now you can create the right design without
worrying about how many .class files it will generate, and the user will get a much
faster download time. .
Consider TicTacToe.java. It looks like a single class, but in fact it contains
five inner classes, so thats six in all. Once youve compiled the program, you
package it into a JAR file with the line:
jar cf TicTacToe.jar *.class
This assumes that the only .class files in the current directory are the ones
from TicTacToe.java (otherwise, youll get extra baggage). .
Now you can create an HTML page with the new archive tag to indicate the name of
the JAR file. Here is the basic applet tag:
<head><title>TicTacToe Example Applet
</title></head>
<body>
<applet code=TicTacToe.class
archive=TicTacToe.jar
width=200 height=100>
</applet>
</body>
Youll need to run this file through the HTMLconverter application that
comes with the JDK in order to get it to work. .
Because of the sandbox security model,
unsigned applets are prevented from performing certain tasks on the client, like writing
to a file or connecting to a local network. [83]
A signed applet verifies to the user that the person who claims to have created the applet
actually did, and that the contents of the JAR file have not been tampered with since that
file left the server. Without this minimum guarantee, the applet will not be allowed to do
anything that could damage a persons machine or violate their privacy. This is a
restriction that is vital for the safe use of applets through the Internet, but which also
makes applets relatively powerless. .
Since the release of the Java Plugin, the process of signing applets has become simpler
and more standardized, and applets have become a more viable means of deploying your
application. Signing an applet has become a reasonably straightforward process and uses
standard Java tools. .
Prior to the plugin, you had to sign a .jar
file with the Netscape tools for a Netscape client, a .cab file with the Microsoft
tools for an Internet Explorer client, and create an applet tag in the HTML file for both
platforms. The user would then have to install a certificate on the browser so that the
applet would be trusted. .
The plugin not only provides a standard approach to applet signing and deployment, but
it also provides the end user with a better experience by making certificate installation
automatic. .
Consider an applet that wants to have access to the clients file system and read
and write some files. This is very similar to FileChooserTest.java, but because
this is an applet, it will only be able to open the Swing JFileChooser dialog if it
is running from a signed JAR file. Otherwise, the showOpenDialog( ) method
will throw a SecurityException. .
//: c14:signedapplet:FileAccessApplet.java
// Demonstration of File dialog boxes.
package c14.signedapplet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class FileAccessApplet extends JApplet {
private JTextField
filename = new JTextField(),
dir = new JTextField();
private JButton
open = new JButton("Open"),
save = new JButton("Save");
private JEditorPane ep = new JEditorPane();
private JScrollPane jsp = new JScrollPane();
private File file;
public void init() {
JPanel p = new JPanel();
open.addActionListener(new OpenL());
p.add(open);
save.addActionListener(new SaveL());
p.add(save);
Container cp = getContentPane();
jsp.getViewport().add(ep);
cp.add(jsp, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
dir.setEditable(false);
save.setEnabled(false);
ep.setContentType("text/html");
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();
c.setFileFilter(new TextFileFilter());
// Demonstrate "Open" dialog:
int rVal = c.showOpenDialog(FileAccessApplet.this);
if(rVal == JFileChooser.APPROVE_OPTION) {
file = c.getSelectedFile();
filename.setText(file.getName());
dir.setText(c.getCurrentDirectory().toString());
try {
System.out.println("Url is " + file.toURL());
ep.setPage(file.toURL());
// ep.repaint();
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
if(rVal == JFileChooser.CANCEL_OPTION) {
filename.setText("You pressed cancel");
dir.setText("");
} else {
save.setEnabled(true);
}
}
}
class SaveL implements ActionListener {
public void actionPerformed(ActionEvent e) {
JFileChooser c = new JFileChooser(file);
c.setSelectedFile(file);
// Demonstrate "Save" dialog:
int rVal = c.showSaveDialog(FileAccessApplet.this);
if(rVal == JFileChooser.APPROVE_OPTION) {
filename.setText(c.getSelectedFile().getName());
dir.setText(c.getCurrentDirectory().toString());
try {
FileWriter fw = new FileWriter(file);
ep.write(fw);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
if(rVal == JFileChooser.CANCEL_OPTION) {
filename.setText("You pressed cancel");
dir.setText("");
}
}
}
public class TextFileFilter extends
javax.swing.filechooser.FileFilter {
public boolean accept(File f) {
return f.getName().endsWith(".txt")
|| f.isDirectory();
}
public String getDescription() {
return "Text Files (*.txt)";
}
}
public static void main(String[] args) {
Console.run(new FileAccessApplet(), 500, 500);
}
} ///:~
It appears to be an ordinary applet. However, as it stands, it would not be allowed to
open and close files on a clients system. To make this run as a signed applet, you
need to put it into a JAR file (see the section on the jar utility, earlier in this
chapter) and sign the JAR file. .
Once you have a JAR file, you will need a certificate or a key to sign it with. If you
were a large corporation, you would apply to a signing authority like Verisign or Thawte,
and they would issue you a certificate. This is used to sign code and thus identify to a
user that you are indeed the provider of the code they are downloading, and that the code
that has been deployed hasnt been modified since you signed it. Essentially, the
digital signature is a load of bits, and the signing authority vouches for you when
someone downloads that signature. .
A certificate from a signing authority
costs money and requires regular renewal. In our case we can just make a little
self-signed one. This needs to be stored in a file somewhere (it is usually called the keychain).
If you type:
keytool list
then it will try to access the default file. If there is no file, then you need to
create one, or specify an existing one. You might try to search for a file called
cacerts, and then try
keytool -list -file <path/filename>
The default location is usually
{java.home}/lib/security/cacerts
where the java.home property points to the JRE home.
You can also easily make a self-signed certificate for testing purposes using the keytool.
If you have your Java bin directory in your executable path, you can type:
keytool genkey alias <keyname> -keystore <url>
where keyname is the alias name that you want to give the key, say
mykeyname, and url is the location of the file that stores your keys,
usually the cacerts file as described above. .
You will now be prompted for the password. Unless you have changed the default, this
will be changeit (a hint to do just that). Next you will be asked for your
name, the organizational unit, the organization, city, state, and country. This
information is stored in the certificate. Lastly, you will be asked for a password for
that key. If you are really security conscious, you can give it a separate password, but
the default password is the same as the keystore itself, and is usually adequate. The
above information can be specified on the command line from within a build tool such as
Ant. .
If you invoke the keytool utility with no parameters at the command prompt, it
will give you a list of its numerous options. You might like to use the valid
option, for example, which enables you to specify how many days the key will be valid for.
.
To confirm that your key is now in the cacerts file, type:
keytool list keystore <url>
and enter the password as before. Your key may be hidden among the other keys already
in your certificate files. .
Your new certificate is self-signed and thus not actually trusted by a signing
authority. If you use this certificate to sign a JAR file, the end user will get a
warning, and a strong recommendation not to use your software. You and your users
will have to tolerate this until you are prepared to pay for a trusted certificate for
commercial purposes. .
To sign your JAR file, use the standard Java jarsigner tool as follows:
jarsigner keystore <url> <jarfile> <keyname>
where url is the location of your cacerts file, jarfile is the name of
your JAR file, and keyname is the alias that you gave to your key. You will again
be prompted for the password. .
You now have a JAR file that can be identified as being signed with your key, and that
can guarantee it has not been tampered with (i.e., no files have been changed, added, or
removed) since you signed it. .
All you have to do now is make sure that the applet tag in your HTML file has an
archive element, which specifies the name of your JAR file. .
The applet tag is somewhat more complicated for the plugin, but if you create a simple
tag like:
<APPLET CODE=package.AppletSubclass.class ARCHIVE = myjar.jar WIDTH=300 HEIGHT=200> </APPLET>
and run the HTMLConverter tool on it (this is packaged
with the freely downloadable JDK), it will create the correct applet tag for you. .
Now, when your applet is downloaded by a client, they will be informed that a signed
applet is being loaded, and given the option of trusting the signer. As previously
mentioned, your test certificate doesnt have a very high degree of trust, and the
user will get a warning to this effect. If they opt to trust your applet, it will have
full access to their system and behave as if it were an ordinary application. .
The source code for this book, downloadable from www.BruceEckel.com, contains
complete working configuration files and an Ant build script to properly compile and build
this project. .
Signed applets are powerful and can effectively take the place
of an application, but they must run inside a Web browser. This requires the extra
overhead of the browser running on the client machine, and also means that the user
interface of the applet is limited and often visually confusing. The Web browser has its
own set of menus and toolbars, which will appear above the applet. .
The Java Network Launch Protocol (JNLP) solves the problem without sacrificing
the advantages of applets. With a JNLP application, you can download and install a
standalone Java application onto the clients machine. This can be run from the
command prompt, a desktop icon, or the application manager that is installed with your
JNLP implementation. The application can even be run from the Web site from which it was
originally downloaded. .
A JNLP application can dynamically download resources from the
Internet at run time, and the version can be automatically checked (if the user is
connected to the Internet) . This means that it has all of the advantages of an applet
together with the advantages of standalone applications. .
Like applets, JNLP applications need to be treated with some caution by the
clients system. A JNLP application is Web-based and easy to download, so it might be
malevolent. Because of this, JNLP applications are subject to the same sandbox security
restrictions as applets. Like applets, they can be deployed in signed JAR files, giving
the user the option to trust the signer. Unlike applets, if they are deployed in an
unsigned JAR file, they can still request access to certain resources of the clients
system by means of services in the JNLP API (the user must approve the requests during
program execution). .
Because JNLP describes a protocol, not an implementation, you will need an
implementation in order to use it. Java Web Start, or JAWS, is Suns freely-available
official reference implementation. All you need to do is download and install it, and if
you are using it for development, make sure that the JAR files are in your classpath. If
you are deploying your JNLP application from a Web server, you have to ensure that your
server recognizes the MIME type application/x-java-jnlp-file. If you are using a
recent version of the Tomcat server (http://jakarta.apache.org/tomcat)
this will already be configured. Consult the user guide for your particular server. .
Creating a JNLP application is not difficult. You create a standard application that is
archived in a JAR file, and then you provide a launch file, which is a simple XML file
that gives the client all the information it needs to download and install your
application. If you choose not to sign your JAR file, then you must make use of the
services supplied by the JNLP API for each type of resource you want access to on the
users machine. .
Here is a variation of the example using the JFileChooser dialog, but this time
using the JNLP services to open it, so that the class can be deployed as a JNLP
application in an unsigned JAR file. .
//: c14:jnlp:JnlpFileChooser.java
// Opening files on a local machine with JNLP.
// {Depends: javaws.jar}
package c14.jnlp;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.jnlp.*;
public class JnlpFileChooser extends JFrame {
private JTextField filename = new JTextField();
private JButton
open = new JButton("Open"),
save = new JButton("Save");
private JEditorPane ep = new JEditorPane();
private JScrollPane jsp = new JScrollPane();
private FileContents fileContents;
public JnlpFileChooser() {
JPanel p = new JPanel();
open.addActionListener(new OpenL());
p.add(open);
save.addActionListener(new SaveL());
p.add(save);
Container cp = getContentPane();
jsp.getViewport().add(ep);
cp.add(jsp, BorderLayout.CENTER);
cp.add(p, BorderLayout.SOUTH);
filename.setEditable(false);
p = new JPanel();
p.setLayout(new GridLayout(2,1));
p.add(filename);
cp.add(p, BorderLayout.NORTH);
ep.setContentType("text");
save.setEnabled(false);
}
class OpenL implements ActionListener {
public void actionPerformed(ActionEvent e) {
FileOpenService fs = null;
try {
fs = (FileOpenService)ServiceManager.lookup(
"javax.jnlp.FileOpenService");
} catch(UnavailableServiceException use) {
throw new RuntimeException(use);
}
if(fs != null) {
try {
fileContents = fs.openFileDialog(".",
new String[]{"txt", "*"});
if(fileContents == null)
return;
filename.setText(fileContents.getName());
ep.read(fileContents.getInputStream(), null);
} catch (Exception exc) {
throw new RuntimeException (exc);
}
save.setEnabled(true);
}
}
}
class SaveL implements ActionListener {
public void actionPerformed(ActionEvent e) {
FileSaveService fs = null;
try {
fs = (FileSaveService)ServiceManager.lookup(
"javax.jnlp.FileSaveService");
} catch(UnavailableServiceException use) {
throw new RuntimeException(use);
}
if(fs != null) {
try {
fileContents = fs.saveFileDialog(".",
new String[]{"txt"},
new ByteArrayInputStream(
ep.getText().getBytes()),
fileContents.getName());
if(fileContents == null)
return;
filename.setText(fileContents.getName());
} catch (Exception exc) {
throw new RuntimeException (exc);
}
}
}
}
public static void main(String[] args) {
JnlpFileChooser fc = new JnlpFileChooser();
fc.setSize(400, 300);
fc.setVisible(true);
}
} ///:~
Note that the FileOpenService and the FileCloseService classes are
imported from the javax.jnlp package and that nowhere in the code is the JFileChooser
dialog box referred to directly. The two services used here must be requested using the ServiceManager.lookup( )
method, and the resources on the client system can only be accessed via the objects
returned from this method. In this case, the files on the clients file system are
being written to and read from using the FileContent interface, provided by the
JNLP. Any attempt to access the resources directly by using, say, a File or a FileReader
object would cause a SecurityException to be thrown in the same way that it
would if you tried to use them from an unsigned applet. If you want to use these classes
and not be restricted to the JNLP service interfaces, you must sign the JAR file (see the
previous section on signing JAR files). .
Now that we have a runnable class that makes use of the JNLP services, all that is
needed is for the class to be put into a JAR file and a launch file to be written. Here is
an appropriate launch file for the preceding example. .
<?xml version="1.0" encoding="UTF-8"?>
<jnlp spec = 1.0+
codebase="file://C:\TIJ3code\c14\jnlp"
href="filechooser.jnlp">
<information>
<title>FileChooser demo application</title>
<vendor>Mindview Inc.</vendor>
<description>
Jnlp File choose Application
</description>
<description kind="short">
A demonstration of opening, reading and
writing a text file
</description>
<icon href="images/tijicon.gif"/>
<offline-allowed/>
</information>
<resources>
<j2se version="1.3+"/>
<jar href="jnlpfilechooser.jar" download="eager"/>
</resources>
<application-desc
main-class="c14.jnlp.JnlpFileChooser"/>
</jnlp>
This launch file needs to be saved as a .jnlp file, in this case, filechooser.jnlp,
in the same directory as the JAR file. .
As you can see, it is an XML file with one <jnlp> tag. This has a few
subelements, which are mostly self-explanatory. .
The spec attribute of the jnlp element tells the client system
what version of the JNLP the application can be run with. The codebase attribute
points to the directory where this launch file and the resources can be found. Typically,
it would be an HTTP URL pointing to a Web server, but in this case it points to a
directory on the local machine, which is a good means of testing the application. The href
attribute must specify the name of this file. .
The information tag has various subelements that provide information about the
application. These are used by the Java Web Start administrative console or equivalent,
which installs the JNLP application and allows the user to run it from the command line,
make short cuts and so on. .
The resources tag serves a similar purpose as the applet tag in an HTML file.
The j2se subelement specifies the version of the j2se that is needed to run the
application, and the jar subelement specifies the JAR file in which the class is
archived. The jar element has an attribute download, which can have the
values eager or lazy that tell the JNLP implementation whether or
not the entire archive needs to be downloaded before the application can be run. .
The application-desc attribute tells the JNLP implementation which class is the
executable class, or entry point, to the JAR file. .
Another useful subelement of the jnlp tag is the security tag, not shown
here. Heres what a security tag looks like:
<security> <all-permissions/> <security/>
You use the security tag when your application is deployed in a signed JAR file. It is
not needed in the preceding example because the local resources are all accessed via the
JNLP services. .
There are a few other tags available, the details of which can be found in the
specification http://java.sun.com/products/javawebstart/download-spec.html. .
Now that the .jnlp is written, you will need to add a hypertext link to it in an
HTML page. This will be its download page. You might have a complex layout with a detailed
introduction to your application, but as long as you have something like:
<a href="filechooser.jnlp">click here</a>
in your HTML file, then you will be able to initiate the installation of the JNLP
application by clicking on the link. Once you have downloaded the application once, you
will be able to configure it by using the administrative console. If you are using Java
Web Start on Windows, then you will be prompted to make a short cut to your application
the second time you use it. This behavior is configurable. .
The source code for this book, downloadable from www.BruceEckel.com, contains
complete working configuration files and an Ant build script to properly compile and build
this project. .
Only two of the JNLP services are covered here, but there are seven services in the
current release. Each is designed for a specific task such as printing, or cutting, and
pasting to the clipboard. An in-depth discussion of these services is beyond the scope of
this chapter. .
Because GUI programming in Java has been an evolving technology with some very
significant changes between Java 1.0/1.1 and the Swing library in Java 2, there have been
some old programming idioms that have seeped through to examples that you might see given
for Swing. In addition, Swing allows you to program in more and better ways than were
allowed by the old models. In this section, some of these issues will be demonstrated by
introducing and examining some programming idioms. .
One of the benefits of the Swing event model is flexibility. You can add and remove
event behavior with single method calls. The following example demonstrates this:
//: c14:DynamicEvents.java
// You can change event behavior dynamically.
// Also shows multiple actions for an event.
// <applet code=DynamicEvents
// width=250 height=400></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
public class DynamicEvents extends JApplet {
private java.util.List list = new ArrayList();
private int i = 0;
private JButton
b1 = new JButton("Button1"),
b2 = new JButton("Button2");
private JTextArea txt = new JTextArea();
class B implements ActionListener {
public void actionPerformed(ActionEvent e) {
txt.append("A button was pressed\n");
}
}
class CountListener implements ActionListener {
private int index;
public CountListener(int i) { index = i; }
public void actionPerformed(ActionEvent e) {
txt.append("Counted Listener " + index + "\n");
}
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
txt.append("Button 1 pressed\n");
ActionListener a = new CountListener(i++);
list.add(a);
b2.addActionListener(a);
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
txt.append("Button2 pressed\n");
int end = list.size() - 1;
if(end >= 0) {
b2.removeActionListener(
(ActionListener)list.get(end));
list.remove(end);
}
}
}
public void init() {
Container cp = getContentPane();
b1.addActionListener(new B());
b1.addActionListener(new B1());
b2.addActionListener(new B());
b2.addActionListener(new B2());
JPanel p = new JPanel();
p.add(b1);
p.add(b2);
cp.add(BorderLayout.NORTH, p);
cp.add(new JScrollPane(txt));
}
public static void main(String[] args) {
Console.run(new DynamicEvents(), 250, 400);
}
} ///:~
The new twists in this example are: .
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). .
In general, youll want to design your classes so that
each one does only one thing. This is particularly important when
user-interface code is concerned, since its easy to tie up what youre
doing with how youre displaying it. This kind of coupling prevents
code reuse. Its much more desirable to separate your business logic from
the GUI. This way, not only can you reuse the business logic more easily, but its
also easier to reuse the GUI. .
Another issue is multitiered 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.[84]
.
The following example shows how easy it is to separate the business logic from the GUI
code:
//: c14:Separation.java
// Separating GUI logic and business objects.
// <applet code=Separation width=250 height=150></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 {
private JTextField
t = new JTextField(15),
mod = new JTextField(15);
private JButton
calc1 = new JButton("Calculation 1"),
calc2 = new JButton("Calculation 2");
private BusinessLogic bl = new BusinessLogic(2);
public 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 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);
}
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 its talking to a
BusinessLogic object (that is, it isnt 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
youre adapting legacy code to work with Java. .
Inner classes, the Swing event model, and the fact that the old AWT event model is
still supported, along with new library features that rely on old-style programming, has
added a new element of confusion to the code design process. Now there are even more
different ways for people to write unpleasant code. .
Except in extenuating circumstances, you can always use the simplest and clearest
approach: Listener classes (typically written as inner classes) to solve your
event-handling needs. This is the form used in most of the examples in this chapter. .
By following
this model you should be able to reduce the statements in your programs that say 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 its much easier to read and maintain. .
It is easy to forget that you are using
threads when you program with Swing. The fact that you dont have to explicitly
create a Thread object means that threading issues can catch you by surprise.
Typically, when you write a Swing program, or any GUI application with a windowed display,
the majority of the application is event driven, and nothing really happens until the user
generates and event by clicking on a GUI component with the mouse, or striking a key. .
Just remember that there is a Swing event dispatching thread, which is always there,
handling all the Swing events in turn. This needs to be considered if you want to
guarantee that your application wont suffer from deadlocking or race conditions. .
This section looks at a couple of issues worth noting when working with threads under
Swing. .
In Chapter 13, I suggested that you think carefully before making a class as an
implementation of Runnable.
Of course, if you must inherit from a class and you want to add threading behavior
to the class, Runnable is the correct solution. The following example exploits this
by making a Runnable JPanel class that paints different colors on itself.
This application is set up to take values from the command line to determine how big the
grid of colors is and how long to sleep( ) between color changes. By playing
with these values, youll discover some interesting and possibly inexplicable
features of threads: .
//: c14:ColorBoxes.java
// Using the Runnable interface.
// <applet code=ColorBoxes width=500 height=400>
// <param name=grid value="12">
// <param name=pause value="50"></applet>
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
class CBox extends JPanel implements Runnable {
private Thread t;
private int pause;
private static final Color[] colors = {
Color.BLACK, Color.BLUE, Color.CYAN,
Color.DARK_GRAY, Color.GRAY, Color.GREEN,
Color.LIGHT_GRAY, Color.MAGENTA,
Color.ORANGE, Color.PINK, Color.RED,
Color.WHITE, Color.YELLOW
};
private static Random rand = new Random();
private static final Color newColor() {
return colors[rand.nextInt(colors.length)];
}
private Color cColor = newColor();
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(cColor);
Dimension s = getSize();
g.fillRect(0, 0, s.width, s.height);
}
public CBox(int pause) {
this.pause = pause;
t = new Thread(this);
t.start();
}
public void run() {
while(true) {
cColor = newColor();
repaint();
try {
t.sleep(pause);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class ColorBoxes extends JApplet {
private boolean isApplet = true;
private int grid = 12;
private int pause = 50;
public void init() {
// Get parameters from Web page:
if(isApplet) {
String gsize = getParameter("grid");
if(gsize != null)
grid = Integer.parseInt(gsize);
String pse = getParameter("pause");
if(pse != null)
pause = Integer.parseInt(pse);
}
Container cp = getContentPane();
cp.setLayout(new GridLayout(grid, grid));
for(int i = 0; i < grid * grid; i++)
cp.add(new CBox(pause));
}
public static void main(String[] args) {
ColorBoxes applet = new ColorBoxes();
applet.isApplet = false;
if(args.length > 0)
applet.grid = Integer.parseInt(args[0]);
if(args.length > 1)
applet.pause = Integer.parseInt(args[1]);
Console.run(applet, 500, 400);
}
} ///:~
ColorBoxes is the usual applet/application with an init( ) that sets
up the GUI. This configures a GridLayout so that it has grid
cells in each dimension. Then it adds the appropriate number of CBox objects to
fill the grid, passing the pause value to each one. In main( ) you can
see how pause and grid have default values that can be changed if you pass
in command-line arguments, or by using applet parameters. .
CBox is where all the work takes place. This is inherited from JPanel and it implements the Runnable interface so that each JPanel
can also be a Thread. Remember that when you implement Runnable, you
dont make a Thread object, just a class that has a run( ) method.
Thus, you must explicitly create a Thread object and hand the Runnable
object to the constructor, then call start( ) (this happens in the
constructor). In CBox this thread is called t. .
Notice the array colors, which is an enumeration of all the colors in class Color.
This is used in newColor( ) to produce a randomly selected color. The current
cell color is cColor. .
paintComponent( ) is quite simple; it just sets the color to cColor
and fills the entire JPanel with that color. .
In run( ), you see the infinite loop that sets the cColor to a new
random color and then calls repaint( ) to show it. Then the thread goes to sleep( )
for the amount of time specified on the command line. .
Precisely because this design is flexible and threading is tied to each JPanel
element, you can experiment by making as many threads as you want. (In reality, there is a
restriction imposed by the number of threads your JVM can comfortably handle.) .
This program also makes an interesting benchmark, since it can and has shown dramatic
performance and behavioral differences between one JVM threading implementation and
another. .
When you make changes to any Swing component properties from the main method of
your class or in a separate thread, be aware that the event dispatching thread might be
vying for the same resources. [85] .
The following program shows how you can get an unexpected result by not paying
attention to the event dispatching thread:
//: c14:EventThreadFrame.java
// Race Conditions using Swing Components.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.Console;
public class EventThreadFrame extends JFrame {
private JTextField statusField =
new JTextField("Initial Value");
public EventThreadFrame() {
Container cp = getContentPane();
cp.add(statusField, BorderLayout.NORTH);
addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
try { // Simulate initialization overhead
Thread.sleep(2000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
statusField.setText("Initialization complete");
}
});
}
public static void main (String[] args) {
EventThreadFrame etf = new EventThreadFrame();
Console.run(etf, 150, 60);
etf.statusField.setText("Application ready");
System.out.println("Done");
}
} ///:~
It is easy to see what is supposed to happen. In the main method, a new EventThreadFrame
class is created and run using the Console.run( ) method. After the frame has
been created and run, the value of the text field is set to Application ready,
and then, just before exiting main( ), Done is sent to the
console. .
When the frame is created, the text field is constructed with the value Initial
Value in the constructor of the frame, and an event listener is added that listens
for the opening of the window. This event will be received by the JFrame as soon as
the setVisible(true) method has been called (by Console.run( )) and is
the right place to do any initialization that affects the view of the window. In this
example, a call to sleep( ) simulates some initialization code that might take
a couple of seconds. After this is done, the value of the text box is set to
Initialization complete. .
You would expect that the text field would display Initial Value followed
by Initialization complete and then Application Ready. Next the
word Done should appear on the command prompt. What really happens is that the
setText( ) method on the TextField is called by the main thread before
the EventThreadFrame has had a chance to process its events. This means that the
string Application ready might actually appear before Initialization
complete. In reality, things might not even appear in this order. Depending on the
speed of your system, the Swing event dispatching thread may already be busy handling the windowOpened
event, so you wont see the text field value until after that event, but by then the
text will have been changed to Initialization Complete. Since the text field
was set to this value last, the message Application ready is lost. To makes
things worse, the word Done appears on the command prompt before anything else
happens at all! .
This undesirable and somewhat unpredictable effect is caused by the simple fact that
there are two threads that need some sort of synchronization. It shows that you can
sometimes get into trouble with threads and Swing. To solve this problem, you must ensure
that Swing component properties are only ever updated by the event dispatch thread. .
This is easier than it sounds, using one of Swings two mechanisms, SwingUtilities.invokeLater( ) and SwingUtilities.invokeandWait( ).
They do most of the work, which means that you dont have to do too much complicated
synchronization or thread programming. .
They both take runnable objects as parameters and drive the run( ) with the
Swing event processing thread, after it has processed any pending events in the queue. .
//: c14:InvokeLaterFrame.java
// Eliminating race Conditions using Swing Components.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.Console;
public class InvokeLaterFrame extends JFrame {
private JTextField statusField =
new JTextField("Initial Value");
public InvokeLaterFrame() {
Container cp = getContentPane();
cp.add(statusField, BorderLayout.NORTH);
addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
try { // Simulate initialization overhead
Thread.sleep(2000);
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
statusField.setText("Initialization complete");
}
});
}
public static void main(String[] args) {
final InvokeLaterFrame ilf = new InvokeLaterFrame();
Console.run(ilf, 150, 60);
// Use invokeAndWait() to synchronize output to prompt:
// SwingUtilities.invokeAndWait(new Runnable() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
ilf.statusField.setText("Application ready");
}
});
System.out.println("Done");
}
} ///:~
A Runnable anonymous inner class is passed to SwingUtilities.invokeLater( ),
which calls the setText( ) method of the text field. This queues the runnable
object as an event so that it is the event dispatching thread that calls the setText( )
method after first processing any pending events. This means that the windowOpening
event will be processed before the text field displays Application ready,
which is the intended result. .
invokeLater( ) is asynchronous, so it returns right away. This can be
useful because it doesnt block, so your code runs smoothly. However, it doesnt
solve the problem with the Done string, which is still printed to the command
prompt before anything else happens. .
The solution to this problem is to use invokeAndWait( ) instead of invokeLater( )
to set the text field value to Application Ready. This method is synchronous,
which means that it will block until the event has been processed before returning. The System.out.println(Done)
statement will only be reached after the text field value has been set, so it will be the
last statement to be executed. This gives us completely predictable and correct behavior. .
Using invokeAndWait( ) provides one of the necessary conditions for
deadlock, so make sure that you are careful about controlling shared resources if you are
using invokeAndWait( ), especially if you are calling it from more than one
thread. .
You will probably use invokeLater( ) more often than invokeAndWait( ),
but remember that if you set the properties of a Swing component any time after
initialization, it should be done using one of these methods. .
So far in this book youve 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 youre putting together an application, what you really
want is components that do exactly what you need. Youd like to drop these parts into
your design like the chips an electronic engineer puts on a circuit board. It seems, too,
that there should be some way to accelerate this modular assembly style of
programming. .
Visual programming first became
successfulvery successfulwith Microsofts Visual BASIC (VB),
followed by a second-generation design in Borlands Delphi (the primary inspiration
for the JavaBeans 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 its color, the
text thats on it, the database its 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 youre probably used to the idea that an object is more than
characteristics; its also a set of behaviors. At design time, the behaviors of a
visual component are partially represented by events,
meaning Heres 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. .
Heres the critical part: The
application builder tool uses reflection to dynamically interrogate the component and 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 them (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 thats 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 applicationcertainly the user interface, but
often other portions of the application as well. .
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 was much easier to create a visual component. However,
Java has brought the creation of visual components to its most advanced state with
JavaBeans, because a Bean is just a class. You dont 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 JDK documentation, this naming convention is mistakenly
termed a design pattern. This is unfortunate, since design patterns (see Thinking
in Patterns (with Java) at www.BruceEckel.com) are challenging enough without
this sort of confusion. Its not a design pattern, its just a naming convention
and its fairly simple:
Point 1 answers a question about something you might have noticed when looking at older
code versus newer code: 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 JavaBean. .
We can use these guidelines to create a simple Bean:
//: frogbean:Frog.java
// A trivial JavaBean.
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 its 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 case change 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 identifier for internal variables
(or, indeed, to even have any internal variables 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 its a public method, not because it conforms to any
naming scheme. .
One of the most critical parts of the JavaBean scheme occurs when you drag a Bean off a
palette and plop it onto a form. The application builder tool must be able to create the
Bean (which it can do if theres a default constructor) and then, without access to
the Beans source code, extract all the necessary information to create the property
sheet and event handlers. .
Part of the solution is already evident from Chapter 10: Java reflection discovers all the methods of an unknown class. This
is perfect for solving the JavaBean 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 was to support JavaBeans (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 tool,
not only to make Beans simpler to use, but also to provide a standard gateway to the
creation of more complex Beans. This tool is the Introspector class, and the most important method in this
class is 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 wont care about any of this; youll probably get most of your
Beans off the shelf from vendors, and you dont need to know all the magic
thats going on underneath. Youll simply drag your Beans onto your form, then
configure their properties and write handlers for the events youre interested in.
However, its an interesting and educational exercise to use the Introspector
to display information about a Bean, so heres a tool that does it:
//: c14:BeanDumper.java
// Introspecting a Bean.
import java.beans.*;
import java.lang.reflect.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import com.bruceeckel.swing.*;
public class BeanDumper extends JFrame {
private JTextField query = new JTextField(20);
private JTextArea results = new JTextArea();
public void print(String s) { results.append(s + "\n"); }
public void dump(Class bean) {
results.setText("");
BeanInfo bi = null;
try {
bi = Introspector.getBeanInfo(bean, Object.class);
} catch(IntrospectionException e) {
print("Couldn't introspect " + bean.getName());
return;
}
PropertyDescriptor[] properties =
bi.getPropertyDescriptors();
for(int i = 0; i < properties.length; i++) {
Class p = properties[i].getPropertyType();
if(p == null) continue;
print("Property type:\n " + p.getName() +
"Property name:\n " + properties[i].getName());
Method readMethod = properties[i].getReadMethod();
if(readMethod != null)
print("Read method:\n " + readMethod);
Method writeMethod = properties[i].getWriteMethod();
if(writeMethod != null)
print("Write method:\n " + writeMethod);
print("====================");
}
print("Public methods:");
MethodDescriptor[] methods = bi.getMethodDescriptors();
for(int i = 0; i < methods.length; i++)
print(methods[i].getMethod().toString());
print("======================");
print("Event support:");
EventSetDescriptor[] events =
bi.getEventSetDescriptors();
for(int i = 0; i < events.length; i++) {
print("Listener type:\n " +
events[i].getListenerType().getName());
Method[] lm = events[i].getListenerMethods();
for(int j = 0; j < lm.length; j++)
print("Listener method:\n " + lm[j].getName());
MethodDescriptor[] lmd =
events[i].getListenerMethodDescriptors();
for(int j = 0; j < lmd.length; j++)
print("Method descriptor:\n "
+ lmd[j].getMethod());
Method addListener= events[i].getAddListenerMethod();
print("Add Listener Method:\n " + addListener);
Method removeListener =
events[i].getRemoveListenerMethod();
print("Remove Listener Method:\n "+ removeListener);
print("====================");
}
}
class Dumper implements ActionListener {
public void actionPerformed(ActionEvent e) {
String name = query.getText();
Class c = null;
try {
c = Class.forName(name);
} catch(ClassNotFoundException ex) {
results.setText("Couldn't find " + name);
return;
}
dump(c);
}
}
public BeanDumper() {
Container cp = getContentPane();
JPanel p = new JPanel();
p.setLayout(new FlowLayout());
p.add(new JLabel("Qualified bean name:"));
p.add(query);
cp.add(BorderLayout.NORTH, p);
cp.add(new JScrollPane(results));
Dumper dmpr = new Dumper();
query.addActionListener(dmpr);
query.setText("frogbean.Frog");
// Force evaluation
dmpr.actionPerformed(new ActionEvent(dmpr, 0, ""));
}
public static void main(String[] args) {
Console.run(new BeanDumper(), 600, 500);
}
} ///:~
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( ),
youll 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 were 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. .
Upon startup, the program forces the evaluation of 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 doesnt
occur is when the property name begins with more than one capital letter in a row.) And
remember that the method names youre 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 youre 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 dont have any other information except the object (again,
a feature of reflection). .
This next example is slightly more sophisticated, albeit frivolous. Its a JPanel
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 {
private int xm, ym;
private int cSize = 20; // Circle size
private String text = "Bang!";
private int fontSize = 48;
private Color tColor = Color.RED;
private 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);
}
} ///:~
The first thing youll notice is that BangBean implements the Serializable interface. This means
that the application builder tool can pickle all the information for the BangBean
by 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 youll usually
do with a Beanallow access only through methods, usually using the
property scheme. .
When you look at the signature for addActionListener( ), youll 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, youll use multicast
events so that many listeners can be notified of an event. However, that runs into
threading issues, so it will be revisited under the heading JavaBeans and
synchronization later in this chapter. In the meantime, a unicast event sidesteps
the problem. .
When you click 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 thats on the canvas, as youll see). .
Here is the BangBeanTest class to test the Bean:
//: c14:BangBeanTest.java
import bangbean.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import com.bruceeckel.swing.*;
public class BangBeanTest extends JFrame {
private JTextField txt = new JTextField(20);
// During testing, report actions:
class BBL implements ActionListener {
private int count = 0;
public void actionPerformed(ActionEvent e) {
txt.setText("BangBean action "+ count++);
}
}
public BangBeanTest() {
BangBean bb = new BangBean();
try {
bb.addActionListener(new BBL());
} catch(TooManyListenersException e) {
txt.setText("Too many listeners");
}
Container cp = getContentPane();
cp.add(bb);
cp.add(BorderLayout.SOUTH, txt);
}
public static void main(String[] args) {
Console.run(new BangBeanTest(), 400, 500);
}
} ///:~
When a Bean is in a development environment, this class will not be used, but its
helpful to provide a rapid testing method for each of your Beans. BangBeanTest
places a BangBean within the applet, attaching a simple ActionListener to
the BangBean to print an event count to the JTextField 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, youll notice that there are many more
properties and actions than are evident from the preceding code. Thats because BangBean
is inherited from JPanel, and JPanel is also Bean, so youre seeing its
properties and events as well. .
Whenever you create a Bean, you must assume that it will run in a multithreaded
environment. This means that:
The first point is fairly easy to deal with, but the second point requires a little
more thought. The previous version of BangBean.java ducked out of the
multithreading question by ignoring the synchronized keyword and making the event
unicast. Here is a modified version that works in a multithreaded environment and uses
multicasting for events:
//: c14:BangBean2.java
// You should write your Beans this way so they
// can run in a multithreaded environment.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import com.bruceeckel.swing.*;
public class BangBean2 extends JPanel
implements Serializable {
private int xm, ym;
private int cSize = 20; // Circle size
private String text = "Bang!";
private int fontSize = 48;
private Color tColor = Color.RED;
private ArrayList actionListeners = new ArrayList();
public BangBean2() {
addMouseListener(new ML());
addMouseMotionListener(new MM());
}
public synchronized int getCircleSize() { return cSize; }
public synchronized void setCircleSize(int newSize) {
cSize = newSize;
}
public synchronized String getBangText() { return text; }
public synchronized void setBangText(String newText) {
text = newText;
}
public synchronized int getFontSize(){ return fontSize; }
public synchronized void setFontSize(int newSize) {
fontSize = newSize;
}
public synchronized Color getTextColor(){ return tColor;}
public synchronized 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 multicast listener, which is more typically
// used than the unicast approach taken in BangBean.java:
public synchronized void
addActionListener(ActionListener l) {
actionListeners.add(l);
}
public synchronized void
removeActionListener(ActionListener l) {
actionListeners.remove(l);
}
// Notice this isn't synchronized:
public void notifyListeners() {
ActionEvent a = new ActionEvent(BangBean2.this,
ActionEvent.ACTION_PERFORMED, null);
ArrayList lv = null;
// Make a shallow copy of the List in case
// someone adds a listener while we're
// calling listeners:
synchronized(this) {
lv = (ArrayList)actionListeners.clone();
}
// Call all the listener methods:
for(int i = 0; i < lv.size(); i++)
((ActionListener)lv.get(i)).actionPerformed(a);
}
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();
notifyListeners();
}
}
class MM extends MouseMotionAdapter {
public void mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public static void main(String[] args) {
BangBean2 bb = new BangBean2();
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("ActionEvent" + e);
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("BangBean2 action");
}
});
bb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("More action");
}
});
Console.run(bb, 300, 300);
}
} ///:~
Adding synchronized to the methods is an easy change. However, notice in addActionListener( ) and removeActionListener( )
that the ActionListeners are now added to and removed from an ArrayList, so
you can have as many as you want. .
You can see that the method notifyListeners( ) is not
synchronized. It can be called from more than one thread at a time. Its also
possible for addActionListener( ) or removeActionListener( ) to be
called in the middle of a call to notifyListeners( ), which is a problem
because it traverses the ArrayList actionListeners. To alleviate the problem, the ArrayList
is cloned inside a synchronized clause, and the clone is traversed (see Appendix A
for details of cloning). This way, the original ArrayList can be manipulated
without impact on notifyListeners( ). .
The paintComponent( ) method is also not synchronized. Deciding whether to
synchronize overridden methods is not as clear as when youre just adding your own
methods. In this example, it turns out that paintComponent( ) seems to work OK
whether its synchronized or not. But the issues you must consider are:
The test code in main( ) has been modified from that seen in BangBeanTest
to demonstrate the multicast ability of BangBean2 by adding extra listeners. .
Before you can bring a JavaBean into a Bean-enabled visual builder
tool, it must be put into the standard Bean container, which is a JAR 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 Its 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, youll see
its in package bangbean (and thus in a subdirectory called
bangbean thats 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 youve 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 youll 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 fileyou might not choose to
include the source with your own Beans). In addition, if you turn around and unpack the
JAR file youve just created, youll discover that your manifest file isnt
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, youll 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 dont 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 youll 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 JavaBeans in their Bean
Builder. (Download from java.sun.com/beans.) You place a Bean into the Bean
Builder by simply copying the JAR file into the correct subdirectory. .
You can see how remarkably simple it is to make a Bean, but you arent limited to
what youve seen here. The JavaBeans architecture 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 java.sun.com/beans.
.
One place where you can add sophistication is with properties. The examples youve
seen here have shown only single properties, but its 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 that 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 by 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: .
There are a number of books about JavaBeans; for example, JavaBeans by Elliotte
Rusty Harold (IDG, 1998). .
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 the native application development tools available on a
particular platform. .
When Java 1.1 introduced the new event model and JavaBeans, the stage was setnow
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 JavaBeans
clearly shows strong consideration for ease of programming and maintainable code
(something that was not evident in the 1.0 AWT). But it wasnt until 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 thats missing is the application builder tool, and this
is where the real revolution lies. Microsofts Visual BASIC and Visual C++ require
Microsofts application builder tools, as does Borlands 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, 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 JavaBeans. This means a leveled
playing field; if a better application builder tool comes along, youre not tied to
the one youve 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 marketplace can generate only positive results for
the productivity of the programmer. .
This chapter 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 youve seen so
far will probably suffice for a good portion of your UI design needs. However,
theres a lot more to Swing; its intended to be a fully powered UI design tool
kit. Theres probably a way to accomplish just about everything you can imagine. .
If you dont see what you need here, delve into the JDK documentation from Sun and
search the Web, and if thats not enough, then find a dedicated Swing book. A good
place to start is The JFC Swing Tutorial, by Walrath & Campione (Addison
Wesley, 1999). .
Solutions to selected exercises can be found in the electronic document The Thinking
in Java Annotated Solution Guide, available for a small fee from www.BruceEckel.com.
[74] A variation on
this is called the principle of least astonishment, which essentially says:
dont surprise the user.
[75] Note that IBM
created a new open-source GUI library for their Eclipse editor (www.Eclipse.org),
which you may want to consider as an alternative to Swing.
[76] Application
frameworks are an example of the design pattern called the Template Method.
[77] It is assumed
that the reader is familiar with the basics of HTML. Its not too hard to figure out,
and there are lots of books and resources.
[78] In my opinion.
And after you learn about Swing, you wont want to waste your time on the pre-Swing
stuff.
[79] As described
earlier, Frame was already taken by the AWT, so Swing uses JFrame.
[80] This will make
sense after youve read further in this chapter. First, make the reference JApplet
a static member of the class (instead of a local variable of main( )),
and then call applet.stop( ) and applet.destroy( ) inside WindowAdapter.windowClosing( )
before you call System.exit( ).
[81] 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.
[82] In Java 1.0/1.1
you could not usefully inherit from the button object. This was only one of
numerous fundamental design flaws.
[83] This section and
the next were created by Jeremy Meyer.
[84] This concept is
more fully explored in Thinking in Enterprise Java, at www.BruceEckel.com.
[85] This section was
created by Jeremy Meyer.
|
|
|