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.
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:
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.
Even doing that is not sufficient, because clickPoint
is private
, and 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 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).
MouseMotion
eventsThe 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.
When the mouse is pressed, we call the mousePressed
method of the LocalMouseListener
object. This method is part of the MouseListener
interface.
When the mouse is dragged, we call the mouseDragged
method of the LocalMouseListener
object. This method is part of the MouseMotionListener
interface.
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.
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.
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 JLabel
s and a JTextField
. The actual parameters for the JLabel
s are the text to appear in the labels.
We set the colors of the text in the JLabel
s 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:
The event will be generated by activity in the JTextField
referenced by fahrenheit
. That is the object to which we add an action listener.
As the parameter to the addActionListener
call, we give a reference to the object that will actually perform the action. In this case, the object is the applet itself.
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 int
s, 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.