The new AWT

Bruce Eckel's Thinking in Java Contents | Prev | Next

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.

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

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

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

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

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

Making the window listener

an anonymous class

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

<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);
  }
} ///:~ 

  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
//: 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
//: 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
//: 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
//: 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
//: 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);
  }
} ///:~ 

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.

Dialog boxes

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

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

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

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

Binding events dynamically

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

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

The new twists in this example are:

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

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.

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

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

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

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

Recommended coding approaches

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

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

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

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

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

The use of this shows up in the three lines:

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

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

  1. Java 1.1 supports JAR files so all your files can be placed in a single compressed JAR archive that requires only one server hit. You no longer need to reduce class count for Internet efficiency.
  2. The code above is much less modular so it’s harder to grab and paste. Note that you must not only implement the various interfaces for your main class, but in actionPerformed( ) you’ve got to detect which action was performed using a cascaded if statement. Not only is this going backwards, away from the listener model, but you can’t easily reuse the actionPerformed( ) method since it’s specific to this particular application. Contrast this with GoodIdea.java, in which you can just grab one listener class and paste it in anywhere else with minimal fuss. Plus you can register multiple listener classes with a single event, allowing even more modularity in what each listener class does.
Mixing the approaches
The second bad idea is to mix the two approaches: use inner listener classes, but also implement one or more listener interfaces as part of the main class. This approach has appeared without explanation in books and documentation, and I can only assume that the authors thought they must use the different approaches for different purposes. But you don’t – in your programming you can probably use inner listener classes exclusively.

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

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

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

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

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

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

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

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

Ugly component inheritance
//: 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.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds