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 SampleGUI.java and SampleGUIWrapper.java. At the moment, these don't do anything active, but we will be adding behavior throughout the lab. You may also wish to review JBullseye2.java, which has been filled out by adding some extra methods for manipulating the colors. In particular, note the cycle() and invert() methods, 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 SampleGUI 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
}
}
We need to add 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 when the button is clicked. The following code goes inside createComponents() right after the button has been added to the pane:
cycleButton.addActionListener(new CycleListener());
Not 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 add the Invert button handler.
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 constant specifying how often the Timer should fire.
/** Used for animation of the bullseye colors */
private Timer timer;
/** Time between updates */
private static final int TIMER_INTERVAL = 500;
We will need to instantiate the Timer object somewhere. It makes sense to do it inside createComponents():
timer = new Timer(TIMER_INTERVAL,new CycleListener());
Note that, as shown above, the constructor takes as its arguments the firing interval in milliseconds and an appropriate ActionListener object. Since we already have an ActionListener 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. The event handlers for these should call timer.start() and timer.stop(), respectively. You will also need to insert code into 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 mouse handler must implement the MouseListener interface, which is somewhat more complicated than ActionListener. It 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, you can create a class that inherits from MouseAdapter, a predefined class that has all five empty methods already in place. Then you simply override the methods you want active by giving them new definitions.)
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()+")");
}
}
Java also defines MouseMotionListener and MouseWheelListener interfaces for responding to drag events and wheel events, respectively. Extend the SampleMouseListener so that it also implements the MouseMotionListener interface. You will need to create mouseMoved() and mouseDragged() methods, which should work in a manner similar to those above.
Of course, these handlers accomplish nothing very useful. 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.)
- Add a method to JBullseye2 that computes the index of the ring from (x,y) coordinates. [help]
- Add another method to JBullseye2 that swaps the colors in two rings. [help]
- Add an int field named ring to SampleGUI 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.
- Modify the mouseClicked() event handler so that it checks the value of ring
and performs the appropriate action: [help]
- If ring == -1, store the ringID() of the current mouse click instead.
- 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
- JBullseye2.java with all changes
- SampleGUI.java with all changes
- SampleGUIWrapper.java unchanged (so I can compile everything if necessary)
- typescript showing compilation and some (not too many!) sample event messages