Main Page

Previous Next

Semantic Event Handling in Applets

Event handling in an applet is exactly the same as in an application, but we ought to see it for ourselves. Let's see how we would handle events for buttons in an applet. We can create an applet that uses some buttons that have listeners. To make this example a bit more gripping, we'll throw in the possibility of monetary gain.

That's interesting to almost everybody. Let's suppose we want to have an applet to create random numbers for a lottery. The requirement is to generate six different random numbers between 1 and 49. It would also be nice to be able to change a single number if you don't like it, so we'll add that capability as well. Since your local lottery may not be like this, we will implement the applet to make it easy for you to adapt the applet to fit your requirements.

By displaying the six selected numbers on buttons, we can provide for changing one of the choices by processing the action event for that button. Thus, clicking a button will provide another number. We'll also add a couple of control buttons, one to make a new selection for a complete set of lottery numbers, and another just for fun to change the button color. Here's how the applet will look when running under appletviewer:

Try It Out – A Lottery Applet

We can outline the broad structure of the applet's code as follows:

// Applet to generate lottery entries
import javax.swing.*;
import java.awt.*;
import java.util.Random;               // For random number generator
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class Lottery extends JApplet {
  // Initialize the applet
  public void init() {
    // Set up the lucky numbers buttons...

    // Set up the control buttons...
  }
 
  // Custom button showing lottery selection
  // Each button listens for its own events
  class Selection extends JButton implements ActionListener {
    // Constructor
    public Selection(int value) {
      // Create the button showing the value...
    }

    // Handle selection button event
    public void actionPerformed(ActionEvent e) {
      // Change the current selection value to a new selection value
    }
    // Details of the rest of the selection class definition...
  }

  // Class defining a handler for a control button
  class HandleControlButton implements ActionListener {
    // Constructor...

    // Handle button click
    public void actionPerformed(ActionEvent e) {
      // Handle button click for a particular button...
    }

    // Rest of the inner class definition...
  }

  final static int numberCount = 6;                   // Number of lucky numbers
  final static int minValue = 1;                      // Minimum in range
  final static int maxValue = 49;                     // Maximum in range
  static int[] values = new int[maxValue-minValue+1]; // Array of possible values
  static {                                            // Initialize array
    for(int i = 0 ; i<values.length ; i++)
      values[i] = i + minValue;
  }

  // An array of custom buttons for the selected numbers
  private Selection[] luckyNumbers = new Selection[numberCount]; 

  private static Random choice = new Random();        // Random number generator
}

How It Works

The applet class is called Lottery and it contains two inner classes, Selection and HandleControlButton. The Selection class provides a custom button that will show a number as its label, the number being passed to the constructor as an argument. We can make an object of the Selection class listen for its own action events. As we said at the outset, an event for a selection button will change the label of the button to a different value so of course we'll need to make sure this doesn't duplicate any of the values for the other buttons.

The two control buttons will use separate listeners to handle their action events and the response to an event will be quite different for each of them. One control button will create a new set of lucky numbers while the other control button will just change the color of the buttons.

The numberCount member of the Lottery class sets the number of values that is created. The minValue and maxValue members specify the range of possible values. The possible values for selections are stored in the values array, and this is set up in the static initialization block. The Lottery class has an array of Selection objects as a data member – we can have arrays of components just like arrays of any other kind of object. Since the Selection buttons will all be the same, it's very convenient to create them as an array, and having an array of components will enable us to set them up in a loop. We also have a Random object as a member as we will need to generate some random integers.

We can now set about filling in the sections of the program that we roughed out previously.

Filling in the Details

To generate maxCount random values from the elements in the values array is quite independent of everything else here, so we'll define a static method in the Lottery class to do this.

public class Lottery extends JApplet {
  // Generate numberCount random selections from the values array
  static int[] getNumbers() {
    int[] numbers = new int[numberCount];  // Store for the numbers to be returned
    int candidate = 0;                     // Stores a candidate selection
    for(int i = 0; i < numberCount; i++) { // Loop to find the selections

      search:
      // Loop to find a new selection different from any found so far
      for(;;) {
        candidate = values[choice.nextInt(values.length)];
        for(int j = 0 ; j<i ; j++)         // Check against existing selections
          if(candidate==numbers[j])        // If it is the same
            continue search;               // get another random selection

        numbers[i] = candidate;            // Store the selection in numbers array
        break;                             // and go to find the next
      }
    }
    return numbers;                        // Return the selections
  }

  // Plus the rest of the class definition...
}

The getNumbers() method returns a reference to an int array containing the selections – which must all be different, of course. We start the process by creating an array to hold the selections, and a variable, candidate to hold a potential selection from the values array. We generate a new selection for each iteration of the outer for loop. The process is quite simple. In the indefinite for loop with the label, search, we choose a random value from the values array using our random number generator, and then check its value against any selections already stored in the numbers array. If it is the same as any of them, the labeled continue will go to the next iteration of the indefinite for loop. This will continue until a selection is found that is different from the others. In this way we ensure that we end up with a set of selections that are all different.

Let's implement the init() method for the Lottery class next, as this sets up the Selection buttons and the rest of the applet.

Try It Out – Setting Up the Lucky Number Buttons

In the class outline we identified two tasks for the init() method. The first was setting up the lucky number buttons to be contained in the luckyNumbers array.

Here's the code to do that:

  // Initialize the applet
  public void init() {
    // Set up the selection buttons
    Container content = getContentPane();
    content.setLayout(new GridLayout(0,1));  // Set the layout for the applet

    // Set up the panel to hold the lucky number buttons
    JPanel buttonPane = new JPanel();  // Add the pane containing numbers

    // Let's have a fancy panel border
    buttonPane.setBorder(BorderFactory.createTitledBorder(
                         BorderFactory.createEtchedBorder(Color.cyan,
                                                          Color.blue),
                                                          "Every One a Winner!"));

    int[] choices = getNumbers();            // Get initial set of numbers
    for(int i = 0; i<numberCount; i++) {
     luckyNumbers[i] = new Selection(choices[i]);
     buttonPane.add(luckyNumbers[i]);
    }
    content.add(buttonPane);

    // Set up the control buttons...
  }

How It Works

The first step is to define the layout manager for the applet. To make the layout easier, we will use one panel to hold the selection buttons and another to hold the control buttons. We can position these panels one above the other by specifying the layout manager for the content pane of the applet as a grid layout with one column. The top panel will contain the lucky number buttons and the bottom panel will contain the control buttons.

The buttonPane panel that holds the lucky number buttons is of type JPanel, so it has a FlowLayout object as its layout manager by default. A flow layout manager allows components to assume their 'natural' or 'preferred size', so we will set the preferred size for the buttons in the Selection class constructor. We decorate the panel with a border by calling its setBorder() method. The argument is returned by the static createTitledBorder() method from the BorderFactory class. The first argument passed to createTitledBorder() is the border to be used, and the second is the title.

We use an etched border that is returned by another static method in the BorderFactory class. The two arguments to this method are the highlight and shadow colors to be used for the border. A big advantage of using the BorderFactory methods rather than creating border objects from the border class constructors directly is that border objects will be shared where possible, so you can use a particular border in various places in your code and only one object will be created.

The buttons to display the chosen numbers will be of type Selection, and we will get to the detail of this inner class in a moment. We call our static method getNumbers() to obtain the first set of random values for the buttons. We then create and store each button in the luckyNumbers array and add it to the panel in the for loop. Since these buttons are going to listen for their own events, we don't need to worry about setting separate action listeners for them. The last step here is to add the buttonPane panel to the content pane for the applet.

We should now add the code for the control buttons to the init() method.

Try It Out – Setting up the Control Buttons

The listeners for each of the control buttons will be of the same class type, so the listener object will need some way to determine which button originated a particular event. One way to do this is to use constants as IDs to identify the control buttons, and pass the appropriate ID to the class constructor for the listener object.

We could define the constants PICK_LUCKY_NUMBERS and COLOR as fields in the Lottery class for this purpose. The COLOR control button will also reference a couple of Color variables, startColor and flipColor. You can add the following statements to the Lottery class after the definition of the luckyNumbers array:

// An array of custom buttons for the selected numbers
private Selection[] luckyNumbers = new Selection[numberCount];

final public static int PICK_LUCKY_NUMBERS = 1;                // Select button ID
final public static int COLOR = 2;                             // Color button ID

// swap colors
Color flipColor = new Color(Color.yellow.getRGB()^Color.red.getRGB()); 

Color startColor = new Color(Color.yellow.getRGB());           // start color

The code to add the other panel and the control buttons is as follows:

// Initialize the applet
public void init() {
    // Setting up the selections buttons as previously...

    // Add the pane containing control buttons
    JPanel controlPane = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 10));  

    // Add the two control buttons
    JButton button;                                      // A button variable
    Dimension buttonSize = new Dimension(100,20);        // Button size

    controlPane.add(button = new JButton("Lucky Numbers!"));
    button.setBorder(BorderFactory.createRaisedBevelBorder());
    button.addActionListener(new HandleControlButton(PICK_LUCKY_NUMBERS));
    button.setPreferredSize(buttonSize);

    controlPane.add(button = new JButton("Color"));
    button.setBorder(BorderFactory.createRaisedBevelBorder());
    button.addActionListener(new HandleControlButton(COLOR));
    button.setPreferredSize(buttonSize);

    content.add(controlPane);
  }

How It Works

We create another JPanel object to hold the control buttons and just to show that we can, we pass a layout manager object to the constructor. It's a FlowLayout manager again, but this time we explicitly specify that the components are to be centered and the horizontal and vertical gaps are to be 5 and 10 pixels respectively.

We declare the button variable to use as a temporary store for the reference to each button while we set it up. We also define a Dimension object that we will use to set a common preferred size for the buttons. The buttons are JButton components, not custom components, so we must set each of them up here with a listener and a border. We add a raised bevel border to each button to make them look like buttons – again using a BorderFactory method.

The listener for each button is an object of the inner class HandleControlButton, and we pass the appropriate button ID to the constructor for reasons that will be apparent when we define that class. To set the preferred size for each button object, we call its setPreferredSize() method. The argument is a Dimension object that specifies the width and height. Finally, after adding the two buttons to controlPane, we add that to the content pane for the applet.

The inner class HandleControlButton defines the listener object for each control button, so let's implement that next.

Try It Out – Defining the Control Button Handler Class

We have already determined that the class constructor will accept an argument that identifies the particular button for which it is listening. This is to enable the actionPerformed() method in the listener class to choose the course of action appropriate to the button. Here's the inner class definition to do that:

  class HandleControlButton implements ActionListener {
    private int buttonID;   

    // Constructor
    public HandleControlButton(int buttonID) {
      this.buttonID = buttonID;                   // Store the button ID
    }

    // Handle button click
    public void actionPerformed(ActionEvent e) {
      switch(buttonID) {
        case PICK_LUCKY_NUMBERS:
          int[] numbers = getNumbers();            // Get maxCount random numbers
          for(int i = 0; i < numberCount; i++)
            luckyNumbers[i].setValue(numbers[i]);  // Set the button values
          break;
        case COLOR:
          Color color = new Color(
                flipColor.getRGB()^luckyNumbers[0].getBackground().getRGB());
          for(int i = 0; i < numberCount; i++)
            luckyNumbers[i].setBackground(color);  // Set the button colors
          break;
      }
    }
  }

How It Works

The constructor stores its argument value in the data member, buttonID, so each listener object will have the ID for the button available. This is used in the actionPerformed() method to select the appropriate code to execute for a particular button. Each case in the switch statement corresponds to a different button. You could extend this to enable the class to handle as many different buttons as you want by adding case statements. Because of the way we have implemented the method, each button must have a unique ID associated with it. Of course, this isn't the only way to do this, as we'll see in a moment.

For the PICK_LUCKY_NUMBERS button, we just call the getNumbers() method to produce a set of numbers, and then call the setValue() method for each selection button and pass a number to it. We will implement the setValue() method when we define the selection class in detail, in a moment.

For the COLOR button, we create a new color by exclusive ORing (that is, XOR) the RGB value of flipColor with the current button color. You will recall from our discussion of the ^ operator (in Chapter 2) that you can use it to exchange two values, and that is what we are doing here. flipColor was defined as the two colors, Color.yellow and Color.red, exclusive ORed together. Exclusive ORing this with either color will produce the other so we flip from one color to the other automatically for each button by exclusive ORing the background and flipColor. We must get the RGB value for each color and operate on those – you can't apply the ^ operator to the objects. We then turn the resulting RGB value back into a Color object.

Let's now add the inner class, Selection, which defines the lucky number buttons.

Try It Out – Defining the Selection Buttons

Each button will need to store the value shown on the label, so the class will need a data member for this purpose. The class will also need a constructor, the setValue() method to set the value for the button to a new value, and a method to compare the current value for a button to a given value. We need to be able to set the value for a button for two reasons – we call it when we set up all six selections in the listener for the control button, and we want to reset the value for a button to change it individually.

The method to compare the value set for a button to a given integer will enable us to exclude a number that was already assigned to a button in the process of generating the button values. We'll also need to implement the actionPerformed() method to handle the action events for the button, as the buttons are going to handle their own events. Here's the basic code for the class definition:

class Selection extends JButton implements ActionListener {
  // Constructor
  public Selection(int value) {
    super(Integer.toString(value));    // Call base constructor and set the label
    this.value = value;                // Save the value
    setBackground(startColor);
    setBorder(BorderFactory.createRaisedBevelBorder());    // Add button border
    setPreferredSize(new Dimension(80,20));
    addActionListener(this);           // Button listens for itself
  }

  // Handle selection button event
  public void actionPerformed(ActionEvent e) {
    // Change this selection to a new selection
    int candidate = 0;
    for(;;) {                              // Loop to find a different selection
      candidate = values[choice.nextInt(values.length)];
      if(isCurrentSelection(candidate))    // If it is not different
        continue;                          // find another
      setValue(candidate);                 // We have one so set the button value
      return;
    }
  }
  // Set the value for the selection
  public void setValue(int value) {
    setText(Integer.toString(value));    // Set value as the button label
    this.value = value;                   // Save the value
  }

  // Check the value for the selection
  boolean hasValue(int possible) {
    return value==possible;               // Return true if equals current value 
  }

  // Check the current choices
  boolean isCurrentSelection(int possible) {
    for(int i = 0; i < numberCount; i++)         // For each button
      if(luckyNumbers[i].hasValue(possible))     // check against possible
        return true;                             // Return true for any =
    return false;                                // Otherwise return false 
  }

  private int value;                       // Value for the selection button
}

How It Works

The constructor calls the base class constructor to set the initial label for the button. It also stores the value of type int that is passed as an argument. The setValue() method just updates the value for a selection button with the value passed as an argument and changes the button label by calling the setText() method which is inherited from the base class, JButton. The hasValue() method returns true if the argument value passed to it is equal to the current value stored in the data member value, and false otherwise.

The actionPerformed() method has a little more meat to it but the technique is similar to that in the getNumbers() method. To change the selection, we must create a new random value for the button from the numbers values array, but excluding all the numbers currently assigned to the six buttons. To do this we just check each candidate against the six existing selections by calling the isCurrentSelection(), and continue choosing a new candidate until we find one that's different.

In the isCurrentSelection() method, we just work through the array of Selection objects, luckyNumbers, comparing each value with the possible argument using the hasValue() method. If any button has the same value as possible, the method returns true, otherwise it returns false.

We're ready to start generating lottery entries. If you compile the Lottery.java file you can run the applet using AppletViewer. You will need an HTML file of course. The following contents for the file will do the job:

<APPLET CODE="Lottery.class" WIDTH=300 HEIGHT=200>
</APPLET>

You can adjust the width and height values to suit your monitor resolution if necessary.

The applet should produce a selection each time you click the left control button. Clicking on any of the selection buttons will generate an action event that will cause a new value to be created for the button. This enables you to replace any selection that you know to be unlucky with an alternative.

Note 

Undoubtedly, anyone who profits from using this applet will have immense feelings of gratitude and indebtedness towards the author, who will not be offended in the slightest by any offers of a portion of that success, however large!

Alternative Event Handling Approaches

As we indicated in the discussion, there are various approaches to implementing listeners. Let's look at a couple of other ways in which we could have dealt with the control button events.

Instead of passing a constant to the listener class constructor to identify which button was selected, we could have used the fact that the event object has a method, getSource(), that returns a reference to the object that is the source of the event. To make use of this, a reference to both button objects would need to be available to the actionPerformed() method. We could easily arrange for this to be the case by adding a couple of fields to the Lottery class:

JButton pickButton = new JButton("Lucky Numbers!");
JButton colorButton = new JButton("Color");

The inner class could then be defined as:

  class HandleControlButton implements ActionListener {
    // Handle button click
    public void actionPerformed(ActionEvent e) {
      Object source = e.getSource();               // Get source object reference

      if(source == pickButton) {                   // Is it the pick button?
          int[] numbers = getNumbers();            // Get maxCount random numbers
          for(int i = 0; i < numberCount; i++)
            luckyNumbers[i].setValue(numbers[i]);  // Set the button values

      } else if(source == colorButton) {           // Is it the color button?
        Color color = new Color(
                     flipColor.getRGB()^luckyNumbers[0].getBackground().getRGB());
        for(int i = 0; i < numberCount; i++)
          luckyNumbers[i].setBackground(color);  // Set the button colors
      }
    }
  }

We no longer need to define a constructor, as the default will do. The actionPerformed() method now decides what to do by comparing the reference returned by the getSource() method for the event object with the two button references. With the previous version of the listener class, we stored the ID as a data member, so a separate listener object was needed for each button. In this case there are no data members in the listener class, so we can use one listener object for both buttons.

The code to add these buttons in the init() method would then be:

    // Add the two control buttons
    Dimension buttonSize = new Dimension(100,20);
    pickButton.setPreferredSize(buttonSize);
    pickButton.setBorder(BorderFactory.createRaisedBevelBorder());

    colorButton.setPreferredSize(buttonSize);
    colorButton.setBorder(BorderFactory.createRaisedBevelBorder());

    HandleControlButton controlHandler = new HandleControlButton();
    pickButton.addActionListener(controlHandler);
    colorButton.addActionListener(controlHandler);

    controlPane.add(pickButton);
    controlPane.add(colorButton);
    content.add(controlPane);

The only fundamental difference here is that we use one listener object for both buttons.

There is another possible way to implement listeners for these buttons. We could define a separate class for each listener – this would not be unreasonable as the actions to be performed in response to the semantic events for each button are quite different. We could use anonymous classes in this case – as we discussed back in Chapter 6. We could do this by adding the listeners for the button objects in the init() method like this:

    // Add the two control buttons
    Dimension buttonSize = new Dimension(100,20);
    pickButton.setPreferredSize(buttonSize);
    pickButton.setBorder(BorderFactory.createRaisedBevelBorder());

    colorButton.setPreferredSize(buttonSize);
    colorButton.setBorder(BorderFactory.createRaisedBevelBorder());

    pickButton.addActionListener(
       new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           int[] numbers = getNumbers(); 
           for(int i = 0; i < numberCount; i++)
             luckyNumbers[i].setValue(numbers[i]);
         }
       });

    colorButton.addActionListener(
       new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           Color color = new Color(flipColor.getRGB()^luckyNumbers[0]
                                                .getBackground().getRGB());
           for(int i = 0; i < numberCount; i++)
             luckyNumbers[i].setBackground(color);
         }
       });

    controlPane.add(pickButton);
    controlPane.add(colorButton);
    content.add(controlPane);

Now the two listeners are defined by anonymous classes, and the implementation of the actionPerformed() method in each just takes care of the particular button for which it is listening. This is a very common technique when the action to be performed in response to an event is simple.

Handling Low-level and Semantic Events

We said earlier in this chapter that a component generates both low-level and semantic events, and you could handle both if you want. We can demonstrate this quite easily with a small extension to the Lottery applet. Suppose we want to change the cursor to a hand cursor when it is over one of the selection buttons. This would be a good cue that you can select these buttons individually. We can do this by adding a mouse listener for each button.

Try It Out – A Mouse Listener for the Selection Buttons

There are many ways in which you could define the listener class. We'll define it as a separate class, called MouseHandler:

// Mouse event handler for a selection button
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;

class MouseHandler extends MouseAdapter {
  Cursor handCursor = new Cursor(Cursor.HAND_CURSOR);
  Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);

  // Handle mouse entering the selection button
  public void mouseEntered(MouseEvent e) {
    e.getComponent().setCursor(handCursor);    // Switch to hand cursor
  }
  // Handle mouse exiting the selection button
  public void mouseExited(MouseEvent e) {
    e.getComponent().setCursor(defaultCursor); // Change to default cursor
  }
}

All we need to do to expedite this is to add a mouse listener for each of the six selection buttons. We only need one listener object and after creating this we only need to change the loop in the init() method for the applet to add the listener:

int[] choices = getNumbers();            // Get initial set of numbers   
MouseHandler mouseHandler = new MouseHandler();    // Create the listener
for(int i = 0 ; i<numberCount ; i++) {
  luckyNumbers[i] = new Selection(choices[i]);
  luckyNumbers[i].addMouseListener(mouseHandler);
  buttonPane.add(luckyNumbers[i]);
}

How It Works

The mouseEntered() method will be called when the mouse enters the area of the component with which the listener is registered, and we can then change the cursor for the component to a hand cursor. When the cursor is moved out of the area occupied by the component, the mouseExited() method is called, and we restore the default cursor.

There are just two extra statements in init() that create the listener object and then add it for each selection button within the loop. If you recompile the applet and run it again, a hand cursor should appear whenever the mouse is over the selection buttons. Of course, you are not limited to just changing the cursor in the event handle. You could highlight the button by changing its color for instance. You could apply the same technique for any kind of component where the mouse is the source of actions for it.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor