CS 10: Winter 2016

Lecture 8, January 20

Code discussed in lecture

Lab Assignment 2, Short Assignment 5

Lab Assignment 2 is due next Wednesday, January 27. Your section leader will be discussing this lab assignment in detail in this week's section. Although attending your section is optional, I highly recommend showing up this week. The hour you spend in section will save you more than an hour later in the week.

Also, Short Assignment 5 is due on Friday.

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, and 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.

**Remember that 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, we'd need to do the following:

We won't cover this approach in lecture, Lewis & Loftus did precisely this in an example in their Java 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 approach. The Canvas class implements the interfaces and is added to the content pane and to both of the calls to add listeners.

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. Notice also the call to setBackground on the Canvas object; this method is inherited from JPanel.

The applet's 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 a 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.

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, a 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, I wanted purple text, and there is no Color.purple provided. Instead, I made purple myself by constructing a Color object. For purple, I 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.) Then 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.