Main Page

Previous Next

Semantic Event Listeners in an Application

An obvious candidate for implementing semantic event listeners is in the Sketcher program, to support the operation of the menu bar in the class SketchFrame. When we click on an item in one of the pull-down menus, a semantic event will be generated that we can listen for and then use to determine the appropriate program action.

Listening to Menu Items

We will start with the Elements menu. This is concerned with identifying the type of graphic element to be drawn next, and the color in which it will be drawn. We won't be drawing them for a while, but we can put in the infrastructure to set the type and color for an element without worrying about how it will actually be created and drawn.

To identify the type of element, we can define constants that will act as IDs for the four types of element we have provided for in the menu so far. This will help us with the operation of the listeners for the menu item as well as provide a way to identify a particular type of element. Since we will accumulate quite a number of application wide constants, it will be convenient to define them in an interface that can be implemented by any class that refers to any of the constants. Here's the initial definition including constants to define line, rectangle, circle and curve elements:

// Defines application wide constants
public interface Constants { 
  // Element type definitions
  int LINE      = 101;
  int RECTANGLE = 102;
  int CIRCLE    = 103;
  int CURVE     = 104;

  // Initial conditions
  int DEFAULT_ELEMENT_TYPE = LINE;
}

Save this in the same directory as the rest of the Sketcher program as Constants.java. Each element type ID is an integer constant with a unique value and we can obviously extend the variety of element types if necessary. We have defined a constant, DEFAULT_ELEMENT_TYPE, representing the initial element type to apply when the application starts. We should do the same thing for the Color submenu and supply a constant that specifies the initial element color:

// Defines application wide constants
import java.awt.Color;

public interface Constants { 
  // Element type definitions
  int LINE      = 101;
  int RECTANGLE = 102;
  int CIRCLE    = 103;
  int CURVE     = 104;

  // Initial conditions
  int DEFAULT_ELEMENT_TYPE = LINE;
  Color DEFAULT_ELEMENT_COLOR = Color.BLUE;
}

We have defined the DEFAULT_ELEMENT_COLOR as type Color, so we have added an import statement for java.awt to get the definition for the Color class. When we want to change the default start-up color or element type, we just need to change the values of the constants in the Constants interface. This will automatically take care of setting things up – as long as we implement the program code appropriately.

We can add fields to the SketchFrame class to store the current element type and color, since these are application-wide values, and are not specific to a view:

private Color elementColor = DEFAULT_ELEMENT_COLOR;     // Current element color
private int elementType = DEFAULT_ELEMENT_TYPE;         // Current element type

We can now use these to ensure that the menu items are checked appropriately when the application starts. We also want the constants from the Constants interface available, so make the following changes to the SketchFrame class definition:

import javax.swing.*;
import java.awt.Event;
import java.awt.Color;

public class SketchFrame extends JFrame implements Constants {
  // Constructor
  public SketchFrame(String title) {
    setTitle(title);                             // Set the window title
    setJMenuBar(menuBar);                        // Add the menu bar to the window
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    
    // Code to create the File menu...

    // Construct the Element pull down menu
    elementMenu.add(lineItem = new JRadioButtonMenuItem(
                                            "Line", elementType==LINE));
    elementMenu.add(rectangleItem = new JRadioButtonMenuItem(
                                            "Rectangle", elementType==RECTANGLE));
    elementMenu.add(circleItem = new JRadioButtonMenuItem(
                                            "Circle", elementType==CIRCLE));
    elementMenu.add(curveItem = new JRadioButtonMenuItem(
                                            "Curve", elementType==CURVE));
    ButtonGroup types = new ButtonGroup();

    // ...plus the rest of the code for the element types as before...

    elementMenu.addSeparator();

    elementMenu.add(colorMenu);                   // Add the sub-menu
    colorMenu.add(redItem = new JCheckBoxMenuItem(                 
"Red", elementColor.equals(Color.RED)));
colorMenu.add(yellowItem = new JCheckBoxMenuItem( "Yellow", elementColor.equals(Color.Yellow))); colorMenu.add(greenItem = new JCheckBoxMenuItem( "Green", elementColor.equals(Color.green))); colorMenu.add(blueItem = new JCheckBoxMenuItem( "Blue", elementColor.equals(Color.blue))); // Add element color accelerators // ... plus the rest of the constructor as before... } // ...plus the rest of the class and include the two new data members... private Color elementColor = DEFAULT_ELEMENT_COLOR; // Current element color private int elementType = DEFAULT_ELEMENT_TYPE; // Current element type }

When we construct the element objects, we use the elementType and elementColor members to set the state of each menu item. Only the element type menu item corresponding to the default type set in elementType will be checked because that's the only comparison that will produce a true result as an argument to the JRadioButtonMenuItem constructor. The mechanism is the same for the color menu items, but note that we use the equals() method defined in the Color class for a valid comparison. We might just get away with using == since we are only using constant Color values defined in the class, but as soon as we use a color that is not one of these, this would no longer work. Of course, we have to use == for the element type items because the IDs are of type int.

Having got that sorted out, we can have a go at implementing the listeners for the Elements menu, starting with the type menu items.

Try It Out – Handling Events for the Element Type Menu

We will add an inner class that will define listeners for the menu items specifying the element type. This class will implement the ActionListener interface because we want to respond to actions on these menu items. Add the following definition as an inner class to SketchFrame:

// Handles element type menu items
class TypeListener implements ActionListener { 
  // Constructor
  TypeListener(int type) {
    this.type = type;
  }
    
  // Sets the element type
  public void actionPerformed(ActionEvent e) {
    elementType = type;  
  }

  private int type;                       // Store the type for the menu
}

Now we can use objects of this class as listeners for the menu items. Add the following code to the SketchFrame constructor, after the code that sets up the type menu items for the Elements menu just before the last two lines of the constructor:

  // Add type menu item listeners
  lineItem.addActionListener(new TypeListener(LINE));
  rectangleItem.addActionListener(new TypeListener(RECTANGLE));
  circleItem.addActionListener(new TypeListener(CIRCLE));
  curveItem.addActionListener(new TypeListener(CURVE));

  menuBar.add(fileMenu);                       // Add the file menu
  menuBar.add(elementMenu);                    // Add the element menu 
}

It will also be necessary to add the following two import statements to the SketchFrame class.

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

Recompile Sketcher and see how it looks.

How It Works

It won't look any different as the listeners just set the current element type in the SketchFrame object. The listener class is remarkably simple. Each listener object stores the type corresponding to the menu item that is passed as the constructor argument. When an event occurs, the actionPerformed() method just stores the type in the listener object in the elementType member of the SketchFrame object.

Now we can do the same for the color menu items.

Try It Out – Implementing Color Menu Item Listeners

We will define another class that is an inner class to SketchFrame that defines listeners for the Color menu items:

  // Handles color menu items
  class ColorListener implements ActionListener {
    public ColorListener(Color color) {
      this.color = color;
    }

    public void actionPerformed(ActionEvent e) {
      elementColor = color;
    }

    private Color color;
  }

We just need to create listener objects and add them to the menu items. Add the following code at the end of the SketchFrame constructor after the code that sets up the Color submenu:

    // Add color menu item listeners
    redItem.addActionListener(new ColorListener(Color.red));
    yellowItem.addActionListener(new ColorListener(Color.yellow));
    greenItem.addActionListener(new ColorListener(Color.green));
    blueItem.addActionListener(new ColorListener(Color.blue));

    menuBar.add(fileMenu);                       // Add the file menu
    menuBar.add(elementMenu);                    // Add the element menu 
  }

This adds a listener object for each menu item in the Color menu.

How It Works

The ColorListener class works in the same way as the TypeListener class. Each class object stores an identifier for the menu item for which it is listening – in this case a Color object corresponding to the color the menu item sets up. The actionPerformed() method just stores the Color object from the listener object in the elementColor member of the SketchFrame object.

Of course, the menu doesn't quite work as it should. The menu item check marks are not being set correctly, as you can see below. We want an exclusive check, as with the radio buttons; more than one color at a time doesn't make sense:

Fixing the Color Menu Checks

One way to deal with the problem is to make the listener object for a color menu item set the checks for all the menu items. You could code this in the ColorListener class as:

  class ColorListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
      elementColor = color;
      // Set the checks for all menu items
      redItem.setState(color.equals(Color.red));
      greenItem.setState(color.equals(Color.green));
      blueItem.setState(color.equals(Color.blue));
      yellowItem.setState(color.equals(Color.yellow));
    }
    // Rest of the class as before...
  }

This calls the setState() method for each menu item. If the argument is true the checkmark is set, and if it is false, it isn't. Clearly this will only set the checkmark for the item that corresponds to the color referenced by color. This is quite straightforward but there is a better way.

A ButtonGroup object works with JCheckBoxMenuItem objects because they have AbstractButton as a base class. Therefore we could add these menu items to their own button group in the SketchFrame constructor, and it will all be taken care of for us. The ButtonGroup object tracks the state of all of the buttons in the group. When any button is turned on, all the others are turned off, so only one button in the group can be on at one time. So add the following code – it could go anywhere after the items have been created but place it following the code that adds the items to the Color menu for consistency with the element type code.

    ButtonGroup colors = new ButtonGroup();     // Color menu items button group
    colors.add(redItem);
    colors.add(yellowItem);
    colors.add(greenItem);
    colors.add(blueItem);

Now our Color menu checks are set automatically so we can forget about them.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor