Oct 2: More GUIs.
Code discussed in lecture
- ClickAMac.java
- ClickAMac2.java
- Dots.java (not discussed)
- DotsMouseListener.java (not discussed)
- DragAMac.java
- DragAMac2.java
- RubberLines.java
- PsychBoxes2.java
- Rebound.java (not discussed)
- Fahrenheit.java
- DrawRects.java
- Rect.java
Classes that Act as Listeners
In ClickAMac.java, we had theClickAMac 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
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:
- The separate listener class needs to have a reference to an object
containing the instance variable
clickPoint. This scheme requires somehow passingthisto the listener class. - Even doing that is not sufficient, because
clickPointisprivate, so attempting to write totheApplet.clickPoint(wheretheAppletis a reference to the object containingclickPoint) will result in an error. Therefore there has to be a new method written to setclickPoint.
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.
- When the mouse is pressed, we call the
mousePressedmethod of theLocalMouseListenerobject. This method is part of theMouseListenerinterface. - When the mouse is dragged, we call the
mouseDraggedmethod of theLocalMouseListenerobject. This method is part of theMouseMotionListenerinterface.
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:
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 thePsychBoxes 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:
- Once we set up a
Timer, we have to tell it to start. And that's whyinitcallstimer.start(), wheretimerreferences ourTimerobject. - We can click the mouse to stop and restart the applet. The
inner class
LocalPsychListeneralso implementsMouseListener, and it responds tomouseClickedevents. ThemouseClickedmethod asks theTimerwhether it's running. If it is, it callstimer.stop()to stop it. Otherwise, the call is totimer.start()to restart theTimer. - Every time the mouse enters the applet window, we reverse the
direction of the boxes, from heading inward to heading outward.
The boolean instance variable
inwardin thePsychBoxes2class tells us in which direction we're going, and themouseEnteredmethod inLocalPsychListenersimply flips the value of this instance variable.If you look at the assignment to the variable
colorwheninwardisfalse, it looks rather complex. The purpose of addingRECTtoiterbefore subtractingiis to keep the value positive. If it could go negative then the% 4would give a negative remainder, which would not work with the switch statement.
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'srepaint 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
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
- The event will be generated by activity in the
JTextFieldreferenced byfahrenheit. That is the object to which we add an action listener. - As the parameter to the
addActionListenercall, we give a reference to the object that will actually perform the action. In this case, the object is the applet itself.
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
cp to reference the content pane for the
applet. The line
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,
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. (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.