This GUI exercise is a continuation of GUI #3a.
Do this...
Read (or re-read) GUI #3a to
understand the problem worked on in this lab exercise.
Do this...
Do this...
Create a driver class BetterGUIDriver; be sure to
declare a stub for main(String[]).
In GUI #3a you (would have) built this look for the output:
You will re-use this output look for this lab:
Do this...
RandomWalkerGUIDriver#displayWalk() into
BetterGUIDriver.RandomWalkerCLIDriver#displayWalk() into
BetterGUIDriver.In either case, write a method stub for
BetterGUIDriver#makeWalker() which returns
null.
Do not put any meaningful code in makeWalker().
The input so far has been very clunky. Usually a GUI will provide fields and checkboxes and buttons for input; it doesn't prompt you for input with dialog boxes.
Wouldn't your user appreciate something along these lines?
Change one of those fields, hit the "Plot walk" button, and a new random walk could be generated. If this view were to start with those default values, that could be greatly appreciated, too.
Take a look at the GUI interface above. You can type input into the text fields; labels indicate what the text fields are for; and a button will plot a walk (presumably). Everything you see in that picture is provided to you by the Swing library.
This new GUI view is itself going to be a
frame, Java's term for an application that uses
the Swing library. Since this view is a frame, use
inheritance: BetterGUIDriver extends
JFrame.
Do this...
Implement the inheritance.
BetterGUIDriver will be based on the GUI itself;
this is in contrast to the other drivers which were based on the
data itself. Compare and contrast the descriptions of the
attributes:
|
|
||||||||||||||||||||||||||||||
All of the GUI widgets come from the javax.swing
package.
A traditional GUI needs to keep track of the input widgets, not the data. As long as the GUI has access to the widgets, they can always be asked for their input. For now it looks like we have to do more work (i.e., deal with a button), but in the long run, we'll do away with the screen and keyboard which makes some things much easier.
Do this...
Implement the attributes of BetterGUIDriver as
instance variables.
The major change in this GUI is the way that data is received.
Most GUIs are event driven
; this is in contrast with CLIs and with the
GUIs you've written so far which have been program
driven.
Drivers that are program driven work off of the code you type in for the program. You are complete control of the control flow.
With event-drivent programming, you set up a framework, and then you let the event handler handle events and take care of the control flow. You give up complete control of your program so that you don't have to worry about all the minutia of dealing with the widgets.
The main() method of BetterGUIDriver
needs to implement this algorithm:
Algorithm of BetterGUIDriver#main(String[])
- Let
guibe a newBetterGUIDriver.- Set
gui's default close operation to be "exit on close".- Pack
gui.- Make
guivisible.
These steps may seem familiar to you if you did GUI #3a; you'll find them in
RandomWalkerGUIDriver#walkDisplay(). However, that
method creates a frame for output.
BetterGUIDriver#main(String[]) is a
JFrame for input.
Objects of BetterGUIDriver#main(String[])Description Type Kind Name the gui BetterGUIDrivervariable gui"exit on close" operation constant intconstant JFrame.EXIT_ON_CLOSE
Operations of BetterGUIDriver#main(String[])Description Predefined? Name Library create a new BetterGUIDriveryes BetterGUIDriver()constructorBetterGUIDriverset the default close-operation yes setDefaultCloseOperation(int)JFrameresize the JFrameto preferred sizes (i.e., "pack" it)yes pack()JFramemake a JFramevisibleyes setVisible(true)JFrame
Keep the inheritance in mind: anything that belongs to the
JFrame class also belong to a
BetterGUIDriver because a BetterGUIDriver
is a JFrame.
Do this...
Implement BetterGUIDriver#main(String[]). Everything
should compile, but it won't
look like anything when it runs.
The last statement of this method gets you off the hook for controling the program. In fact, you can get into serious trouble if you do try to take control! Once you make your frame visible, do not invoke any more methods. All of the rest of your code will have to go into listeners which will listen for events.
This does mean that there's a potential problem with
the code in RandomWalkerGUIDriver from GUI #3a; strictly speaking, you
shouldn't use JOptionPanes and
JFrames from a program-driven driver. But since that
driver is so controlled, you probably won't notice any
problems (although that won't stop some Java programmers from
taking offense).
There are three tasks to be done when constructing a GUI application: create the widgets, add the widgets to the application's frame, and add listeners to the widgets (at least some of them) to be notified of any events. We end up with a rather long algorithm!
Since you'll have so many widgets in this one frame, you need to
worry about the layout of the frame. Layouts in
Java are, simply put, a pain. Some of the layout managers require
too much of your attention; others don't really work very well.
Fortunately, for BetterGUIDriver, we can use
one of the easiest layout managers: FlowLayout. This
layout manager makes the widgets flow "naturally" in the frame.
Remember: internal perspective indicates clearly when I'm using
instance variables or my own instance methods. Be sure to call my
own instance methods directly (even (especially?) if they are
inherited from JFrame).
Algorithm of BetterGUIDriver#BetterGUIDriver()
- Set
myNumberOfStepsto be a text field with a default value of"20".- Set
myExtentto be a text field with a default value of"2".- Set
mySizeOfFieldto be a text field with a default value of"50".- Let
walkButtonto be a button with"Walk"for a label.- Let
numberOfStepsLabelbe a label with the text"Number of steps:".- Let
extentLabelbe a label with the text"Maximum stepsize:".- Let
sizeOfFieldLabelbe a label with the text"Size of field:".- Let
layoutbe a new flow layout.- Set my layout to be
layout.- Add
numberOfStepsLabelto me.- Add
myNumberOfStepsto me.- Add
extentLabelto me.- Add
myExtentto me.- Add
sizeOfFieldLabelto me.- Add
mySizeOfFieldto me.- Add
walkButtonto me.- Let
walkActionbe a new walk action using myself as an argument.- Add
walkActiontowalkButtonas an action listener.
Here are the objects and operations for all but the last two statements:
Objects of BetterGUIDriver#BetterGUIDriver()Description Type Kind Name my number-of-steps text field JTextFieldinstance variable myNumberOfStepsmy extent text field JTextFieldinstance variable myExtendmy size-of-field text field JTextFieldinstance variable mySizeOfFieldthe walk button JButtonvariable walkButtonthe label for the number-of-steps text field JLabelvariable numberOfStepsLabelthe label for the extent text field JLabelvariable extentLabelthe label for the size-of-field text field JLabelvariable sizeOfFieldLabelthe flow layout FlowLayoutvariable layout
Operations of BetterGUIDriver#BetterGUIDriver()Description Predefined? Name Library create a text field with a default value yes JTextField(String)JTextFieldcreate a button with text on it yes JButton(String)JButtoncreate a label with text on it yes JLabel(String)JLabelcreate a new flow layout yes FlowLayout()FlowLayout, a subclass ofLayoutset my layout yes setLayout(Layout)JFrameadd a widget to me yes add(Component)JFrame
Look at all of the inheritance here!
BetterGUIDriver, that means I'm also
a JFrame; I'll invoke a lot of methods directly.FlowLayout is a subclass of
Layout, it can be passed to
JFrame#setLayout(Layout) without any hassles.JTextField, JLabel, and
JButton are all subclasses of Component,
their instances can be used as arguments to
JFrame#add(Component).Do this...
Implement BetterGUIDriver#BetterGUIDriver() except
for its last two statements. Compile, and run this driver.
Everything in the driver should work except for the button. Now, a non-working button seems like a lot, but consider what is working without you really trying:
All of these things (the look of the output, entering data, the order of entering data) were determined precisely in previous programs. Not now!
The key here is that you are not in charge of this program. Once you make an application visible, you hand over complete control to the event handler. The event handler hands the control of the program until the program ends. So the key now is to get the event handler to let you know when something interesting happens. You're told of interesting events (e.g., a button pushed, text typed in, etc.) through listeners.
The Swing library has already added some listeners to the widgets in your GUI. There's a listener for the button that reacts when the button is pressed so that the button looks like its pressed. There's a listener for each text field that processes every keypress made in the text field. There's a listener that redraws the application when you resize it.
Thankfully, we don't have to write any of this code!
Instead, you want to listen to events that will trigger some action in your model (most likely).
You don't need to listen to the text fields because the Swing library is already listening to them to handle all the mundane (and obvious) details. You'll want to get the text from the text fields, but that doesn't involve listening to them.
So, for you, the only widget worth listening to is the button.
A "listener" is a thing, a noun. Nouns are implemented as classes. The event handler needs to be able to execute a particular method. So: you need to implement a class and the event handler demands a particular method. Answer: an interface!
Buttons use an action listener specified by the
ActionListener interface (from the
java.awt.event package). This interface requires just
one method:
public void actionPerformed(ActionEvent event)
The ActionEvent parameter specifies the details of
the event, but it often can be safely ignored. Pressing the button
is pressing the button, and the mere fact that your method is
called is all the information you need from the event handler.
So you need to write a class that implements the
ActionListener interface. Let's call this class
WalkAction.
Do this...
WalkAction which implements
ActionListener.WalkAction#actionPerformed(ActionEvent).Let's design the
WalkAction#actionPerformed(ActionEvent) method.
Behavior of WalkAction#actionPerformed(ActionEvent)I am a walk action. When my button is pressed, I will ask the
BetterGUIDriverto display a walk using data from its text fields.
Wait a second...
Where is this BetterGUIDriver going to come from?
It cannot come as an argument because the interface says
that all I will receive is an ActionEvent. I should
have this as an instance variable.
Let's try that again...
Behavior of WalkAction#actionPerformed(ActionEvent)I am a walk action. When my button is pressed, I will ask
myBetterGUIDriverto display a walk (using data from its own text fields).
This suggests an instance variable and a constructor:
Do this...
myBetterGUIDriver (of type
BetterGUIDriver) in WalkAction.WalkAction which receives
a BetterGUIDriver and initializes the instance
variable appropriately.Now WalkAction#actionPerformed(ActionEvent) seems a
bit to easy:
Algorithm of WalkAction#actionPerformed(ActionEvent)
- Invoke
displayWalk()onmyBetterGUIDriver.
That's all the behavior asks for!
So the rest of the work is back in
BetterGUIDriver#BetterGUIDriver().
Here are the objects and operations for the last two statements of that constructor.
Objects of BetterGUIDriver#BetterGUIDriver()continuedDescription Type Kind Name the walk action that listens to the button WalkActionvariable walkActionmyself (to be passed as an argument to the WalkActionconstructor)BetterGUIDriverkeyword this
As indicated in this table, this is a reserved word
in Java. In an instance method or constructor, it refers to myself
(i.e., the object being constructed or receiving the message).
Usually we don't need this, but when I have to pass myself in as an
argument (as with the WalkAction constructor), then I
need to use this.
Think of this as an undeclared constant. You
cannot set its value; you cannot declare it as
your own variable.
Operations of BetterGUIDriver#BetterGUIDriver()continuedDescription Predefined? Name Library create a new WalkActionyes WalkAction(BetterGUIDriver)WalkActionadd an action listener to a widget yes addActionListener(ActionListener)Component
That last operation is nothing but inheritance! Since a
JButton is a Component, you can
invoke the method on any JButton. Since
WalkAction is a ActionListener,
you can pass an instance of WalkAction as an
argument.
Do this...
Implement the last two statements of
BetterGUIDriver#BetterGUIDriver().
Most things are in place.
Do this...
Compile your code, and run the better GUI. When you press the
button, you should get a horrible error message (probably about a
null-pointer exception) on your console. If you get no message at
all, then probably something isn't hooked up right yet.
The missing piece is the
BetterGUIDriver#makeWalker() method. It exists, but it
should be returning null (like a good method
stub).
Do this...
Look at the makeWalker() methods from the other
drivers. Note where they get their data from.
You cannot go to instance variables directly to get the data for
a random walker. The data in BetterGUIDriver is tied
up in text fields.
Your code will be a bit more readable if you create some pseudo-accessors.
Algorithm of BetterGUIDriver#getNumberOfSteps()
- Let
textbe the text frommyNumberOfSteps.- Let
numberOfStepsbe the parsed integer fromtext.- Return
numberOfSteps.
Objects of BetterGUIDriver#getNumberOfSteps()Description Type Kind Movement Name my number-of-steps text field JTextFieldinstance variable instance myNumberOfStepsthe text from the text field Stringvariable local textthe parsed integer from the text intvariable out numberOfSteps
Operations of BetterGUIDriver#getNumberOfSteps()Description Predefined? Name Library get the text from a text field yes getText()JTextFieldparse an intfrom aStringyes parseInt(String)Integerreturn the integer yes returnstatementbuilt-in
Do this...
Write a pseudo-accessor method for each of the text fields.
I call these "pseudo-accessors" because their named as if they were accessors and to the outside they kind of behave like accessors.
Now BetterGUIDriver#makeWalker() is easier to
write.
Algorithm of BetterGUIDriver#makeWalker()
- Let
numberOfStepsbe my number of steps.- Let
extentbe my extent.- Let
sizeOfFieldbe my size of field.- Let
walkerbe a newRandomWalkerconstructed withnumberOfSteps,extent, andsizeOfField.- Return
walker.
The first three statements are written using the pseudo-accessors.
Do this...
Implement BetterGUIDriver#makeWalker(). Compile your code, and run this GUI. It should work quite
nicely except for one small flaw.
There's a small problem: if you close one of the displays of a walk, it stops the entire program. That doesn't seem quite right. What if the user makes a data-entry mistake and wants to close up a bad display? You're forcing the user to restart the whole program. Instead, closing a display should just close that one display. (It does seem fair that closing the frame with the text fields and the button should stop the whole program.)
Do this...
This is fixed by removing one line of code from the
program. Do that.
action listener, button, event, event driven, event handler, frame, label, layout, listener, program driven, text field