Course Links

Exercises

Resources

External

This lab will introduce you to various forms of event handlers in Java, and also cover a little bit of component layout. Before you begin, familiarize yourself with BullseyeGUI.java. At the moment, this doesn't do anything active, but we will be adding behavior throughout the lab. You should also review JBullseye.java, which is a subclass of JComponent that draws a bullseye shape consisting of concentric circles of color. In particular, note the cycle() and invert() methods, which modify the colors in the bullseye, and which we will soon associate with the appropriate buttons.

Button Events

The first thing we will do with our GUI is activate the buttons. This text will go through the activation of the Cycle button step by step. Then you will take similar steps to activate the Invert button.

We need to add an event listener with a method that will be called when the Cycle button is pressed. This event listener must be a class that implements the ActionListener interface. The only requirement for ActionListener is the includion of an actionPerformed() method. Sometimes you will see programs where the GUI class itself does double duty as a listener -- i.e., it implements ActionListener, and has an actionPerformed() method. This approach runs into problems when there are multiple buttons: each button will end up activating the same actionPerformed() method. At the same time, it usually doesn't make sense to implement a completely separate class to handle GUI events, since this class only makes sense within the context of the GUI. A nice solution is to use private nested classes -- typically short utility classes that are dfined within the BullseyeGUI class and can therefore access all of its members. Here is the definition of a nested event listener class for the Cycle button:

    /** Event handler for Cycle button */
    class CycleListener implements ActionListener {
        /**
         *  Cycles the colors when the button is pushed.
         *
         *  @param e  Holds information about the button-push event
         */
	    public void actionPerformed(ActionEvent e) {
	        bull.cycle();  // note the reference to the enclosing class's private field
	    }
    }

Put the above code inside BullseyeGUI.java but at the end, after all the other methods (including main). We also need to do one more thing before our button will function: The button must be told about the listener we have created, so it can activate the actionPerformed() method whenever the user clicks. The following code goes inside createComponents() right after the button has been added to the pane:

	cycleButton.addActionListener(new CycleListener());

Note that there is no limit on how many listeners may be attached to a GUI component like a button, and occasionally it is useful to have more than one. Here is the code with the Cycle button handler inserted. Now go ahead and write the Invert button handler yourself, adding it to the code and registering it in the same manner.

Animation and Timers

It is not hard to add simple animations to your GUI programs using events and the Java Swing Timer class. The basic idea is that we will create a Timer instance, which will cause an event to fire either once or repeatedly at some specified interval. The Timer starts in an inactive state, and thereafter its behavior is controlled by calls to its start() and stop() methods.

Because the Timer will be used in several places within the GUI class, it should be a class field. We will also establish a numeric constant specifying how often the Timer should fire (in thousandths of a second).

    /** Used for animation of the bullseye colors */
    private Timer timer;
 
    /** Time between updates is half a second */
    private static final int TIMER_INTERVAL = 500;

We will need to instantiate the Timer object and assign it to our new field. It makes sense to do it in the BullseyeGUI constructor:

	timer = new Timer(TIMER_INTERVAL,new CycleListener());

Note that, as shown above, creation of a Timer takes as arguments the firing interval in milliseconds and an appropriate ActionListener object. Since we already have an ActionListener class that cycles the colors, we can just reuse it.

We have now created a timer, but since it begins in the inactive state and we have no way to turn it on, it isn't much use yet. Add two more buttons, Start and Stop to the layout. The event handlers for these should call timer.start() and timer.stop(), respectively. You will also need to insert code within createComponents() adding the new buttons to the panel.

Mouse Events

Action events are quite useful, applying as they do to buttons, checkboxes, radio buttons, and menu items. However, they are not the only kind of event programs can generate. Another very useful event type is the MouseEvent. A basic mouse handler will implement the MouseListener interface, which is somewhat more complicated than ActionListener. MouseListener requires implementation of five different methods: mouseClicked(), mousePressed(), mouseReleased(), mouseEntered(), and mouseExited(). Not all of these need to do anything, but they must all be defined, possibly with empty bodies. (To make this easier, Java provides a predefined class called MouseAdapter that has all five empty methods already in place. You can create a class that inherits from MouseAdapter, and then simply override with new definitions any methods you want active.)

To see how a MouseListener works, add one to the bullseye component.

	bull.addMouseListener(new SampleMouseListener());

Use this for the definition of the mouse listener:

/** Mouse event handlers */
private class SampleMouseListener extends MouseAdapter {
    /** Click event handler prints a message with the event location */
	public void mouseClicked(MouseEvent e) {
	    System.out.println("Click event at ("+e.getX()+","+e.getY()+")");
	}

    /** Press event handler prints a message with the event location */
	public void mousePressed(MouseEvent e) {
	    System.out.println("Press event at ("+e.getX()+","+e.getY()+")");
	}

    /** Release event handler prints a message with the event location */
	public void mouseReleased(MouseEvent e) {
	    System.out.println("Release event at ("+e.getX()+","+e.getY()+")");
	}

    /** Enter event handler prints a message with the event location */
	public void mouseEntered(MouseEvent e) {
	    System.out.println("Enter event at ("+e.getX()+","+e.getY()+")");
	}

    /** Exit event handler prints a message with the event location */
	public void mouseExited(MouseEvent e) {
	    System.out.println("Exit event at ("+e.getX()+","+e.getY()+")");
	}
}

Now play with the mouse on top of the bullseye and see what kind of events you can generate.

Java also defines MouseMotionListener and MouseWheelListener interfaces for responding to drag events and scroll wheel events, respectively. The MouseAdapter class also implements these interfaces and provides empty event handlers for them. Let's override some of those empty handlers with ones that provide information analogous to the five handlers already written. If you look at the MouseMotionListener interface, you will see that it promises handlers called mouseMoved() and mouseDragged(). Go ahead and write these in your SampleMouseListener class, making them print the mouse location just like the other handlers. Now, before you can see them in action, you'll also have to register the listener using the appropriate JComponent's addMouseMotionListener() method. Do this in createComponents(), right below the line that adds the mouse listener. (Even though the same class is handling both types of mouse events, you need separate statements to tell the bullseye component to report events from each category.)

Of course, these handlers accomplish nothing more useful than providing us with some information about where the event occurred. A more typical mouse handler might use the coordinates of the mouse event to trigger some state change in the component where the event occurred. As an example, clicking twice in the bullseye might cause two colors to be swapped. To accomplish this feat, you will need to make a the following changes. (Try to do this without looking at the help, if you can. Then check your solution.)

  1. Add a method to JBullseye that computes the index of the ring from (x,y) coordinates. The outer ring is index 0. [help]
  2. Add another method to JBullseye that swaps the colors in two rings specified by their indices. [help]
  3. Add an int field named ring to BullseyeGUI to keep track of the first ring clicked. (This is what is referred to as a state variable, because it records the current state or recent history of the GUI.) If the user has not clicked on a ring, its value will be -1.
  4. Modify the mouseClicked() event handler so that it checks the value of ring and performs the appropriate action: [help]
    1. If ring == -1, store the ringID() of the current mouse click instead.
    2. Otherwise, swapColors() between the index stored in ring and the ringID() of the current mouse click, and set ring back to -1.

Layout

Our example so far has used a FlowLayout manager, which arranges the components like the words on a page of text. Sometimes a simple layout manager like FlowLayout cannot give you the flexibility you need to create an attractive GUI. One way to gain more flexibility is to use panels to group and arrange elements. A panel acts as a single component in terms of the layout it fits into. At the same time, it can contain multiple elements in a layout different from that of the parent pane.

The image at right shows the four buttons grouped into a panel that uses a 2x2 GridLayout. The main window pane still uses a FlowLayout. To accomplish this, we must create a panel element and insert the buttons into it instead of the top-level pane.

	JPanel panel = new JPanel();
	panel.setLayout(new GridLayout(2,2));
...
	panel.add(cycleButton);
...
	panel.add(invertButton);
...
	panel.add(startButton);
...
	panel.add(stopButton);
	pane.add(panel);

To do: Modify the button panel so that it looks like the image at right. You will need to use a BorderLayout, and insert the button elements into the BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.EAST, and BorderLayout.WEST positions. Here's an example using the first button:

	panel.add(cycleButton,BorderLayout.NORTH);

To Submit