Oct 2: More GUIs.


Code discussed in lecture

Classes that Act as Listeners

In ClickAMac.java, we had the ClickAMac class, which really is the applet, implement the MouseListener interface. There's nothing wrong with that, but there is another way. Let's create a new class whose sole purpose is to implement the MouseListener interface. We'll create an object of this class to pass to Java as the listener. ClickAMac2.java illustrates this approach.

We create a class called LocalMouseListener. It is an inner class, because it is declared inside another class. It is private, because we do not want it seen outside of the ClickAMac2 class. It implements the MouseListener interface, using the exact same methods as in ClickAMac.java.

An inner class can access all the instance variables of the class it's declared in (its outer class), even if the instance variables in the outer class are private.
Therefore, Because LocalMouseListener is an inner class, it can see all of the instance variables, even the private ones, of ClickAMac2.

Now the init method of the ClickAMac2 class creates a LocalMouseListener object called listener and passes it to addMouseListener. By the way, there was not actually any need to have named this LocalMouseListener object; we could replace the lines

LocalMouseListener listener = new LocalMouseListener(); addMouseListener(listener); by the single statement addMouseListener(new LocalMouseListener());

Having LocalMouseListener be an inner class is not absolutely necessary, but it is very convenient. Why? Because we need the the mouseClicked method to be able access the instance variable clickPoint in ClickAMac2. For a separate class that is not an inner class to do that, several things need to be done:

  1. The separate listener class needs to have a reference to an object containing the instance variable clickPoint. This scheme requires somehow passing this to the listener class.

  2. Even doing that is not sufficient, because clickPoint is private, so attempting to write to theApplet.clickPoint (where theApplet is a reference to the object containing clickPoint) will result in an error. Therefore there has to be a new method written to set clickPoint.
We won't cover this approach in lecture, Lewis & Loftus did precisely this in an example in their textbook: Dots.java and DotsMouseListener.java. You can see how it is done. (I left these unchanged, so they extend Applet instead of JApplet.) In practice we will almost always prefer to use the applet itself (as in ClickAMac.java) or to create an inner class (as in ClickAMac2.java).

The MouseMotion events

The DragAMac.java program demonstrates how to deal with mouse motion. The MouseMotion events are handled by the MouseMotionListener interface, with two methods: mouseMoved and mouseDragged. Like the methods in the MouseListener interface, these two methods take one parameter, which is a reference to a MouseEvent object. In all objects registered by calling addMouseMotionListener, the method mouseMoved is called when the mouse moves with the button up, and the method mouseDragged is called when the mouse moves with the button depressed.

This program uses an inner class, LocalMouseListener, containing two methods to handle two different events. The LocalMouseListener class implements both the MouseListener and MouseMotionListener interfaces.

Both methods perform the same action: they update dragPoint to be the current location of the mouse passed in event, and then they repaint the window. (In fact, we just made mouseDragged call mousePressed.) Thus, the Mac will be drawn at the location where the mouse is pressed, and it will be redrawn continuously as the mouse is dragged.

The Java folks decided that these event types are different enough that they should be part of different interfaces. That's why we have both the MouseListener and MouseMotionListener interfaces. But that doesn't mean that we have to create two different objects to listen for these events. Instead, we created just a LocalMouseListener class that implements both interfaces and defines all the methods of both classes.

Because we wanted just one LocalMouseListener object to handle the two events, we have registered the listener for the two events with two different calls (addMouseListener and addMouseMotionListener). We need to assign a reference to the LocalMouseListener object to a local variable in the init method, passing that reference to the two calls. Instead, we could have done without the local variable, but we would have had to create two LocalMouseListener objects and registered them separately:

addMouseListener(new LocalMouseListener()); addMouseMotionListener(new LocalMouseListener());

This approach works, but we prefer to create just the one LocalMouseListener object.

There is another class that can handle events: the Canvas class. DragAMac2.java demonstrates this. The Canvas class implements the interfaces and is added to the content pane and to both of the calls to add listeners.

Interesting thing to try: Comment out the call to super.paintComponent and see what happens. This is why you should always call super.paintComponent as the first line in paintComponent.

RubberLines

The program RubberLines.java is from a textbook by Lewis & Loftus. It is a good example of something done commonly in graphics.

This program also uses both MouseMotion and Mouse events. Like ClickAMac.java, the applet itself is the listener, but like DragAMac.java, the listening object implements both the MouseListener and MouseMotionListener interfaces.

The instance variables are called point1 and point2. point1 is where the mouse is pressed, and point2 is the current location as the mouse is dragged. Therefore our two added methods do slightly different things. The method mousePressed updates point1, and the method mouseDragged updates point2 and calls repaint. (What do you think would happen if mousePressed also called repaint? Actually, the behavior is a little bit subtle...we'll go over it in lecture. But you can make the change yourself and experiment.)

This paint method simply draws an line from point1 to point2, after setting the drawing color. These simple routines interact in a very cool way. Because point1 stays fixed and point2 moves, and because each mouseDragged call invokes repaint, thus clearing the window and then painting, we get a line with one fixed end while the other appears to be attached to the mouse, causing a "rubberbanding" effect.

Animations with timers

Some animations require us to slow the action down a bit. For example, here's a new version of the PsychBoxes applet, which we call PsychBoxes2.java. Here, we want to redraw all 55 boxes every 1/10th of a second (which is the same as every 100 milliseconds, since a millisecond is 1/1000th of a second).

To get this periodic action to occur, we use a Timer object. A Timer object calls a method at regular time intervals. In PsychBoxes2.java, the time interval is 100 milliseconds, which is the value of the final variable DELAY that is given to the Timer constructor. The Timer constructor also takes a reference to an object that implements the ActionListener interface. This interface has just one method, actionPerformed. In this example, a private inner class, LocalPsychListener, implements the ActionListener interface and therefore has an actionPerformed method.

In this applet, the Timer object will call the LocalPsychListener's actionPerformed method every 100 milliseconds. This method increments the iteration number (the instance variable iter) and then redraws all 55 boxes by calling repaint. The repaint eventually results in a call to paintComponent to draw the 55 boxes. Then the applet goes "back to sleep" until the Timer calls actionPerformed again. If it seems like there's an infinite loop in here somewhere, you're right...but we haven't written it. It's implicit in the use of an event-driven applet.

There's a little more going on in this applet:

Unlike the mouse events, for which we had to call addMouseListener to register listeners, we don't have to call addTimerListener to register action listeners. The action listener is given as a parameter to the Timer constructor. You can add other listeners, if you want to, by calling addActionListener, and you can alter the delay of a Timer by calling setDelay. See the Java documentation for details.

Bonus coverage: The Rebound applet

We won't go over this in lecture, but another example of an applet with a timer is the Rebound.java demo from a Lewis & Loftus textbook. Here, we have a bouncing ball. (Actually, a bouncing smiley face. Place happyFace.gif in your project before running the applet.) This applet's repaint method just draws the face image. A method that is run periodically updates the position of the image and calls repaint. We'll look at this applet in a moment.

Again, we use a Timer object, this time with a time interval of 20 milliseconds, as represented by the final variable DELAY, which is given to the Timer constructor. The Timer constructor also takes a reference to an object that implements the ActionListener interface. This interface has just one method, actionPerformed. In the applet, the class ReboundActionListener implements the ActionListener interface and therefore has an actionPerformed method.

In this applet, the Timer object will call the ReboundActionListener's actionPerformed method every 20 milliseconds. This method moves the image's position, possibly reverses its direction in either dimension, and then calls repaint. repaint clears the applet window and draws the image at its new location. Then the applet goes "back to sleep" until the Timer calls actionPerformed again. As in the PsychBoxes2.java applet, mouse clicks stop and restart the applet.

More GUI Components

So far, our applets have only drawn or responded to mouse, keyboard, and timer events. Graphical User Interfaces (GUIs) have many other things to interact with, however—buttons, menus, text fields, scroll bars, and so on. It is time to learn how to interact with these other components.

As we said before, component takes up visual space in the GUI. Most components generate events that the program must deal with. (Just like you've been writing programs that deal with mouse clicks or timer events.) That is how the program responds to events such as clicking on a button or selecting an item in a menu.

We noted before that components are added to containers. There are different ways to place the components within a container. The layout we'll use for now is called flow layout. The components are laid out in rows, left to right, with a new row started when the next component won't fit in the current row. Later on, we will learn a number of other ways to lay out components.

We have modified programs from a textbook by Lewis & Loftus to show you some examples of simple GUIs. The first is Fahrenheit.java.

Note that the class Fahrenheit has two instance variables. One is a reference to a JLabel, and the other is a reference to JTextField.

A JLabel is an area of text. The program can set and change its contents, but the user of the program cannot. It is used for labeling other components and for output to the user. Although it seems like you could get the same effect by calling the good old drawString method, all drawString does is write some text in the window. The layout manager does not leave any space for the text written by a call to drawString. In contrast, the layout manager does leave space for a JLabel. (Another difference is that if you use drawString to draw two text strings one atop the other, they actually overlay.)

A JTextField is a bit more complex. It allows the user to enter input. When the user presses the "enter" or "return" keys an ActionEvent is generated. An ActionListener will handle that event. Recall that ActionListener is an interface consisting of one method, whose header is

public void actionPerformed(ActionEvent event) We have already used the ActionListener interface for the Timer class.

In Fahrenheit.java, the init method sets everything up. It first constructs three JLabels and a JTextField. The actual parameters for the JLabels are the text to appear in the labels.

We set the colors of the text in the JLabels by calling the setForeground method on each JLabel. setForeground takes as a parameter a reference to a Color. For the result, we wanted purple text, and there is no Color.purple provided. Instead, we made purple myself by constructing a Color object. For purple, we used half red and full blue.

The parameter in the JTextField constructor gives the number of characters in the input field, which is five in this applet. We call the setBackground method to make the JTextField's background yellow, and the call to setForeground will make any typed-in text appear in red. The init method then assigns an action listener (this, i.e., the applet itself) to listen to the JTextField via the call

fahrenheit.addActionListener(this); Notice that now there are two objects involved in "registering" the listener: So, the above call says, "When an ActionEvent occurs within the object referenced by fahrenheit, I want you to call the method named actionPerformed within the current class (Fahrenheit).

This situation is a little different from how we registered listeners before. Previously, the events occurred in the applet itself. Now they don't. They occur in the JTextField object, and that's why we have to give the reference fahrenheit to the left of the dot in the addActionListener call. The applet itself is the listener, and hence the parameter this.

So far, we have only added a single object (the canvas) to the applet's content pane. Now we will add four. We add them in the order in which they are to appear. First, we have the assignment

Container cp = getContentPane(); which sets cp to reference the content pane for the applet. The line cp.setLayout(new FlowLayout()); tells the content pane that we are going to use flow layout when we add components. Note that we create a new FlowLayout object (whatever that is) and then tell the content pane that this FlowLayout object will handle the layout duties. Apparently, just providing a reference to a FlowLayout object as the actual parameter to setLayout suffices to indicate that we are using flow layout. (Note that we don't even need to create a local variable to hold this reference to a FlowLayout object. Since new gives back this reference, we just pass it directly to setLayout.) The four calls to add, cp.add(inputLabel); cp.add(fahrenheit); cp.add(outputLabel); cp.add(resultLabel); tell the applet the order in which to lay out the components, using flow layout.

Finally, the applet sets the background color to pink, and it sets the applet size. If we change the applet width, then the four GUI components are laid out in a different way. Make the applet wide enough, and all four appear in the same row. Make the applet narrow enough, and they appear in one column.

Note that there is no paintComponent method provided. The GUI components are drawn automatically by repaint. The JApplet paint method takes care of drawing all the GUI components.

The actionPerformed method handles the event in which the user types into the text field. It is called when the "enter" or "return" key is pressed. The method getText gets the text from the text field. What is returned by getText is a String reference, and this String is then converted to an integer via a call to Integer.parseInt. Recall that Integer is the wrapper class for ints, and it has many useful static methods, including this one and toString. The conversion to celsius is done, and then the command

resultLabel.setText(Integer.toString(celsiusTemp)); displays the output in the label resultLabel. (Note that I added a space at the end of "N/A " in the creation of this field to make more room for later outputs.)

Notice that the references inputLabel and outputLabel are local to the init method, rather than being instance variables. It would not have been a bug to have made them instance variables, but it would have been poor design, since the applet has no need to refer to these variables outside of init method. Therefore, it is cleaner to confine their use to just the method that needs them. Because the variables resultLabel and fahrenheit are needed in the actionPerformed method, they must be instance variables.

Drawing Rectangles

The class DrawRects.java allows us to drag out and draw rectangles on the screen. Here, we use an expanded Rect class in Rect.java. Each Rect object knows its upper left corner, width, height, and color. It also knows how to draw itself.

The DrawRects class has the user drag out rectangles, and it adds each of them to the ArrayList named boxes. It does this using three methods that handle mouse events and mouse-motion events. The method mousePressed remembers the place where the mouse was pressed in pressedPoint, and it constructs a new currentRect with no width or height at the place where the mouse is pressed. It colors this new rectangle black.

The method mouseDragged updates currentRect so that it has pressedPoint and the current mouse position as two opposite corners. Note the use of Math.min to get the upper left corner and of Math.abs to compute the height and width of this rectangle. Because this method responds to mouse dragged events, the rectangle is updated (almost) continuously. The screen is repainted after each update.

The method mouseReleased completes the operation. It changes the color of the current rectangle to one of four that are saved in the array color. (Note the use of an initializer list when this array is defined.) It then adds the current rectangle to the set boxes. Finally it sets currentRect to null to indicate that the Rect object that currentRect had a reference to is now in the data structure boxes and should not be used any more for getting new rectangles from the user. Both this method and mouseDragged check to see that the currentRect is not null before doing anything to it.

The drawing is done on a Canvas inner class similar to the ones that we have seen before. Its paintComponent first draws a black rectangle around the canvas. (The "-1" on the width and height keeps the pen from being off of the canvas on the right side and bottom). It then draws all the saved rectangles. Finally it draws the rectangle currently being dragged (if any).

There is also a contructor for Canvas, which is something new. This constructor just sets the preferred size of the Canvas component. It does this by calling setPreferredSize and handing it a Dimension object. This is what you call on a JPanel instead of setSize.

There is another method in Canvas - actionPerformed. The Canvas class implements ActionListener. The actionPerformed method will handle clicks on the "clear" button. All it does it to clear boxes and repaint.

Finally we can look at the init method of DrawRects. It adds this as both a mouseListener and a mouseMotionListener. It sets the applet size. It gets the content pane and says that it should use FlowLayout. It adds the canvas to the content pane.

It then creates a JButton with the label "Clear." It sets the button's background color and adds the canvas as an actionListener. Note that these calls are made on clearButton rather than this. Finally it adds the button to the content pane. Because there is not enough room next to the canvas it is placed centered below the canvas.