Session 17

Inside an Event-Driven Program


CS 2530
Intermediate Computing


An Opening Exercise: You Tell Me...

What color is the cannon ball?

How does CannonGame draw the cannon?

What do lv, lh, sv, and sh mean? What about sx and sy?

How does the cannon ball follow the prescribed angle, when we don't pass the angle to the cannon ball?

How does the cannon ball reverse direction?

How does the game know that the ball has hit something (either the target or the floor)?

How does the program terminate?



You Told Me...

The cannon ball...

The CannonGame draws the cannon...

lv, lh, sv, and sh mean... sx and sy mean...

The cannon ball follows the prescribed angle, even though we don't pass the angle to the cannon ball, by...

The cannon ball reverses direction by...

The game knows that the ball has hit something (either the target or the floor) by...

The program terminate...



Events and Objects

a snapshot of the Cannon Game at the start

CannonGame adds two new kinds of object to our games:

The user can cause use these widgets to create events at any time. The user now controls the flow of the program, not the game world. Control of the game changes from

Do this, then that, then this other thing.

to

Respond to the user's action.

In order for the program to respond to the user's action, it must also include another new kind of object, whose sole responsibility is to watch, or listen, for the user to cause an event.



A Quick Exercise: Another Kind of Message

Modify the CannonGame so that it displays both the angle of the cannon and the position of the cannon ball. As the ball moves through the air, the message should update automatically.

~~~~~~~~~~~~~~~~~~~~

Where must our new code go?

paint() calls writeMessage(), which refreshes the message on the screen after the cannon ball moves and paints. The frame needs to update the message when the ball moves. So we need to change drawCannonBall(). How about if we copy the message up from the slider listener and add a string:

    message = "Angle: " + angle + "...... " +
              "(" + cannonBall.x() +  ", " + cannonBall.y() + ")";

The slider listener updates the message, too, when the user adjusts the position of the scrollbar. So we need to change its adjustmentValueChanged() method similarly.

How does the at work? Almost. Notice that the y coordinate gets smaller as the ball rises and larger as the ball falls. We need to dy() it!

Now our copy-and-paste operation comes back to get us. We have to make the change twice. Let's factor an updateMessage() method out of our two update sites, fix that code...

    private void updateMessage()
    {
      message = "Angle: " + angle + "...... " +
                "(" + cannonBall.x() +  ", " + dy(cannonBall.y()) + ")";
    }

... and change the original sites to call our new method.

Good. But there is still one problem. The user can adjust the slider at any time, even when there is no cannon ball! How can we fix that? Guard the creation of the part of the message that displays the ball's position:

    String cannonBallPosition = "";
    if ( cannonBall != null )
      cannonBallPosition = "...... " +
        "(" + cannonBall.x() +  ", " + dy(cannonBall.y()) + ")";

    message = "Angle: " + angle + cannonBallPosition;

Looking good. What remains to fix or improve?



Widgets, Listeners, and Frames

Each "active" component in our frame needs a listener to wait for the user to manipulate the component and then relay the event to the application.

In the case of the scrollbar, we define an instance variable:

    private Scrollbar slider;

We also create a new kind of listener, one that sends the messages we we want to the frame and its components:

    private class AngleSliderListener implements AdjustmentListener
    {
      public void adjustmentValueChanged( AdjustmentEvent e )
      {
        angle   = angleSlider.getValue();
        updateMessage();
        repaint();
      }
    }

Then, in the frame's constructor, we create a scrollbar, we create a listener, and we connect the parts:

    slider = new Scrollbar( Scrollbar.VERTICAL, angle, 5, 0, 90 );
    slider.addAdjustmentListener( new ScrollBarListener() );
    add( "East", slider );

How do we know what values to send to the Scrollbar constructor when we create it? We look it up in the Java API.

For the "fire!" button, we follow a similar set of steps:

We don't create an instance variable for the button, though. We use a temporary variable:

    Button fire = new Button( "shoot a cannonball" );
    fire.addActionListener( new FireButtonListener() );
    add( "North", fire );

Why? How can that work?

Our program never talks to the button. Only the user does, by pressing it. Unlike a slider, a button has no state. So the program never has to send it a query like angleSlider.getValue().

Now surely some object in our program talks to the button. It is repainted like the slider and the frame window itself. But our code does not. Once added to the frame, the button acts independent of the CannonGameFrame class. So we don't need to store it in an instance variable.

Create instance variables only when you need them.

(Soon, we will dig deeper into the AWT and figure out which object is talking to our button, and how.)



Listeners Implement Interfaces

Notice:

    ScrollBarListener  implements AdjustmentListener {...}
    FireButtonListener implements ActionListener     {...}

As we saw back in our first program, an interface is a list of responsibilities. It specified a set of messages to which an object promises to respond. It is a little like a class with no methods, only method signatures.

We define an interface just like a class, with a semi-colon in place of the method bodies. The AdjustmentListener interface looks like this:

    public interface AdjustmentListener
    {
      public void adjustmentValueChanged( AdjustmentEvent e );
    }

Why do we have to use these two interfaces in our cannon game program?

When the user adjusts a slider, the Java run-time system does not know that means. It only knows that the user has performed an action. The system has to tell our program that something has happened. It does that by sending a message.

On the other hand, our program knows what the adjustment means, but it doesn't know the mechanics of user interaction (or want to). Our program just need to know when the user has taken the action. It does that by receiving a message.

Which message? That has to be defined up front, because the code for the object sending the message was written at a different time than the code for the object receiving it. The interface defines the contract that makes it possible for two objects that don't know anything else about each other to communicate.

So, if we want our program to be made aware of the event, we need to have an object "listening" for a particular message. The AngleSliderListener must listen for an adjustmentValueChanged message, and a FireButtonListener must listen for an actionPerformed message.

More generally, why do we use interfaces?

Think about this some more. Two objects that don't know anything about one another, that were implemented and created at different times by different people, and that can't see inside one another communicate by sending and receiving a message.

unknown objects communicate with a message

They are truly independent of one another, yet they collaborate to solve a problem.

That is object-oriented programming.



Listeners as Inner Classes

We defined our listener classes inside the class definition for CannonWorldFrame:

    public class CannonGameFrame extends CloseableFrame {
      ...
      private class FireButtonListener implements ActionListener {
        public void actionPerformed( ActionEvent e ) { ... }
      }

      private class AngleSliderListener implements AdjustmentListener {
        public void adjustmentValueChanged( AdjustmentEvent e ) { ... }
      }
    }

We call such a class, creatively, an inner class. Inner classes have a particular advantage over other classes in a Java program. Because they are inside the definition of the "outer class", they are allowed to access private members of that class. This is convenient especially for listeners, which are really pure helpers to the frame class.

This is the key. Listeners are really an implementation detail of the frame object. So they are generally best defined with the rest of the object's implementation detail, private inside the class body. We wouldn't want to violate the object's control over its internal state for another object.

That said, we can define listeners using independent classes outside the frame's body. Try it!



Class Miscellaneous

On Pousse and Homework 6

On the midterm:



Wrap Up



Eugene Wallingford ..... wallingf@cs.uni.edu ..... October 16, 2012