Reading

Required: These notes!
Recommended:
Java in a Nutshell,
TO APPEAR

Overview

Adding a JButton and the wonderful world of LayoutManagers

One of the big lessons of last class is that GUI components are
just objects - i.e. they are simply instances of classes, just
like any other. For windows we used the class JFrame.
The class for buttons is JButton. So you can create a new
button with label "Button" with the statement:

JButton b = new JButton("Button");

Note that the label could have been "George" instead of "Button"
and it would all be the same. There is a big difference between
a JFrame and a JButton, however. A JFrame is a window that sits
on the screen all by itself. But a JButton is a component that
must be placed inside some other GUI container - like a JFrame
or some other component that can act as a container for other
GUI elements. So, for starters we'd like to add the JButton to
a JFrame. However, now things get a bit complicated. Where in
the JFrame window do we want the JButton to go? This question
is outrageously subtle because there might be lots of components
sitting in a container like a JFrame, and because windows can
get resized or, perhaps, were never given a set size at all, and
the programmer wants it to be "big enough".

To deal with this complication of where to place components
that get added to containers like JFrames, the designers of
Java's Swing library turned to OOP principles: let's make the
positioner of components within the container be ... an
object. Specifically, there is an interface
named LayoutManager, and objects implementing
this interface are used to position componenets.
By default, JFrame's have LayoutManagers of type
BorderLayout, which thinks of the screen being divied up into
regions: North, East, South, West and Center. When you add a
component to a JFrame, you specify which BorderLayout region
you want it in. So if f is a JFrame you might
say:

f.add(new JButton("Button"), BorderLayout.CENTER);

if you want a new button with label "Button" added to the
Center region, and

f.add(new JButton("Button"), BorderLayout.CENTER);

if you want it added to the East region. These regions
automatically expand or shrink to fit the various componenets
into their various regions.

BorderLayout's regions

add button BorderLayout.CENTER

add button BorderLayout.East

The funny thing is that by default if you only add one
component, all other regions shrink to nothing, and the one
component grows to fill the whole window. You can get a big
button that way!

Adding several components: JLabel, JButton, JTextField

Now that we know about LayoutManagers - or at least BorderLayout
- we can add several components to our GUI. Here we will use
the following classes:

We'll add these classes to make the GIU pictured to the right,
which will show up, but which won't react to user input. So
the user can click the button and add text in the box, but
nothing will happen. We'll do that next.

Reacting to user interactions with components

As we saw last class, the general model for reacting to user
interactions with GUI components is that there is an interface
with methods corresponding to the different kinds of actions
that might occur involving a given kind of component
(WindowListener in the case of JFrame), and you define a class
that implements that interface (a "listener") where the code you give for each
of the interface's methods defines what you want done in case
that action occurs on the given component. You then "add" an
instance of this class you've created to the component's list of
listeners. Should an action occur on that component, it goes
through its list of listener's and calls on each of them the
method corresponding to the action that occurred.

In our simple GUI, we have a button that the user can click
and a text field the user can type something in.
A click is considered "the" action for a button, and a new
text value (user presses enter while the box has the focus)
is considered "the" action for a button. So both components
use a the simple ActionListener interface, which looks like
this basically:

So a class that implements ActionListener is what you want,
whether you want to react to a button being clicked or text
being entered.
In this example, we'll simply react to the button being
clicked. When that happens, we'll take any text in the text
field, and make it the new text in the label, erasing the text
field in the process.

Now, if a user hits "enter" while in the text field, that should
change the label just like clicking the button would. So let's
make that happen. We could actually add the same listener
object to both the button and the text field and be done with
it. However, let's do it in a slightly roundabout (but cool)
way by using the JButton method doClick() which, when called,
simulates the user having clicked the button. We'll make the
ActionListener we add to the text field do that.

Cute, eh?

A simple GUI application: Units Conversion

As a goal for the rest of this lesson, let's create a GUI that
actually solves a problem. Let's make a units converter!
We'd like it to look like this:

Now, an immediate problem is that the BorderLayout LayoutManager
doesn't allow us to do this. So, moving forward, we're going to
have to learn about a new LayoutManager (FlowLayout). Also,
we're going to learn about a new component,
JComboBox<T>, for drop-down list. Notice
that this class uses generics, so that different kinds of
objects can appear in the drop-down.

JPanel and the FlowLayout

JPanel is a class that provides a container that is not itself a
window, but just a container that can be added to other
containers (including JFrames). When you create a JPanel, you
pass the constructor the LayoutManager to use, which means you
can choose from among the many Java LayoutManagers.
(See A
Visual Guide to LayoutMangers.)
If you add a JPanel with, for example, a FlowLayout
LayoutManager, and it is the only component added to the JFrame,
the JPanel expands to fill the whole frame, and you've
effectively changed the LayoutManager from BorderLayout to
FlowLayout.

FlowLayout is nice sometimes. It just packs the components you
add into the container in the order they are added.
For out problem above, we'll just add the components and they'll
be placed left-to-right in a single row, since they'll all fit
nicely that way.

The nice, systematic solution

Creating and placing the GUI components is easy. The meat of
the problem is reacting to user events: entering data into
the "from" text field, or choosing a value from either of the
combo boxes. For all three of these events we want to do the
same thing: get the numerical value from the text field and the
conversion factors for the units chosen in the combo boxes, do
the conversion, and set the
"to" text field to the newly computed value. There are
different ways to do this, but the cleanest (though not shortest
or easiest) is to create a new class, ConversionWindow, that is
a JFrame with a little added functionality - specifically, the
methods:

public double getFromValue(); // get the "from" value
public double getFromCF(); // get the conversion factor for the "from" units
public double getToCF(); // get the conversion factor for the "to" units
public void setToValue(double x); // set the "to" value to x

Why? Because this is precisely the functionality required by
the Listener object that will be called upon to react to our
various events - change in "from" value, change in "from" units,
change in "to" units. Following this approach, our Listener
object will need to 1) remember the ConversionWindow it belongs
to, and 2) call onthe above methods to make the coversion happen.

Ex3.java

ConverterWindow.javax

ConverterActionListener.java

Or you could use non-static inner classes ...

We had to put a lot of thought and design and work into the
previous version of the converter program in order to
communicate between the ConverterWindow and the
ConverterActionListener.
A less principled, but shorter, approach is make the
CovnerterActionListener a non-static inner class of
ConverterWindow. This way, the CovnerterActionListener has
unfettered direct access to all the private fields of the
ConverterWindow. Of course this doesn't separate interface and
implementation ...