Lecture 15

Agenda

Announcements

AWT

As you discovered in the BoxBall lab, drawing even simple graphic controls can be painful and tedious. How many times did you type

private final static double .... = ...;

while writing that program to set up constants describing the locations of the buttons, the dimensions of the buttons and the place where the text in the buttons belonged?

Now, imagine how much worse it could be if you wanted to include a more sophisticated interface for BoxBall. For example:

  1. We might just want fancier buttons (buttons with shading and the ability to blink when you click them).
  2. Demo: Boxball using Buttons

    This is a lot like our original program except that the buttons are fancier and they do things like change color when you press them.

  3. Another possibility would be to replace the buttons with a drop-down menu.
  4. Demo: Boxball using a Choice/Menu

    Here, the drawing work would be spread out between the begin method and the onMousePress, onMouseDrag and onMouseRelease methods.

  5. We might want a scrollbar instead of buttons so that we could adjust the level more precisely.

Demo: Boxball using a Scrollbar

Don't worry about how to make the scrollbar work. Just think about how hard it would be to draw it in the first place. You would have to place the little arrows on the ends, draw the regions with different shading, etc.

Fortunately, these seemingly complex user interface objects can be created easily in Java (not so in some other languages!).

    1. Create the component (a construction),
    2. Place the component in the desired position on the display (Note: We say "display" rather than "canvas". Eventually, we will see that the canvas itself is a Component).
    3. Write code that responds to events involving the component (i.e. something like onScrollBarMove instead of onMouseMove)
    4. Tell the AWT where to find the method(s) that should be invoked when the component is used.

Scrollbars

We begin with scrollbars.

Warning: I call them scrollbars, but some say that they should be called sliders unless they are actually being used to scroll something.

We start with scrollbars because:

If we look at the code for Boxball Using a Scrollbar, we find three statements responsible for creating the Scrollbar, displaying the scrollbar, and telling the AWT where to find the method to invoke when the method is used:

         // create and display the game-level scrollbar
         level = new Scrollbar(Scrollbar.VERTICAL, EASY_LINE_Y, 1, HARD_LINE_Y,
                               EASY_LINE_Y); 
         add (level, BorderLayout.EAST);
         // ask to be notified when scrollbar is used
         level.addAdjustmentListener(this); 


Step 1. Create the component (a construction)

First, we construct a Scrollbar. You might expect that the parameters of the constructor to describe things like the width of the scrollbar, it's position within the window, etc. It turns out that this is not the case. Let's see what the Scrollbar constructor parameters mean. The scrollbar is somehow going to determine the position of the boundary above which we have to drop the ball. It would be nice if, after the scrollbox is moved, we could just ask the scrollbar (using some method) where the line should be placed. This will only be possible if the scrollbar knows the allowable range of boundary line positions. This is what (most of) the parameters to the construction are for.

Step 2. Place the component in the desired position on the display

Unlike objectdraw graphic objects, AWT component do not appear on the screen as soon as you create them. They only appear when you add them. The add determines where they appear. If we change the "EAST" in the "add" to "WEST", the scrollbar moves. If we change the "WEST" to "SOUTH" it appears in a very unexpected place (and size). Clearly, the construction does not determine the Scrollbar's dimensions.

If we resize the window, we see one reason why the AWT doesn't make or even let us specify the size of the scrollbar in the construction. The AWT automatically adjusts the size of the component to fit "well" in the window. Notice that the size of the scrollbar changes when we resize the window, but the amount it moves our line and resizes our box remain the same.

The add method is what actually places a component on the screen. We will see other variants later, but for now, all you can specify for the location of the component is one of the four compass points.

Step 3. Handling Scrollbar Events

Our third step was to write an event handler. In this case, it is the adjustmentValueChanged method at the bottom of Boxball.

The code for the adjustmentValueChanged method of Boxball is:

public void adjustmentValueChanged(AdjustmentEvent e) { 
    startingLine.setEndPoints(PLAY_AREA_LEFT, level.getValue(),
                              PLAY_AREA_LEFT+PLAY_AREA_WIDTH, level.getValue() );
    box.setSize(EASY_BOX_WIDTH - BOX_SHRINKAGE
                       *( EASY_LINE_Y - level.getValue()) / (EASY_LINE_Y - HARD_LINE_Y));
    startingLineY = level.getValue();
} // end adjustmentValueChanged

Step 4. Tell our Scrollbar to report its move events to our adjustmentValueChanged method.

To do this, we call the Scrollbar's addAdjustmentListener method and pass it an instance of a class that contains the adjustmentValueChanged method. Here, we pass it this, since it is our window controller class that contains the adjustmentValueChanged method.

The addAdjustmentListener method certainly isn't expecting a class of type Boxball or even a WindowController. In fact, it wants to be passed something of type AdjustmentListener.

AdjustmentListener is an interface.

Sun Java Docs: AdjustmentListener interface

In order to pass in our Boxball class as the parameter to addAdjustmentListener, we have to make Boxball implement the AdjustmentListener interface. (It does.)

Here, the class that implements AdjustmentListener also happens to be the class that created our Scrollbar and everything else. This is not always the case, and any class that implements AdjustmentListener can be used.

Summary of the steps for the Scrollbar example:

Buttons

Step 1. Buttons are very easy to create, but we see that add is a bit more complex.

First, to create a Button:

    Button easy = new Button("Easy");

Where the parameter simply specifies the text label for the Button.

Step 2. In this program, we need three buttons, one for each level. To add these to the screen, we need to use three different compass points.

So what we do is to create a Panel, and add the buttons to the panel. The panel will do something intelligent to arrange the buttons. We will see later how to have more control over exactly how the buttons are displayed. The panel is then added to the screen at a compass point, like the items we saw previously.

The code for steps 1, 2, and 4 in the begin method is:

         // creates the panel and buttons for the difficulty-level 
         Panel buttonPanel = new Panel();
         easy=new Button ("Easy");
         medium=new Button ("Medium");
         hard=new Button ("Hard");
         buttonPanel.add (easy);
         buttonPanel.add (medium);
         buttonPanel.add (hard);
         add (buttonPanel, BorderLayout.SOUTH);

         // request notification if buttons are clicked
         easy.addActionListener (this);
         medium.addActionListener (this);
         hard.addActionListener (this);

Step 4. Above, we tell each Button about its event handler class using the method addActionListener. The Boxball class must include the method actionPerformed that implements ActionListener interface.

Step 3. Handling AWT button events is very similar to the original Boxball class.

When we implemented the buttons our self in onMousePress, we used the Location point parameter to determine which button was pressed.

In the actionPerformed method of our Boxball game with buttons. We will use the ActionEvent parameter event to determine which button was clicked. Basically, we just call getSource on the event passed in, see which button caused the event, and act accordingly.

     public void actionPerformed (ActionEvent event) {
      
         if (event.getSource() == easy) {
            startingLine.setEndPoints(new Location(PLAY_AREA_LEFT, EASY_LINE_Y), 
                            new Location(PLAY_AREA_LEFT + PLAY_AREA_WIDTH, EASY_LINE_Y));
            startingLineY = EASY_LINE_Y;
            box.setSize(EASY_BOX_WIDTH);
         }
         else if (event.getSource() == medium) {
            startingLine.setEndPoints(new Location(PLAY_AREA_LEFT, MEDIUM_LINE_Y), 
                           new Location(PLAY_AREA_LEFT + PLAY_AREA_WIDTH, MEDIUM_LINE_Y));   
            startingLineY = MEDIUM_LINE_Y;
            box.setSize(MEDIUM_BOX_WIDTH);
        }
         else if (event.getSource() == hard) {
            startingLine.setEndPoints(new Location(PLAY_AREA_LEFT, HARD_LINE_Y), 
                            new Location(PLAY_AREA_LEFT + PLAY_AREA_WIDTH, HARD_LINE_Y));
            startingLineY = HARD_LINE_Y;
            box.setSize(HARD_BOX_WIDTH);
         }
      } // end actionPerformed

For AWT components in general, the same event handler can be used for multiple components, so the parameters to AWT event handlers are used to find out which component generated the event. All of the event handlers we'll use have a parameter of a type that implements the EventObject interface. This interface includes a method getSource that returns the source of the event.

Choices

A pop-up menu is created using an AWT Choice object. These pop-up menus are also known as "selection boxes" or "drop-down boxes."

Creating these menus is simpler, but involves an extra method.

The constructor for a Choice takes no parameters. It simply creates an empty menu.

	popup = new Choice()

Then, to fill the menu in with items we issue statements of the form:

popup.add("Easy");

The menu is again placed on the display using the add primitive.