The new AWT | CodeGuru

The new AWT

Bruce Eckel’s Thinking in Java Contents | Prev | Next 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 […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
26 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

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
ActionEvent

ActionListener

addActionListener( )

removeActionListener( )

Button,
List,
TextField, MenuItem,

and its derivatives including
CheckboxMenuItem,
Menu,
and
PopupMenu
AdjustmentEvent

AdjustmentListener

addAdjustmentListener( )

removeAdjustmentListener( )

Scrollbar

Anything
you create that implements the
Adjustable
interface

ComponentEvent

ComponentListener

addComponentListener( )

removeComponentListener( )

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

ContainerListener

addContainerListener( )

removeContainerListener( )

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

FocusListener

addFocusListener( )

removeFocusListener( )

Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame

Label,
List,
Scrollbar,
TextArea,
and
TextField
KeyEvent

KeyListener

addKeyListener( )

removeKeyListener( )

Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
MouseEvent
(for both clicks and motion)

MouseListener

addMouseListener( )

removeMouseListener( )

Component
and its derivatives, including
Button,
Canvas,
Checkbox,
Choice,
Container
,
Panel,
Applet,
ScrollPane,
Window,
Dialog,
FileDialog,
Frame
,
Label,
List,
Scrollbar,
TextArea,
and
TextField
MouseEvent[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
WindowEvent

WindowListener

addWindowListener( )

removeWindowListener( )

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

ItemListener

addItemListener( )

removeItemListener( )

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

TextListener

addTextListener( )

removeTextListener( )

Anything
derived from
TextComponent,
including
TextArea
and
TextField

You


can see that each type of component supports only certain types of events.


It’s helpful to see the events supported by each component, as shown in


the following table:

Component
type Events
supported by this component
Adjustable AdjustmentEvent
Applet ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Button ActionEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Canvas FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
Checkbox ItemEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
CheckboxMenuItem ActionEvent,
ItemEvent
Choice ItemEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Component FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
Container ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Dialog ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
FileDialog ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Frame ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Label FocusEvent,
KeyEvent, MouseEvent, ComponentEvent
List ActionEvent,
FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent
Menu ActionEvent
MenuItem ActionEvent
Panel ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
PopupMenu ActionEvent
Scrollbar AdjustmentEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
ScrollPane ContainerEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextArea TextEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextComponent TextEvent,
FocusEvent, KeyEvent, MouseEvent, ComponentEvent
TextField ActionEvent,
TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent
Window ContainerEvent,
WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent

Once


you know which events a particular component supports, you don’t need to


look anything up to react to that event. You simply:

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

To


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

Listener
interface

w/
adapter

Methods
in interface
ActionListener actionPerformed(ActionEvent)
AdjustmentListener adjustmentValueChanged(


AdjustmentEvent)

ComponentListener

ComponentAdapter

componentHidden(ComponentEvent)

componentShown(ComponentEvent)

componentMoved(ComponentEvent)

componentResized(ComponentEvent)

ContainerListener

ContainerAdapter

componentAdded(ContainerEvent)

componentRemoved(ContainerEvent)

FocusListener

FocusAdapter

focusGained(FocusEvent)

focusLost(FocusEvent)

KeyListener

KeyAdapter

keyPressed(KeyEvent)

keyReleased(KeyEvent)

keyTyped(KeyEvent)

MouseListener

MouseAdapter

mouseClicked(MouseEvent)

mouseEntered(MouseEvent)

mouseExited(MouseEvent)

mousePressed(MouseEvent)

mouseReleased(MouseEvent)

MouseMotionListener

MouseMotionAdapter

mouseDragged(MouseEvent)

mouseMoved(MouseEvent)

WindowListener

WindowAdapter

windowOpened(WindowEvent)

windowClosing(WindowEvent)

windowClosed(WindowEvent)

windowActivated(WindowEvent)

windowDeactivated(WindowEvent)

windowIconified(WindowEvent)

windowDeiconified(WindowEvent)

ItemListener itemStateChanged(ItemEvent)
TextListener textValueChanged(TextEvent)


Using
listener adapters for simplicity

In


the table above, you can see that some listener interfaces have only one


method. These are trivial to implement since you’ll implement them only


when you want to write that particular method. However, the listener interfaces


that have multiple methods could be less pleasant to use. For example,


something you must always do when creating an application is provide a


WindowListener

to the


Frame

so that when you get the


windowClosing( )

event you can call


System.exit(0)

to exit the application. But since


WindowListener

is an


interface

,


you must implement all of the other methods even if they don’t do


anything. This can be annoying.

To


solve the problem, each of the listener interfaces that have more than one


method are provided with


adapters

,


the names of which you can see in the table above. Each adapter provides


default methods for each of the interface methods. (Alas,


WindowAdapter

does


not

have a default


windowClosing( )

that calls


System.exit(0)

.)


Then all you need to do is inherit from the adapter and override only the


methods you need to change. For example, the typical


WindowListener

you’ll use looks like this:

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

The


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

There


is a downside to adapters, however, in the form of a pitfall. Suppose you write


a


WindowAdapter

like the one above:

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

This


doesn’t work, but it will drive you crazy trying to figure out why, since


everything will compile and run fine – except that closing the window


won’t exit the program. Can you see the problem? It’s in the name


of the method:


WindowClosing( )

instead of


windowClosing( )

.


A simple slip in capitalization results in the addition of a completely new


method. However, this is not the method that’s called when the window is


closing, so you don’t get the desired results.


Advertisement

Making
windows and applets

with
the Java 1.1 AWT

Often


you’ll want to be able to create a class that can be invoked as either a


window or an applet. To accomplish this, you simply add a


main( )

to your applet that builds an instance of the applet inside a


Frame

.


As a simple example, let’s look at


Button2New.java

modified to work as both an application and an applet:

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

The


inner class


WL

and the


main( )

are the only two elements added to the applet, and the rest of the applet is


untouched. In fact, you can usually copy and paste the


WL

class and


main( )

into your own applets with little modification. The


WL

class


is


static

so


it can be easily created in


main( ).

(Remember


that an inner class normally needs an outer class handle when it’s


created. Making it


static

eliminates this need.)



You


can see that in


main( )

,


the applet is explicitly initialized and started since in this case the browser


isn’t available to do it for you. Of course, this doesn’t provide


the full behavior of the browser, which also calls


stop( )

and


destroy( )

,


but for most situations it’s acceptable. If it’s a problem, you can:

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

Notice


the last line:

aFrame.setVisible(true);

This


is one of the changes in the Java 1.1


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

This


example is also modified to use a


TextField

rather than printing to the console or to the browser status line. One


restriction in making a program that’s both an applet and an application


is that you must choose input and output forms that work for both situations.

There’s


another small new feature of the Java 1.1


AWT shown here. You no longer need to use the error-prone approach of specifying
BorderLayout
positions
using a
String.
When adding an element to a
BorderLayout
in Java 1.1
,
you can say:
aFrame.add(applet,
BorderLayout.CENTER);

You


name the location with one of the


BorderLayout

constants, which can then be checked at compile-time (rather than just quietly


doing the wrong thing, as with the old form). This is a definite improvement,


and will be used throughout the rest of the book.


Making
the window listener

an
anonymous class

Any


of the listener classes could be implemented as

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

will


become:

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

This


has the advantage that it doesn’t require yet another class name. You


must decide for yourself whether it makes the code easier to understand or more


difficult. However, for the remainder of the book an anonymous inner class will


usually be used for the window listener.


Packaging
the applet into a JAR file

An


important

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

JAR


files change all of that by compressing all of your


.class

files into a single file that is downloaded by the browser. Now you don’t


need to create an ugly design to minimize the number of classes you create, and


the user will get a much faster download time.

Consider


the example above. It looks like


Button2NewB

is a single class, but in fact it contains three inner classes, so that’s


four in all. Once you’ve compiled the program, you package it into a JAR


file with the line:

jar
cf Button2NewB.jar *.class

This


assumes that the only


.class

files in the current directory are the ones from


Button2NewB

.

java

(otherwise you’ll get extra baggage).

Now


you can create an HTML page with the new

archive
tag to indicate the name of the JAR file, like this:
<head><title>Button2NewB Example Applet
</title></head>
<body>
<applet code="Button2NewB.class"
        archive="Button2NewB.jar"
        width=200 height=150>
</applet>
</body>

Everything


else about applet tags in HTML files remains the same.


Revisiting
the earlier examples

To


see a number of examples using the new event model and to study the way a


program can be converted from the old to the new event model, the following


examples revisit many of the issues demonstrated in the first part of this


chapter using the old event model. In addition, each program is now both an


applet and an application so you can run it with or without a browser.


Text
fields

This


is similar to


TextField1.java

,


but it adds significant extra behavior:

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

The


TextField
t3

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

The


TextField
t1

has several listeners attached to it. The


T1

listener copies all text from


t1

into


t2

and the


T1K

listener forces all characters to upper case. You’ll notice that the two


work together, and if you add the


T1K

listener


after

you add the


T1

listener, it doesn’t matter: all characters will still be forced to upper


case in both text fields. It would seem that keyboard events are always fired


before

TextComponent
events, and if you want the characters in
t2
to retain the original case that was typed in, you must do some extra work.
T1K

has some other activities of interest. You must detect a backspace (since


you’re controlling everything now) and perform the deletion. The caret


must be explicitly set to the end of the field; otherwise it won’t behave


as you expect. Finally, to prevent the original character from being handled by


the default mechanism, the event must be “consumed” using the

consume( )
method that exists for event objects. This tells the system to stop firing the
rest of the event handlers for this particular event.

This


example also quietly demonstrates one of the benefits of the design of inner


classes. Note that in the

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

and


t2

are


not

members of


T1

,


and yet they’re accessible without any special qualification. This is


because an object of an inner class automatically captures a handle to the


outer object that created it, so you can treat members and methods of the


enclosing class object as if they’re yours. As you can see, this is quite


convenient.


[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


Checkbox

es.


Of course, you can use this with radio buttons as well. It should be used,


however, only when your logic is general enough to support this approach.


Otherwise you’ll end up with a cascaded


if

statement, a sure sign that you should revert to using independent listener


classes.


Drop-down
lists

Drop-down


lists (

Choice)
in Java 1.1

also use
ItemListeners
to notify you when a choice has changed:
//: ChoiceNew.java
// Drop-down lists with Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.applet.*;
 
public class ChoiceNew extends Applet {
  String[] description = { "Ebullient", "Obtuse",
    "Recalcitrant", "Brilliant", "Somnescent",
    "Timorous", "Florid", "Putrescent" };
  TextField t = new TextField(100);
  Choice c = new Choice();
  Button b = new Button("Add items");
  int count = 0;
  public void init() {
    t.setEditable(false);
    for(int i = 0; i < 4; i++)
      c.addItem(description[count++]);
    add(t);
    add(c);
    add(b);
    c.addItemListener(new CL());
    b.addActionListener(new BL());
  }
  class CL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      t.setText("index: " +  c.getSelectedIndex()
        + "   " + e.toString());
    }
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      if(count < description.length)
        c.addItem(description[count++]);
    }
  }
  public static void main(String[] args) {
    ChoiceNew applet = new ChoiceNew();
    Frame aFrame = new Frame("ChoiceNew");
    aFrame.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    aFrame.add(applet, BorderLayout.CENTER);
    aFrame.setSize(750,100);
    applet.init();
    applet.start();
    aFrame.setVisible(true);
  }
} ///:~ 

Nothing


else here is particularly new (except that Java 1.1


has significantly fewer bugs in the UI classes).


Lists

You’ll


recall that one of the problems with the Java 1.0


List
design is that it took extra work to make it do what you’d expect: react
to a single click on one of the list elements. Java 1.1

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

You


can see that no extra logic is required to support a single click on a list


item. You just attach a listener like you do everywhere else.


Menus

The


event handling for menus does seem to benefit from the Java 1.1


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

In


addition, you’ll find the events for menus are inconsistent and can lead


to confusion:

MenuItems
use
ActionListeners,
but
CheckboxMenuItems
use
ItemListeners.
The
Menu
objects can also support
ActionListeners,
but that’s not usually helpful. In general, you’ll attach listeners
to each
MenuItem
or
CheckboxMenuItem,
but the following example (revised from the earlier version) also shows ways to
combine the capture of multiple menu components into a single listener class.
As you’ll see, it’s probably not worth the hassle to do this.
//: MenuNew.java
// Menus in Java 1.1
import java.awt.*;
import java.awt.event.*;
 
public class MenuNew extends Frame {
  String[] flavors = { "Chocolate", "Strawberry",
    "Vanilla Fudge Swirl", "Mint Chip",
    "Mocha Almond Fudge", "Rum Raisin",
    "Praline Cream", "Mud Pie" };
  TextField t = new TextField("No flavor", 30);
  MenuBar mb1 = new MenuBar();
  Menu f = new Menu("File");
  Menu m = new Menu("Flavors");
  Menu s = new Menu("Safety");
  // Alternative approach:
  CheckboxMenuItem[] safety = {
    new CheckboxMenuItem("Guard"),
    new CheckboxMenuItem("Hide")
  };
  MenuItem[] file = {
    // No menu shortcut:
    new MenuItem("Open"),
    // Adding a menu shortcut is very simple:
    new MenuItem("Exit",
      new MenuShortcut(KeyEvent.VK_E))
  };
  // A second menu bar to swap to:
  MenuBar mb2 = new MenuBar();
  Menu fooBar = new Menu("fooBar");
  MenuItem[] other = {
    new MenuItem("Foo"),
    new MenuItem("Bar"),
    new MenuItem("Baz"),
  };
  // Initialization code:
  {
    ML ml = new ML();
    CMIL cmil = new CMIL();
    safety[0].setActionCommand("Guard");
    safety[0].addItemListener(cmil);
    safety[1].setActionCommand("Hide");
    safety[1].addItemListener(cmil);
    file[0].setActionCommand("Open");
    file[0].addActionListener(ml);
    file[1].setActionCommand("Exit");
    file[1].addActionListener(ml);
    other[0].addActionListener(new FooL());
    other[1].addActionListener(new BarL());
    other[2].addActionListener(new BazL());
  }
  Button b = new Button("Swap Menus");
  public MenuNew() {
    FL fl = new FL();
    for(int i = 0; i < flavors.length; i++) {
      MenuItem mi = new MenuItem(flavors[i]);
      mi.addActionListener(fl);
      m.add(mi);
      // Add separators at intervals:
      if((i+1) % 3 == 0)
        m.addSeparator();
    }
    for(int i = 0; i < safety.length; i++)
      s.add(safety[i]);
    f.add(s);
    for(int i = 0; i < file.length; i++)
      f.add(file[i]);
    mb1.add(f);
    mb1.add(m);
    setMenuBar(mb1);
    t.setEditable(false);
    add(t, BorderLayout.CENTER);
    // Set up the system for swapping menus:
    b.addActionListener(new BL());
    add(b, BorderLayout.NORTH);
    for(int i = 0; i < other.length; i++)
      fooBar.add(other[i]);
    mb2.add(fooBar);
  }
  class BL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuBar m = getMenuBar();
      if(m == mb1) setMenuBar(mb2);
      else if (m == mb2) setMenuBar(mb1);
    }
  }
  class ML implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      String actionCommand =
        target.getActionCommand();
      if(actionCommand.equals("Open")) {
        String s = t.getText();
        boolean chosen = false;
        for(int i = 0; i < flavors.length; i++)
          if(s.equals(flavors[i])) chosen = true;
        if(!chosen)
          t.setText("Choose a flavor first!");
        else
          t.setText("Opening "+ s +". Mmm, mm!");
      } else if(actionCommand.equals("Exit")) {
        dispatchEvent(
          new WindowEvent(MenuNew.this,
            WindowEvent.WINDOW_CLOSING));
      }
    }
  }
  class FL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      MenuItem target = (MenuItem)e.getSource();
      t.setText(target.getLabel());
    }
  }
  // Alternatively, you can create a different
  // class for each different MenuItem. Then you
  // Don't have to figure out which one it is:
  class FooL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Foo selected");
    }
  }
  class BarL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Bar selected");
    }
  }
  class BazL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      t.setText("Baz selected");
    }
  }
  class CMIL implements ItemListener {
    public void itemStateChanged(ItemEvent e) {
      CheckboxMenuItem target =
        (CheckboxMenuItem)e.getSource();
      String actionCommand =
        target.getActionCommand();
      if(actionCommand.equals("Guard"))
        t.setText("Guard the Ice Cream! " +
          "Guarding is " + target.getState());
      else if(actionCommand.equals("Hide"))
        t.setText("Hide the Ice Cream! " +
          "Is it cold? " + target.getState());
    }
  }
  public static void main(String[] args) {
    MenuNew f = new MenuNew();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~ 

This


code is similar to the previous (Java 1.0

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

Java


1.1


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

You


can also see the use of

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

Much


of the constructor is the same as before, with the exception of a couple of


calls to add listeners. The bulk of the work happens in the listeners. In


BL

,


the

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

The


FL

listener is simple even though it’s handling all the different flavors in


the flavor menu. This approach is useful if you have enough simplicity in your


logic, but in general, you’ll want to take the approach used with


FooL

,


BarL,

and


BazL

,


in which they are each attached to only a single menu component so no extra


detection logic is necessary and you know exactly who called the listener. Even


with the profusion of classes generated this way, the code inside tends to be


smaller and the process is more foolproof.


Dialog
boxes

This


is a direct rewrite of the earlier


ToeTest.java.

In this version, however, everything is placed inside an inner class. Although


this completely eliminates the need to keep track of the object that spawned


any class, as was the case in


ToeTest.java

,


it could be taking the concept of inner classes a bit too far. At one point,


the inner classes are nested four deep! This is the kind of design in which you


need to decide whether the benefit of inner classes is worth the increased


complexity. In addition, when you create a non-


static

inner class you’re tying that class to its surrounding class. Sometimes a


standalone class can more easily be reused.

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

Because


static

s


can be at only the outer level of the class, inner classes cannot have


static

data or


static

inner classes.


File
dialogs

Converting


from

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

It


would be nice if all the conversions were this easy, but they’re usually


easy enough, and your code benefits from the improved readability.


Advertisement

Binding
events dynamically

One


of the benefits of the new AWT event model is flexibility. In the old model you


were forced to hard code the behavior of your program, but with the new model


you can add and remove event behavior with single method calls. The following


example demonstrates this:

//: DynamicEvents.java
// The new Java 1.1 event model allows you to
// change event behavior dynamically. Also
// demonstrates multiple actions for an event.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
public class DynamicEvents extends Frame {
  Vector v = new Vector();
  int i = 0;
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public DynamicEvents() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B());
    b1.addActionListener(new B1());
    b2.addActionListener(new B());
    b2.addActionListener(new B2());
    add(b1);
    add(b2);
  }
  class B implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("A button was pressed");
    }
  }
  class CountListener implements ActionListener {
    int index;
    public CountListener(int i) { index = i; }
    public void actionPerformed(ActionEvent e) {
      System.out.println(
        "Counted Listener " + index);
    }
  }
  class B1 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 1 pressed");
      ActionListener a = new CountListener(i++);
      v.addElement(a);
      b2.addActionListener(a);
    }
  }
  class B2 implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 2 pressed");
      int end = v.size() -1;
      if(end >= 0) {
        b2.removeActionListener(
          (ActionListener)v.elementAt(end));
        v.removeElementAt(end);
      }
    }
  }
  public static void main(String[] args) {
    Frame f = new DynamicEvents();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.show();
  }
} ///:~ 

The


new twists in this example are:

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

This


kind of flexibility provides much greater power in your programming.

You


should notice that

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

Separating
business logic

from
UI logic

In


general you’ll want to design your classes so that each one does


“only one thing.” This is particularly important when


user-interface code is concerned, since it’s easy to wrap up “what


you’re doing” with “how you’re displaying it.”


This kind of coupling prevents code reuse. It’s much more desirable to


separate your “business logic” from the GUI. This way, you can not


only reuse the business logic more easily, it’s also easier to reuse the


GUI.

Another


issue is

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

The


following example shows how easy it is to separate the business logic from the


GUI code:

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

You


can see that


BusinessLogic

is a straightforward class that performs its operations without even a hint


that it might be used in a GUI environment. It just does its job.

Separation

keeps track of all the UI details, and it talks to


BusinessLogic

only through its


public

interface. All the operations are centered around getting information back and


forth through the UI and the


BusinessLogic

object. So


Separation

,


in turn, just does its job. Since


Separation

knows only that it’s talking to a


BusinessLogic

object (that is, it isn’t highly coupled), it could be massaged into


talking to other types of objects without much trouble.

Thinking


in terms of separating UI from business logic also makes life easier when


you’re adapting legacy code to work with Java.


Advertisement

Inner


classes, the new event model, and the fact that the old event model is still


supported along with new library features that rely on old-style programming


has added a new element of confusion. Now there are even more different ways


for people to write unpleasant code. Unfortunately, this kind of code is


showing up in books and article examples, and even in documentation and


examples distributed from Sun! In this section we’ll look at some


misunderstandings about what you should and shouldn’t do with the new


AWT, and end by showing that except in extenuating circumstances you can always


use

listener
classes (written as
inner
classes) to solve your event-handling needs. Since this is also the simplest
and clearest approach, it should be a relief for you to learn this.

Before


looking at anything else, you should know that although Java 1.1


is backward-compatible with Java 1.0

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


Baseline:
the good way to do it

To


give you something to compare with, here’s an example showing the


recommended approach. By now it should be reasonably familiar and comfortable:

//: GoodIdea.java
// The best way to design classes using the new
// Java 1.1 event model: use an inner class for
// each different event. This maximizes 
// flexibility and modularity.
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
public class GoodIdea extends Frame {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public GoodIdea() {
    setLayout(new FlowLayout());
    b1.addActionListener(new B1L());
    b2.addActionListener(new B2L());
    add(b1);
    add(b2);
  }
  public class B1L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 1 pressed");
    }
  }
  public class B2L implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      System.out.println("Button 2 pressed");
    }
  }
  public static void main(String[] args) {
    Frame f = new GoodIdea();
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.out.println("Window Closing");
          System.exit(0);
        }
      });
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~ 

This


is fairly trivial: each button has its own listener that prints something out


to the console. But notice that there isn’t an


if

statement in the entire program, or any statement that says, “I wonder


what caused this event.” Each piece of code is concerned with


doing

,


not type-checking. This is the best way to write your code; not only is it


easier to conceptualize, but much easier to read and maintain. Cutting and


pasting to create new programs is also much easier.


Implementing
the main class as a listener

The


first bad idea is a common and recommended approach. This makes the main class


(typically


Applet

or


Frame

,


but it could be any class) implement the various listeners. Here’s an


example:

//: BadIdea1.java
// Some literature recommends this approach,
// but it's missing the point of the new event
// model in Java 1.1
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
public class BadIdea1 extends Frame
    implements ActionListener, WindowListener {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public BadIdea1() {
    setLayout(new FlowLayout());
    addWindowListener(this);
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }
  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == b1)
      System.out.println("Button 1 pressed");
    else if(source == b2)
      System.out.println("Button 2 pressed");
    else
      System.out.println("Something else");
  }
  public void windowClosing(WindowEvent e) {
    System.out.println("Window Closing");
    System.exit(0);
  }
  public void windowClosed(WindowEvent e) {}
  public void windowDeiconified(WindowEvent e) {}
  public void windowIconified(WindowEvent e) {}
  public void windowActivated(WindowEvent e) {}
  public void windowDeactivated(WindowEvent e) {}
  public void windowOpened(WindowEvent e) {}
 
  public static void main(String[] args) {
    Frame f = new BadIdea1();
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~ 

The


use of this shows up in the three lines:

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

Since


BadIdea1

implements


ActionListener

and


WindowListener,

these lines are certainly acceptable, and if you’re still stuck in the


mode of trying to make fewer classes to reduce server hits during applet


loading, it seems to be a good idea. However:

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


Mixing
the approaches

The


second bad idea is to mix the two approaches: use inner listener classes, but


also implement one or more listener interfaces as part of the main class. This


approach has appeared without explanation in books and documentation, and I can


only assume that the authors thought they must use the different approaches for


different purposes. But you don’t – in your programming you can


probably use inner listener classes exclusively.

//: BadIdea2.java
// An improvement over BadIdea1.java, since it
// uses the WindowAdapter as an inner class 
// instead of implementing all the methods of
// WindowListener, but still misses the
// valuable modularity of inner classes
import java.awt.*;
import java.awt.event.*;
import java.util.*;
 
public class BadIdea2 extends Frame
    implements ActionListener {
  Button
    b1 = new Button("Button 1"),
    b2 = new Button("Button 2");
  public BadIdea2() {
    setLayout(new FlowLayout());
    addWindowListener(new WL());
    b1.addActionListener(this);
    b2.addActionListener(this);
    add(b1);
    add(b2);
  }
  public void actionPerformed(ActionEvent e) {
    Object source = e.getSource();
    if(source == b1)
      System.out.println("Button 1 pressed");
    else if(source == b2)
      System.out.println("Button 2 pressed");
    else
      System.out.println("Something else");
  }
  class WL extends WindowAdapter {
    public void windowClosing(WindowEvent e) {
      System.out.println("Window Closing");
      System.exit(0);
    }
  }
  public static void main(String[] args) {
    Frame f = new BadIdea2();
    f.setSize(300,200);
    f.setVisible(true);
  }
} ///:~ 

Since


actionPerformed( )

is still tightly coupled to the main class, it’s hard to reuse that code.


It’s also messier and less pleasant to read than the inner class approach.

There’s


no reason that you have to use any of the old thinking for events in Java 1.1


– so why do it?


Inheriting
a component

Another


place where you’ll often see variations on the old way of doing things is


when creating a new type of component. Here’s an example showing that


here, too, the new way works:

//: GoodTechnique.java
// Your first choice when overriding components
// should be to install listeners. The code is
// much safer, more modular and maintainable.
import java.awt.*;
import java.awt.event.*;
 
class Display {
  public static final int
    EVENT = 0, COMPONENT = 1,
    MOUSE = 2, MOUSE_MOVE = 3,
    FOCUS = 4, KEY = 5, ACTION = 6,
    LAST = 7;
  public String[] evnt;
  Display() {
    evnt = new String[LAST];
    for(int i = 0; i < LAST; i++)
      evnt[i] = new String();
  }
  public void show(Graphics g) {
    for(int i = 0; i < LAST; i++)
      g.drawString(evnt[i], 0, 10 * i + 10);
  }
}
 
class EnabledPanel extends Panel {
  Color c;
  int id;
  Display display = new Display();
  public EnabledPanel(int i, Color mc) {
    id = i;
    c = mc;
    setLayout(new BorderLayout());
    add(new MyButton(), BorderLayout.SOUTH);
    addComponentListener(new CL());
    addFocusListener(new FL());
    addKeyListener(new KL());
    addMouseListener(new ML());
    addMouseMotionListener(new MML());
  }
  // To eliminate flicker:
  public void update(Graphics g) {
    paint(g);
  }
  public void paint(Graphics  g) {
    g.setColor(c);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    display.show(g);
  }
  // Don't need to enable anything for this:
  public void processEvent(AWTEvent e) {
    display.evnt[Display.EVENT]= e.toString();
    repaint();
    super.processEvent(e);
  }
  class CL implements ComponentListener {
    public void componentMoved(ComponentEvent e){
      display.evnt[Display.COMPONENT] =
        "Component moved";
      repaint();
    }
    public void
    componentResized(ComponentEvent e) {
      display.evnt[Display.COMPONENT] =
        "Component resized";
      repaint();
    }
    public void
    componentHidden(ComponentEvent e) {
      display.evnt[Display.COMPONENT] =
        "Component hidden";
      repaint();
    }
    public void componentShown(ComponentEvent e){
      display.evnt[Display.COMPONENT] =
        "Component shown";
      repaint();
    }
  }
  class FL implements FocusListener {
    public void focusGained(FocusEvent e) {
      display.evnt[Display.FOCUS] =
        "FOCUS gained";
      repaint();
    }
    public void focusLost(FocusEvent e) {
      display.evnt[Display.FOCUS] =
        "FOCUS lost";
      repaint();
    }
  }
  class KL implements KeyListener {
    public void keyPressed(KeyEvent e) {
      display.evnt[Display.KEY] =
        "KEY pressed: ";
      showCode(e);
    }
    public void keyReleased(KeyEvent e) {
      display.evnt[Display.KEY] =
        "KEY released: ";
      showCode(e);
    }
    public void keyTyped(KeyEvent e) {
      display.evnt[Display.KEY] =
        "KEY typed: ";
      showCode(e);
    }
    void showCode(KeyEvent e) {
      int code = e.getKeyCode();
      display.evnt[Display.KEY] +=
        KeyEvent.getKeyText(code);
      repaint();
    }
  }
  class ML implements MouseListener {
    public void mouseClicked(MouseEvent e) {
      requestFocus(); // Get FOCUS on click
      display.evnt[Display.MOUSE] =
        "MOUSE clicked";
      showMouse(e);
    }
    public void mousePressed(MouseEvent e) {
      display.evnt[Display.MOUSE] =
        "MOUSE pressed";
      showMouse(e);
    }
    public void mouseReleased(MouseEvent e) {
      display.evnt[Display.MOUSE] =
        "MOUSE released";
      showMouse(e);
    }
    public void mouseEntered(MouseEvent e) {
      display.evnt[Display.MOUSE] =
        "MOUSE entered";
      showMouse(e);
    }
    public void mouseExited(MouseEvent e) {
      display.evnt[Display.MOUSE] =
        "MOUSE exited";
      showMouse(e);
    }
    void showMouse(MouseEvent e) {
      display.evnt[Display.MOUSE] +=
        ", x = " + e.getX() +
        ", y = " + e.getY();
      repaint();
    }
  }
  class MML implements MouseMotionListener {
    public void mouseDragged(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] =
        "MOUSE dragged";
      showMouse(e);
    }
    public void mouseMoved(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] =
        "MOUSE moved";
      showMouse(e);
    }
    void showMouse(MouseEvent e) {
      display.evnt[Display.MOUSE_MOVE] +=
        ", x = " + e.getX() +
        ", y = " + e.getY();
      repaint();
    }
  }
}
 
class MyButton extends Button {
  int clickCounter;
  String label = "";
  public MyButton() {
    addActionListener(new AL());
  }
  public void paint(Graphics g) {
    g.setColor(Color.green);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, s.width - 1, s.height - 1);
    drawLabel(g);
  }
  private void drawLabel(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin =
      (getSize().width - width)/2;
    int verMargin =
      (getSize().height - height)/2;
    g.setColor(Color.red);
    g.drawString(label, horizMargin,
      verMargin + ascent + leading);
  }
  class AL implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      clickCounter++;
      label = "click #" + clickCounter +
        " " + e.toString();
      repaint();
    }
  }
}
 
public class GoodTechnique extends Frame {
  GoodTechnique() {
    setLayout(new GridLayout(2,2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
  }
  public static void main(String[] args) {
    Frame f = new GoodTechnique();
    f.setTitle("Good Technique");
    f.addWindowListener(
      new WindowAdapter() {
        public void windowClosing(WindowEvent e){
          System.out.println(e);
          System.out.println("Window Closing");
          System.exit(0);
        }
      });
    f.setSize(700,700);
    f.setVisible(true);
  }
} ///:~ 

This


example also demonstrates the various events that occur and displays the


information about them. The class


Display

is a way to centralize that information display. There’s an array of


String

s


to hold information about each type of event, and the method


show( )

takes a handle to whatever


Graphic

s


object you have and writes directly on that surface. The scheme is intended to


be somewhat reusable.

EnabledPanel

represents the new type of component. It’s a colored panel with a button


at the bottom, and it captures all the events that happen over it by using


inner listener classes for every single event


except

those in which


EnabledPanel

overrides

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

Each


listener modifies the


Display

string associated with its particular event and calls

repaint( )
so the strings get displayed. You can also see a trick that will usually
eliminate
flicker:
  public void update(Graphics g) {
    paint(g);
  }

You


don’t always need to override

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

You


can see that there are a lot of listeners – however, type checking occurs


for the listeners, and you can’t listen for something that the component


doesn’t support (unlike


BadTechnique.java

,


which you will see momentarily).

Experimenting


with this program is quite educational since you learn a lot about the way that


events occur in Java. For one thing, it shows a flaw in the design of most


windowing systems: it’s pretty hard to click and release the mouse


without moving it, and the windowing system will often think you’re


dragging when you’re actually just trying to click on something. A


solution to this is to use


mousePressed( )

and


mouseReleased( )

instead of


mouseClicked( )

,


and then determine whether to call your own


“mouseReallyClicked(

 

)” method based on time and about 4


pixels of mouse hysteresis.


Ugly
component inheritance

The


alternative, which you will see put forward in many published works, is to call

enableEvents( )
and
pass it the masks corresponding to the events you want to handle. This causes
those events to be sent to the old-style methods (although they’re new to
Java 1.1
)
with names like
processFocusEvent( ).
You must also remember to call the base-class version. Here’s what it
looks like:
//: BadTechnique.java
// It's possible to override components this way,
// but the listener approach is much better, so
// why would you?
import java.awt.*;
import java.awt.event.*;
 
class Display {
  public static final int
    EVENT = 0, COMPONENT = 1,
    MOUSE = 2, MOUSE_MOVE = 3,
    FOCUS = 4, KEY = 5, ACTION = 6,
    LAST = 7;
  public String[] evnt;
  Display() {
    evnt = new String[LAST];
    for(int i = 0; i < LAST; i++)
      evnt[i] = new String();
  }
  public void show(Graphics g) {
    for(int i = 0; i < LAST; i++)
      g.drawString(evnt[i], 0, 10 * i + 10);
  }
}
 
class EnabledPanel extends Panel {
  Color c;
  int id;
  Display display = new Display();
  public EnabledPanel(int i, Color mc) {
    id = i;
    c = mc;
    setLayout(new BorderLayout());
    add(new MyButton(), BorderLayout.SOUTH);
    // Type checking is lost. You can enable and
    // process events that the component doesn't
    // capture:
    enableEvents(
      // Panel doesn't handle these:
      AWTEvent.ACTION_EVENT_MASK |
      AWTEvent.ADJUSTMENT_EVENT_MASK |
      AWTEvent.ITEM_EVENT_MASK |
      AWTEvent.TEXT_EVENT_MASK |
      AWTEvent.WINDOW_EVENT_MASK |
      // Panel can handle these:
      AWTEvent.COMPONENT_EVENT_MASK |
      AWTEvent.FOCUS_EVENT_MASK |
      AWTEvent.KEY_EVENT_MASK |
      AWTEvent.MOUSE_EVENT_MASK |
      AWTEvent.MOUSE_MOTION_EVENT_MASK |
      AWTEvent.CONTAINER_EVENT_MASK);
      // You can enable an event without
      // overriding its process method.
  }
  // To eliminate flicker:
  public void update(Graphics g) {
    paint(g);
  }
  public void paint(Graphics  g) {
    g.setColor(c);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    display.show(g);
  }
  public void processEvent(AWTEvent e) {
    display.evnt[Display.EVENT]= e.toString();
    repaint();
    super.processEvent(e);
  }
  public void
  processComponentEvent(ComponentEvent e) {
    switch(e.getID()) {
      case ComponentEvent.COMPONENT_MOVED:
        display.evnt[Display.COMPONENT] =
          "Component moved";
        break;
      case ComponentEvent.COMPONENT_RESIZED:
        display.evnt[Display.COMPONENT] =
          "Component resized";
        break;
      case ComponentEvent.COMPONENT_HIDDEN:
        display.evnt[Display.COMPONENT] =
          "Component hidden";
        break;
      case ComponentEvent.COMPONENT_SHOWN:
        display.evnt[Display.COMPONENT] =
          "Component shown";
        break;
      default:
    }
    repaint();
    // Must always remember to call the "super"
    // version of whatever you override:
    super.processComponentEvent(e);
  }
  public void processFocusEvent(FocusEvent e) {
    switch(e.getID()) {
      case FocusEvent.FOCUS_GAINED:
        display.evnt[Display.FOCUS] =
          "FOCUS gained";
        break;
      case FocusEvent.FOCUS_LOST:
        display.evnt[Display.FOCUS] =
          "FOCUS lost";
        break;
      default:
    }
    repaint();
    super.processFocusEvent(e);
  }
  public void processKeyEvent(KeyEvent e) {
    switch(e.getID()) {
      case KeyEvent.KEY_PRESSED:
        display.evnt[Display.KEY] =
          "KEY pressed: ";
        break;
      case KeyEvent.KEY_RELEASED:
        display.evnt[Display.KEY] =
          "KEY released: ";
        break;
      case KeyEvent.KEY_TYPED:
        display.evnt[Display.KEY] =
          "KEY typed: ";
        break;
      default:
    }
    int code = e.getKeyCode();
    display.evnt[Display.KEY] +=
      KeyEvent.getKeyText(code);
    repaint();
    super.processKeyEvent(e);
  }
  public void processMouseEvent(MouseEvent e) {
    switch(e.getID()) {
      case MouseEvent.MOUSE_CLICKED:
        requestFocus(); // Get FOCUS on click
        display.evnt[Display.MOUSE] =
          "MOUSE clicked";
        break;
      case MouseEvent.MOUSE_PRESSED:
        display.evnt[Display.MOUSE] =
          "MOUSE pressed";
        break;
      case MouseEvent.MOUSE_RELEASED:
        display.evnt[Display.MOUSE] =
          "MOUSE released";
        break;
      case MouseEvent.MOUSE_ENTERED:
        display.evnt[Display.MOUSE] =
          "MOUSE entered";
        break;
      case MouseEvent.MOUSE_EXITED:
        display.evnt[Display.MOUSE] =
          "MOUSE exited";
        break;
      default:
    }
    display.evnt[Display.MOUSE] +=
      ", x = " + e.getX() +
      ", y = " + e.getY();
    repaint();
    super.processMouseEvent(e);
  }
  public void
  processMouseMotionEvent(MouseEvent e) {
    switch(e.getID()) {
      case MouseEvent.MOUSE_DRAGGED:
        display.evnt[Display.MOUSE_MOVE] =
          "MOUSE dragged";
        break;
      case MouseEvent.MOUSE_MOVED:
        display.evnt[Display.MOUSE_MOVE] =
          "MOUSE moved";
        break;
      default:
    }
    display.evnt[Display.MOUSE_MOVE] +=
      ", x = " + e.getX() +
      ", y = " + e.getY();
    repaint();
    super.processMouseMotionEvent(e);
  }
}
 
class MyButton extends Button {
  int clickCounter;
  String label = "";
  public MyButton() {
    enableEvents(AWTEvent.ACTION_EVENT_MASK);
  }
  public void paint(Graphics g) {
    g.setColor(Color.green);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
    g.setColor(Color.black);
    g.drawRect(0, 0, s.width - 1, s.height - 1);
    drawLabel(g);
  }
  private void drawLabel(Graphics g) {
    FontMetrics fm = g.getFontMetrics();
    int width = fm.stringWidth(label);
    int height = fm.getHeight();
    int ascent = fm.getAscent();
    int leading = fm.getLeading();
    int horizMargin =
      (getSize().width - width)/2;
    int verMargin =
      (getSize().height - height)/2;
    g.setColor(Color.red);
    g.drawString(label, horizMargin,
                 verMargin + ascent + leading);
  }
  public void processActionEvent(ActionEvent e) {
    clickCounter++;
    label = "click #" + clickCounter +
      " " + e.toString();
    repaint();
    super.processActionEvent(e);
  }
}
 
public class BadTechnique extends Frame {
  BadTechnique() {
    setLayout(new GridLayout(2,2));
    add(new EnabledPanel(1, Color.cyan));
    add(new EnabledPanel(2, Color.lightGray));
    add(new EnabledPanel(3, Color.yellow));
    // You can also do it for Windows:
    enableEvents(AWTEvent.WINDOW_EVENT_MASK);
  }
  public void processWindowEvent(WindowEvent e) {
    System.out.println(e);
    if(e.getID() == WindowEvent.WINDOW_CLOSING) {
      System.out.println("Window Closing");
      System.exit(0);
    }
  }
  public static void main(String[] args) {
    Frame f = new BadTechnique();
    f.setTitle("Bad Technique");
    f.setSize(700,700);
    f.setVisible(true);
  }
} ///:~ 

Sure,


it works. But it’s ugly and hard to write, read, debug, maintain, and


reuse. So why bother when you can use inner listener classes?



[58]

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.

[59]

It also solves the problem of “callbacks” without adding any


awkward “method pointer” feature to Java.

Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.