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 passingthis
to the listener class. - Even doing that is not sufficient, because
clickPoint
isprivate
, so attempting to write totheApplet.clickPoint
(wheretheApplet
is 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
mousePressed
method of theLocalMouseListener
object. This method is part of theMouseListener
interface. - When the mouse is dragged, we call the
mouseDragged
method of theLocalMouseListener
object. This method is part of theMouseMotionListener
interface.
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 whyinit
callstimer.start()
, wheretimer
references ourTimer
object. - We can click the mouse to stop and restart the applet. The
inner class
LocalPsychListener
also implementsMouseListener
, and it responds tomouseClicked
events. ThemouseClicked
method asks theTimer
whether 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
inward
in thePsychBoxes2
class tells us in which direction we're going, and themouseEntered
method inLocalPsychListener
simply flips the value of this instance variable.If you look at the assignment to the variable
color
wheninward
isfalse
, it looks rather complex. The purpose of addingRECT
toiter
before subtractingi
is to keep the value positive. If it could go negative then the% 4
would 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 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, 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
JTextField
referenced byfahrenheit
. 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.
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
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
. (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.