|
| - |
The
new AWT
In
Java
1.1 a dramatic change has been accomplished in the creation of the new AWT.
Most of this change revolves around the new
event model used in Java 1.1: as bad, awkward, and non-object-oriented as the
old event model was, the new event model is possibly the most elegant I have
seen. It’s difficult to understand how such a bad design (the old AWT)
and such a good one (the new event model) could come out of the same group.
This new way of thinking about events seems to drop so easily into your mind
that the issue no longer becomes an impediment; instead, it’s a tool that
helps you design the system. It’s also essential for Java Beans,
described later in the chapter.
Instead
of the non-object-oriented cascaded
if
statements in the old AWT, the new approach designates objects as
“sources” and “listeners” of events. As you will see,
the use of inner classes is integral to the object-oriented nature of the new
event model. In addition, events are now represented in a class hierarchy
instead of a single class, and you can create your own event types.
You’ll
also find, if you’ve programmed with the old AWT, that Java 1.1 has made
a number of what might seem like gratuitous name changes. For example,
setSize( )
replaces
resize( ).
This will make sense when you learn about Java Beans, because Beans use a
particular naming convention. The names had to be modified to make the standard
AWT components into Beans.
Java
1.1 continues to support the old AWT to ensure backward compatibility with
existing programs. Without fully admitting disaster, the online documents for
Java 1.1 list all the problems involved with programming the old AWT and
describe how those problems are addressed in the new AWT.
Clipboard
operations are supported in 1.1, although drag-and-drop “will be
supported in a future release.” You can access the desktop color scheme
so your Java program can fit in with the rest of the desktop. Pop-up menus are
available, and there are some improvements for graphics and images. Mouseless
operation is supported. There is a simple API for printing and simplified
support for scrolling.
The
new event model
In
the new event model a component can initiate (“fire”) an event.
Each type of event is represented by a distinct class. When an event is fired,
it is received by one or more “listeners,” which act on that event.
Thus, the source of an event and the place where the event is handled can be
separate.
Each
event
listener is an object of a class that implements a particular type of listener
interface.
So as a programmer, all you do is create a listener object and register it with
the component that’s firing the event. This registration is performed by
calling a
addXXXListener( )
method in the event-firing component, in which
XXX
represents the type of event listened for. You can easily know what types of
events can be handled by noticing the names of the addListener
methods, and if you try to listen for the wrong events you’ll find out
your mistake at compile time. Java Beans also uses the names of the addListener
methods to determine what a Bean can do.
All
of your event logic, then, will go inside a listener class. When you create a
listener class, the sole restriction is that it must implement the appropriate
interface. You can create a global listener class, but this is a situation in
which inner
classes tend to be quite useful, not only because they provide a logical
grouping of your listener classes inside the UI or business logic classes they
are serving, but because (as you shall see later) the fact that an inner class
object keeps a handle to its parent object provides a nice way to call across
class and subsystem boundaries.
A
simple example will make this clear. Consider the
Button2.java
example from earlier in this chapter.
//: Button2New.java
// Capturing button presses
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2New extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
add(b1);
add(b2);
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
getAppletContext().showStatus("Button 1");
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
getAppletContext().showStatus("Button 2");
}
}
/* The old way:
public boolean action(Event evt, Object arg) {
if(evt.target.equals(b1))
getAppletContext().showStatus("Button 1");
else if(evt.target.equals(b2))
getAppletContext().showStatus("Button 2");
// Let the base class handle it:
else
return super.action(evt, arg);
return true; // We've handled it here
}
*/
} ///:~ So
you can compare the two approaches, the old code is left in as a comment. In
init( ),
the only change is the addition of the two lines:
b1.addActionListener(new B1());
b2.addActionListener(new B2()); addActionListener( )
tells a button which object to activate when the button is pressed. The classes
B1
and
B2
are inner classes that implement the
interface
ActionListener
.
This interface contains a single method
actionPerformed( )
(meaning “This is the action that will be performed when the event is
fired”). Note that
actionPerformed( )
does not take a generic event, but rather a specific type of event,
ActionEvent.
So you don’t need to bother testing and downcasting the argument if you
want to extract specific
ActionEvent
information.
One
of the nicest things about
actionPerformed( )
is how simple it is. It’s just a method that gets called. Compare it to
the old
action( )
method, in which you must figure out what happened and act appropriately, and
also worry about calling the base class version of
action( )
and return a value to indicate whether it’s been handled. With the new
event model you know that all the event-detection logic is taken care of so you
don’t have to figure that out; you just say what happens and you’re
done. If you’re don’t already prefer this approach over the old
one, you will soon.
Event
and listener types
All
the AWT components have been changed to include
addXXXListener( )
and
removeXXXListener( )
methods so that the appropriate types of listeners can be added and removed
from each component. You’ll notice that the “
XXX”
in each case also represents the argument for the method, for example,
addFooListener(FooListener
fl)
.
The following table includes the associated events, listeners, methods, and the
components that support those particular events by providing the
addXXXListener( )
and
removeXXXListener( )
methods.
Event,
listener interface and add- and remove-methods
|
Components
supporting this event
|
ActionEventActionListener addActionListener( ) removeActionListener( )
|
Button,
List,
TextField, MenuItem,
and its derivatives including
CheckboxMenuItem,
Menu,
and
PopupMenu
|
AdjustmentEventAdjustmentListener addAdjustmentListener( ) removeAdjustmentListener( )
|
ScrollbarAnything
you create that implements the
Adjustable
interface
|
ComponentEventComponentListener addComponentListener( ) removeComponentListener( )
|
Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
|
ContainerEventContainerListener addContainerListener( ) removeContainerListener( )
|
Container
and its derivatives, including
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
and
Frame
|
FocusEventFocusListener addFocusListener( ) removeFocusListener( )
|
Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
Label,
List,
Scrollbar,
TextArea,
and
TextField
|
KeyEventKeyListener addKeyListener( ) removeKeyListener( )
|
Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
|
MouseEvent
(for both clicks and motion)
MouseListener addMouseListener( ) removeMouseListener( )
|
Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
|
MouseEvent[58]
(for
both clicks and motion)
MouseMotionListener addMouseMotionListener( ) removeMouseMotionListener( )
|
Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
|
WindowEventWindowListener addWindowListener( ) removeWindowListener( )
|
Window
and its derivatives, including
Dialog,
FileDialog,
and
Frame
|
ItemEventItemListener addItemListener( ) removeItemListener( )
|
Checkbox,
CheckboxMenuItem,
Choice,
List,
and anything that implements the
ItemSelectable
interface
|
TextEventTextListener addTextListener( ) removeTextListener( )
|
Anything
derived from
TextComponent,
including
TextArea
and
TextField
|
You
can see that each type of component supports only certain types of events.
It’s helpful to see the events supported by each component, as shown in
the following table:
|
|
Events
supported by this component
|
|
|
|
|
|
ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ActionEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
|
|
|
ItemEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
|
|
|
ItemEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
|
|
|
ActionEvent,
FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent
|
|
|
|
|
|
|
|
|
ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
|
|
|
AdjustmentEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
TextEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
TextEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ActionEvent,
TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
|
|
ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
|
Once
you know which events a particular component supports, you don’t need to
look anything up to react to that event. You simply:
- Take
the name of the event class and remove the word “
Event.”
Add the word “
Listener”
to what remains. This is the listener interface you need to implement in your
inner class.
- Implement
the interface above and write out the methods for the events you want to
capture. For example, you might be looking for mouse movements, so you write
code for the
mouseMoved( )
method of the
MouseMotionListener
interface. (You must implement the other methods, of course, but there’s
a shortcut for that which you’ll see soon.)
- Create
an object of the listener class in step 2. Register it with your component with
the method produced by prefixing “
add”
to your listener name. For example,
addMouseMotionListener( ).
To
finish what you need to know, here are the listener interfaces:
Listener
interface
w/
adapter
|
|
|
|
actionPerformed(ActionEvent)
|
|
|
adjustmentValueChanged(
AdjustmentEvent)
|
ComponentListenerComponentAdapter
|
componentHidden(ComponentEvent)componentShown(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent)
|
ContainerListenerContainerAdapter
|
componentAdded(ContainerEvent)componentRemoved(ContainerEvent)
|
FocusListenerFocusAdapter
|
focusGained(FocusEvent)focusLost(FocusEvent)
|
|
|
keyPressed(KeyEvent)keyReleased(KeyEvent) keyTyped(KeyEvent)
|
MouseListenerMouseAdapter
|
mouseClicked(MouseEvent)mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent)
|
MouseMotionListenerMouseMotionAdapter
|
mouseDragged(MouseEvent)mouseMoved(MouseEvent)
|
WindowListenerWindowAdapter
|
windowOpened(WindowEvent)windowClosing(WindowEvent)
windowClosed(WindowEvent)
windowActivated(WindowEvent)
windowDeactivated(WindowEvent)
windowIconified(WindowEvent)
windowDeiconified(WindowEvent)
|
|
|
itemStateChanged(ItemEvent)
|
|
|
textValueChanged(TextEvent)
|
Using
listener adapters for simplicity
In
the table above, you can see that some listener interfaces have only one
method. These are trivial to implement since you’ll implement them only
when you want to write that particular method. However, the listener interfaces
that have multiple methods could be less pleasant to use. For example,
something you must always do when creating an application is provide a
WindowListener
to the
Frame
so that when you get the
windowClosing( )
event you can call
System.exit(0)
to exit the application. But since
WindowListener
is an
interface,
you must implement all of the other methods even if they don’t do
anything. This can be annoying.
To
solve the problem, each of the listener interfaces that have more than one
method are provided with
adapters,
the names of which you can see in the table above. Each adapter provides
default methods for each of the interface methods. (Alas,
WindowAdapter
does
not
have a default
windowClosing( )
that calls
System.exit(0).)
Then all you need to do is inherit from the adapter and override only the
methods you need to change. For example, the typical
WindowListener
you’ll use looks like this:
class MyWindowListener extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}The
whole point of the adapters is to make the creation of listener classes easy.
There
is a downside to adapters, however, in the form of a pitfall. Suppose you write
a
WindowAdapter
like the one above:
class MyWindowListener extends WindowAdapter {
public void WindowClosing(WindowEvent e) {
System.exit(0);
}
}This
doesn’t work, but it will drive you crazy trying to figure out why, since
everything will compile and run fine – except that closing the window
won’t exit the program. Can you see the problem? It’s in the name
of the method:
WindowClosing( )
instead of
windowClosing( ).
A simple slip in capitalization results in the addition of a completely new
method. However, this is not the method that’s called when the window is
closing, so you don’t get the desired results.
Making
windows and applets
with
the Java 1.1 AWT
Often
you’ll want to be able to create a class that can be invoked as either a
window or an applet. To accomplish this, you simply add a
main( )
to your applet that builds an instance of the applet inside a
Frame.
As a simple example, let’s look at
Button2New.java
modified to work as both an application and an applet:
//: Button2NewB.java
// An application and an applet
import java.awt.*;
import java.awt.event.*; // Must add this
import java.applet.*;
public class Button2NewB extends Applet {
Button
b1 = new Button("Button 1"),
b2 = new Button("Button 2");
TextField t = new TextField(20);
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
add(b1);
add(b2);
add(t);
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Button 1");
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Button 2");
}
}
// To close the application:
static class WL extends WindowAdapter {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}
// A main() for the application:
public static void main(String[] args) {
Button2NewB applet = new Button2NewB();
Frame aFrame = new Frame("Button2NewB");
aFrame.addWindowListener(new WL());
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ The
inner class
WL
and the
main( )
are the only two elements added to the applet, and the rest of the applet is
untouched. In fact, you can usually copy and paste the
WL
class and
main( )
into your own applets with little modification. The
WL
class
is
static
so
it can be easily created in
main( ).
(Remember
that an inner class normally needs an outer class handle when it’s
created. Making it
static
eliminates this need.)
You
can see that in
main( ),
the applet is explicitly initialized and started since in this case the browser
isn’t available to do it for you. Of course, this doesn’t provide
the full behavior of the browser, which also calls
stop( )
and
destroy( ),
but for most situations it’s acceptable. If it’s a problem, you can:
- Make
the handle
applet
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( ).
This
is one of the changes in the Java 1.1
AWT. The
show( )
method is deprecated and
setVisible(true)
replaces it. These sorts of seemingly capricious changes will make more sense
when you learn about Java Beans later in the chapter.
This
example is also modified to use a
TextField
rather than printing to the console or to the browser status line. One
restriction in making a program that’s both an applet and an application
is that you must choose input and output forms that work for both situations.
There’s
another small new feature of the Java 1.1
AWT shown here. You no longer need to use the error-prone approach of specifying BorderLayout
positions
using a
String.
When adding an element to a
BorderLayout
in Java 1.1,
you can say:
aFrame.add(applet,
BorderLayout.CENTER);
You
name the location with one of the
BorderLayout
constants, which can then be checked at compile-time (rather than just quietly
doing the wrong thing, as with the old form). This is a definite improvement,
and will be used throughout the rest of the book.
Making
the window listener
an
anonymous class
Any
of the listener classes could be implemented as anonymous
classes, but there’s always a chance that you might want to use their
functionality elsewhere. However, the window listener is used here only to
close the application’s window so you can safely make it an anonymous
class. Then, in
main( ),
the line:
aFrame.addWindowListener(new
WL());
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
}); This
has the advantage that it doesn’t require yet another class name. You
must decide for yourself whether it makes the code easier to understand or more
difficult. However, for the remainder of the book an anonymous inner class will
usually be used for the window listener.
Packaging
the applet into a JAR file
An
important JAR
use is to optimize applet loading. In Java 1.0, people tended to try to cram
all their code into a single
Applet
class so the client would need only a single server hit to download the applet
code. Not only did this result in messy, hard to read (and maintain) programs,
but the
.class
file was still uncompressed so downloading wasn’t as fast as it could
have been.
JAR
files change all of that by compressing all of your
.class
files into a single file that is downloaded by the browser. Now you don’t
need to create an ugly design to minimize the number of classes you create, and
the user will get a much faster download time.
Consider
the example above. It looks like
Button2NewB
is a single class, but in fact it contains three inner classes, so that’s
four in all. Once you’ve compiled the program, you package it into a JAR
file with the line:
jar
cf Button2NewB.jar *.class
This
assumes that the only
.class
files in the current directory are the ones from
Button2NewB.java
(otherwise you’ll get extra baggage).
Now
you can create an HTML page with the new archive
tag to indicate the name of the JAR file, like this:
<head><title>Button2NewB Example Applet
</title></head>
<body>
<applet code="Button2NewB.class"
archive="Button2NewB.jar"
width=200 height=150>
</applet>
</body>Everything
else about applet tags in HTML files remains the same.
Revisiting
the earlier examples
To
see a number of examples using the new event model and to study the way a
program can be converted from the old to the new event model, the following
examples revisit many of the issues demonstrated in the first part of this
chapter using the old event model. In addition, each program is now both an
applet and an application so you can run it with or without a browser.
Text
fields
This
is similar to
TextField1.java,
but it adds significant extra behavior:
//: TextNew.java
// Text fields with Java 1.1 events
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextNew extends Applet {
Button
b1 = new Button("Get Text"),
b2 = new Button("Set Text");
TextField
t1 = new TextField(30),
t2 = new TextField(30),
t3 = new TextField(30);
String s = new String();
public void init() {
b1.addActionListener(new B1());
b2.addActionListener(new B2());
t1.addTextListener(new T1());
t1.addActionListener(new T1A());
t1.addKeyListener(new T1K());
add(b1);
add(b2);
add(t1);
add(t2);
add(t3);
}
class T1 implements TextListener {
public void textValueChanged(TextEvent e) {
t2.setText(t1.getText());
}
}
class T1A implements ActionListener {
private int count = 0;
public void actionPerformed(ActionEvent e) {
t3.setText("t1 Action Event " + count++);
}
}
class T1K extends KeyAdapter {
public void keyTyped(KeyEvent e) {
String ts = t1.getText();
if(e.getKeyChar() ==
KeyEvent.VK_BACK_SPACE) {
// Ensure it's not empty:
if( ts.length() > 0) {
ts = ts.substring(0, ts.length() - 1);
t1.setText(ts);
}
}
else
t1.setText(
t1.getText() +
Character.toUpperCase(
e.getKeyChar()));
t1.setCaretPosition(
t1.getText().length());
// Stop regular character from appearing:
e.consume();
}
}
class B1 implements ActionListener {
public void actionPerformed(ActionEvent e) {
s = t1.getSelectedText();
if(s.length() == 0) s = t1.getText();
t1.setEditable(true);
}
}
class B2 implements ActionListener {
public void actionPerformed(ActionEvent e) {
t1.setText("Inserted by Button 2: " + s);
t1.setEditable(false);
}
}
public static void main(String[] args) {
TextNew applet = new TextNew();
Frame aFrame = new Frame("TextNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ The
TextField
t3
is included as a place to report when the action listener for the
TextField
t1
is fired. You’ll see that the action listener for a
TextField
is fired only when you press the “enter” key.
The
TextField
t1
has several listeners attached to it. The
T1
listener copies all text from
t1
into
t2
and the
T1K
listener forces all characters to upper case. You’ll notice that the two
work together, and if you add the
T1K
listener
after
you add the
T1
listener, it doesn’t matter: all characters will still be forced to upper
case in both text fields. It would seem that keyboard events are always fired
before TextComponent
events, and if you want the characters in
t2
to retain the original case that was typed in, you must do some extra work.
T1K
has some other activities of interest. You must detect a backspace (since
you’re controlling everything now) and perform the deletion. The caret
must be explicitly set to the end of the field; otherwise it won’t behave
as you expect. Finally, to prevent the original character from being handled by
the default mechanism, the event must be “consumed” using the consume( )
method that exists for event objects. This tells the system to stop firing the
rest of the event handlers for this particular event.
This
example also quietly demonstrates one of the benefits of the design of inner
classes. Note that in the inner
class:
class T1 implements TextListener {
public void textValueChanged(TextEvent e) {
t2.setText(t1.getText());
}
} t1
and
t2
are
not
members of
T1,
and yet they’re accessible without any special qualification. This is
because an object of an inner class automatically captures a handle to the
outer object that created it, so you can treat members and methods of the
enclosing class object as if they’re yours. As you can see, this is quite
convenient.
[59]
Text
areas
The
most significant change to text areas in Java 1.1
concerns scroll bars. With the TextArea
constructor,
you can now control whether a
TextArea
will have scroll bars: vertical, horizontal, both, or neither. This example
modifies the earlier Java 1.0
TextArea1.java
to show the Java 1.1 scrollbar constructors:
//: TextAreaNew.java
// Controlling scrollbars with the TextArea
// component in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class TextAreaNew extends Applet {
Button b1 = new Button("Text Area 1");
Button b2 = new Button("Text Area 2");
Button b3 = new Button("Replace Text");
Button b4 = new Button("Insert Text");
TextArea t1 = new TextArea("t1", 1, 30);
TextArea t2 = new TextArea("t2", 4, 30);
TextArea t3 = new TextArea("t3", 1, 30,
TextArea.SCROLLBARS_NONE);
TextArea t4 = new TextArea("t4", 10, 10,
TextArea.SCROLLBARS_VERTICAL_ONLY);
TextArea t5 = new TextArea("t5", 4, 30,
TextArea.SCROLLBARS_HORIZONTAL_ONLY);
TextArea t6 = new TextArea("t6", 10, 10,
TextArea.SCROLLBARS_BOTH);
public void init() {
b1.addActionListener(new B1L());
add(b1);
add(t1);
b2.addActionListener(new B2L());
add(b2);
add(t2);
b3.addActionListener(new B3L());
add(b3);
b4.addActionListener(new B4L());
add(b4);
add(t3); add(t4); add(t5); add(t6);
}
class B1L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t5.append(t1.getText() + "\n");
}
}
class B2L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t2.setText("Inserted by Button 2");
t2.append(": " + t1.getText());
t5.append(t2.getText() + "\n");
}
}
class B3L implements ActionListener {
public void actionPerformed(ActionEvent e) {
String s = " Replacement ";
t2.replaceRange(s, 3, 3 + s.length());
}
}
class B4L implements ActionListener {
public void actionPerformed(ActionEvent e) {
t2.insert(" Inserted ", 10);
}
}
public static void main(String[] args) {
TextAreaNew applet = new TextAreaNew();
Frame aFrame = new Frame("TextAreaNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,725);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ You’ll
notice that you can control the scrollbars only at the time of construction of
the
TextArea.
Also, even if a
TextArea
doesn’t have a scrollbar, you can move the cursor such that scrolling
will be forced. (You can see this behavior by playing with the example.)
Check
boxes and radio buttons
As
noted previously, check boxes and radio buttons are both created with the same
class, Checkbox,
but radio buttons are
Checkboxes
placed into a
CheckboxGroup.
In either case, the interesting event is ItemEvent,
for which you create an ItemListener. When
dealing with a group of check boxes or radio buttons, you have a choice. You
can either create a new inner class to handle the event for each different
Checkbox
or you can create one inner class that determines which
Checkbox
was clicked and register a single object of that inner class with each
Checkbox
object. The following example shows both approaches:
//: RadioCheckNew.java
// Radio buttons and Check Boxes in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class RadioCheckNew extends Applet {
TextField t = new TextField(30);
Checkbox[] cb = {
new Checkbox("Check Box 1"),
new Checkbox("Check Box 2"),
new Checkbox("Check Box 3") };
CheckboxGroup g = new CheckboxGroup();
Checkbox
cb4 = new Checkbox("four", g, false),
cb5 = new Checkbox("five", g, true),
cb6 = new Checkbox("six", g, false);
public void init() {
t.setEditable(false);
add(t);
ILCheck il = new ILCheck();
for(int i = 0; i < cb.length; i++) {
cb[i].addItemListener(il);
add(cb[i]);
}
cb4.addItemListener(new IL4());
cb5.addItemListener(new IL5());
cb6.addItemListener(new IL6());
add(cb4); add(cb5); add(cb6);
}
// Checking the source:
class ILCheck implements ItemListener {
public void itemStateChanged(ItemEvent e) {
for(int i = 0; i < cb.length; i++) {
if(e.getSource().equals(cb[i])) {
t.setText("Check box " + (i + 1));
return;
}
}
}
}
// vs. an individual class for each item:
class IL4 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button four");
}
}
class IL5 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button five");
}
}
class IL6 implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("Radio button six");
}
}
public static void main(String[] args) {
RadioCheckNew applet = new RadioCheckNew();
Frame aFrame = new Frame("RadioCheckNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ ILCheck
has the advantage that it automatically adapts when you add or subtract
Checkboxes.
Of course, you can use this with radio buttons as well. It should be used,
however, only when your logic is general enough to support this approach.
Otherwise you’ll end up with a cascaded
if
statement, a sure sign that you should revert to using independent listener
classes.
Drop-down
lists
Drop-down
lists (Choice)
in Java 1.1
also use
ItemListeners
to notify you when a choice has changed:
//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ChoiceNew extends Applet {
String[] description = { "Ebullient", "Obtuse",
"Recalcitrant", "Brilliant", "Somnescent",
"Timorous", "Florid", "Putrescent" };
TextField t = new TextField(100);
Choice c = new Choice();
Button b = new Button("Add items");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
c.addItem(description[count++]);
add(t);
add(c);
add(b);
c.addItemListener(new CL());
b.addActionListener(new BL());
}
class CL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("index: " + c.getSelectedIndex()
+ " " + e.toString());
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(count < description.length)
c.addItem(description[count++]);
}
}
public static void main(String[] args) {
ChoiceNew applet = new ChoiceNew();
Frame aFrame = new Frame("ChoiceNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(750,100);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ Nothing
else here is particularly new (except that Java 1.1
has significantly fewer bugs in the UI classes).
Lists
You’ll
recall that one of the problems with the Java 1.0
List
design is that it took extra work to make it do what you’d expect: react
to a single click on one of the list elements. Java 1.1
has solved this problem:
//: ListNew.java
// Java 1.1 Lists are easier to use
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
public class ListNew extends Applet {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
// Show 6 items, allow multiple selection:
List lst = new List(6, true);
TextArea t = new TextArea(flavors.length, 30);
Button b = new Button("test");
int count = 0;
public void init() {
t.setEditable(false);
for(int i = 0; i < 4; i++)
lst.addItem(flavors[count++]);
add(t);
add(lst);
add(b);
lst.addItemListener(new LL());
b.addActionListener(new BL());
}
class LL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
t.setText("");
String[] items = lst.getSelectedItems();
for(int i = 0; i < items.length; i++)
t.append(items[i] + "\n");
}
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
if(count < flavors.length)
lst.addItem(flavors[count++], 0);
}
}
public static void main(String[] args) {
ListNew applet = new ListNew();
Frame aFrame = new Frame("ListNew");
aFrame.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
aFrame.add(applet, BorderLayout.CENTER);
aFrame.setSize(300,200);
applet.init();
applet.start();
aFrame.setVisible(true);
}
} ///:~ You
can see that no extra logic is required to support a single click on a list
item. You just attach a listener like you do everywhere else.
Menus
The
event handling for menus does seem to benefit from the Java 1.1
event model, but Java’s approach to menus is still messy and requires a
lot of hand coding. The right medium for a menu seems to be a resource rather
than a lot of code. Keep in mind that program-building tools will generally
handle the creation of menus for you, so that will reduce the pain somewhat (as
long as they will also handle the maintenance!).
In
addition, you’ll find the events for menus are inconsistent and can lead
to confusion: MenuItems
use
ActionListeners,
but CheckboxMenuItems
use
ItemListeners.
The Menu
objects can also support
ActionListeners,
but that’s not usually helpful. In general, you’ll attach listeners
to each
MenuItem
or
CheckboxMenuItem,
but the following example (revised from the earlier version) also shows ways to
combine the capture of multiple menu components into a single listener class.
As you’ll see, it’s probably not worth the hassle to do this.
//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;
public class MenuNew extends Frame {
String[] flavors = { "Chocolate", "Strawberry",
"Vanilla Fudge Swirl", "Mint Chip",
"Mocha Almond Fudge", "Rum Raisin",
"Praline Cream", "Mud Pie" };
TextField t = new TextField("No flavor", 30);
MenuBar mb1 = new MenuBar();
Menu f = new Menu("File");
Menu m = new Menu("Flavors");
Menu s = new Menu("Safety");
// Alternative approach:
CheckboxMenuItem[] safety = {
new CheckboxMenuItem("Guard"),
new CheckboxMenuItem("Hide")
};
MenuItem[] file = {
// No menu shortcut:
new MenuItem("Open"),
// Adding a menu shortcut is very simple:
new MenuItem("Exit",
new MenuShortcut(KeyEvent.VK_E))
};
// A second menu bar to swap to:
MenuBar mb2 = new MenuBar();
Menu fooBar = new Menu("fooBar");
MenuItem[] other = {
new MenuItem("Foo"),
new MenuItem("Bar"),
new MenuItem("Baz"),
};
// Initialization code:
{
ML ml = new ML();
CMIL cmil = new CMIL();
safety[0].setActionCommand("Guard");
safety[0].addItemListener(cmil);
safety[1].setActionCommand("Hide");
safety[1].addItemListener(cmil);
file[0].setActionCommand("Open");
file[0].addActionListener(ml);
file[1].setActionCommand("Exit");
file[1].addActionListener(ml);
other[0].addActionListener(new FooL());
other[1].addActionListener(new BarL());
other[2].addActionListener(new BazL());
}
Button b = new Button("Swap Menus");
public MenuNew() {
FL fl = new FL();
for(int i = 0; i < flavors.length; i++) {
MenuItem mi = new MenuItem(flavors[i]);
mi.addActionListener(fl);
m.add(mi);
// Add separators at intervals:
if((i+1) % 3 == 0)
m.addSeparator();
}
for(int i = 0; i < safety.length; i++)
s.add(safety[i]);
f.add(s);
for(int i = 0; i < file.length; i++)
f.add(file[i]);
mb1.add(f);
mb1.add(m);
setMenuBar(mb1);
t.setEditable(false);
add(t, BorderLayout.CENTER);
// Set up the system for swapping menus:
b.addActionListener(new BL());
add(b, BorderLayout.NORTH);
for(int i = 0; i < other.length; i++)
fooBar.add(other[i]);
mb2.add(fooBar);
}
class BL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuBar m = getMenuBar();
if(m == mb1) setMenuBar(mb2);
else if (m == mb2) setMenuBar(mb1);
}
}
class ML implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem target = (MenuItem)e.getSource();
String actionCommand =
target.getActionCommand();
if(actionCommand.equals("Open")) {
String s = t.getText();
boolean chosen = false;
for(int i = 0; i < flavors.length; i++)
if(s.equals(flavors[i])) chosen = true;
if(!chosen)
t.setText("Choose a flavor first!");
else
t.setText("Opening "+ s +". Mmm, mm!");
} else if(actionCommand.equals("Exit")) {
dispatchEvent(
new WindowEvent(MenuNew.this,
WindowEvent.WINDOW_CLOSING));
}
}
}
class FL implements ActionListener {
public void actionPerformed(ActionEvent e) {
MenuItem target = (MenuItem)e.getSource();
t.setText(target.getLabel());
}
}
// Alternatively, you can create a different
// class for each different MenuItem. Then you
// Don't have to figure out which one it is:
class FooL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Foo selected");
}
}
class BarL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Bar selected");
}
}
class BazL implements ActionListener {
public void actionPerformed(ActionEvent e) {
t.setText("Baz selected");
}
}
class CMIL implements ItemListener {
public void itemStateChanged(ItemEvent e) {
CheckboxMenuItem target =
(CheckboxMenuItem)e.getSource();
String actionCommand =
target.getActionCommand();
if(actionCommand.equals("Guard"))
t.setText("Guard the Ice Cream! " +
"Guarding is " + target.getState());
else if(actionCommand.equals("Hide"))
t.setText("Hide the Ice Cream! " +
"Is it cold? " + target.getState());
}
}
public static void main(String[] args) {
MenuNew f = new MenuNew();
f.addWindowListener(
new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(300,200);
f.setVisible(true);
}
} ///:~ This
code is similar to the previous (Java 1.0)
version, until you get to the initialization section (marked by the opening
brace right after the comment “Initialization code:”). Here you can
see the
ItemListeners
and
ActionListeners
attached to the various menu components.
Java
1.1
supports “menu
shortcuts,” so you can select a menu item using the keyboard instead of
the mouse. These are quite simple; you just use the overloaded
MenuItem
constructor that takes as a second argument a
MenuShortcut
object. The constructor for
MenuShortcut
takes the key of interest, which magically appears on the menu item when it
drops down. The example above adds Control-E to the “Exit” menu item.
You
can also see the use of setActionCommand( ).
This seems a bit strange because in each case the “action command”
is exactly the same as the label on the menu component. Why not just use the
label instead of this alternative string? The problem is internationalization.
If you retarget this program to another language, you want to change only the
label in the menu, and not go through the code changing all the logic that will
no doubt introduce new errors. So to make this easy for code that checks the
text string associated with a menu component, the “action command”
can be immutable while the menu label can change. All the code works with the
“action command,” so it’s unaffected by changes to the menu
labels. Note that in this program, not all the menu components are examined for
their action commands, so those that aren’t don’t have their action
command set.
Much
of the constructor is the same as before, with the exception of a couple of
calls to add listeners. The bulk of the work happens in the listeners. In
BL,
the MenuBar
swapping happens as in the previous example. In
ML,
the “figure out who rang” approach is taken by getting the source
of the ActionEvent
and casting it to a MenuItem,
then getting the action command string to pass it through a cascaded
if
statement. Much of this is the same as before, but notice that if
“Exit” is chosen, a new WindowEvent
is created, passing in the handle of the enclosing class object (
MenuNew.this)
and creating a WINDOW_CL | |