GridBagLayout
is a very flexible
layout manager that allows you to position components relative to one
another using constraints. With GridBagLayout
(and a fair amount of effort), you
can create almost any imaginable layout. Components are arranged at
logical coordinates on an abstract grid. We call them “logical”
coordinates because they designate positions in the space of rows and
columns formed by the set of components. Rows and columns of the grid
stretch to different sizes, based on the sizes and constraints of the
components they hold.
A row or column in a GridBagLayout
expands to accommodate the
dimensions and constraints of the largest component it contains.
Individual components may also be told to span more than one row or
column. Components that aren’t as large as their grid cell can be anchored
(positioned to one side) within their cell. They can also be set to fill
or expand their size in either dimension. Extra area in the grid rows and
columns can be parceled out according to the weight constraints of the
components. In this way, you can control how various components will grow
and stretch when a window is resized.
GridBagLayout
is much easier to
use in a graphical WYSIWYG GUI builder environment. That’s because working
with GridBag
is kind of like messing
with the old rabbit-ears antennae on your television. It’s not
particularly difficult to get the results that you want through trial and
error, but writing out hard and fast rules for how to go about it is
difficult. In short, GridBagLayout
is
complex and has some quirks. It is also simply a bit ugly both in model
and implementation. Remember that you can do a lot with nested panels and
by composing simpler layout managers within one another. If you look back
through this chapter, you’ll see some examples of composite layouts; it’s
up to you to determine how far you should go before making the break from
simpler layout managers to a more complex all-in-one layout manager like
GridBagLayout
.
Having stated that GridBagLayout
is complex and a bit ugly, we’re
going to contradict ourselves a little and say that its API is
surprisingly simple. There is only one constructor with no
arguments—GridBagLayout()
—and there
aren’t a lot of fancy methods to control how the display works.
The appearance of a grid bag layout is controlled by sets of
GridBagConstraints
, and that’s where
things get hairy. Each component that is managed by a GridBagLayout
is associated with a GridBagConstraints
object. GridBagConstraints
holds the following
variables, which we’ll describe in detail shortly:
int gridx
int gridy
int weightx
int weighty
Controls how additional space in the row or column is allotted to the component
int fill
Controls whether the component expands to fill the allotted space
int gridheight
int gridwidth
int anchor
Controls the position of the component if there is extra room within the allotted space
int ipadx
int ipady
Controls padding between the component and the borders of its area
Insets insets
Controls padding between the component and neighboring components
To make a set of constraints for a component or components, create
a new instance of GridBagConstraints
and set these public variables to the appropriate values. (There is also
a large constructor that takes all 11 arguments.)
The easiest way to associate a set of constraints with a component
is to use the version of add()
that
takes both a component object and a layout object as arguments. This
puts the component in the container and associates the GridBagConstraints
object with it:
Container
content
=
getContentPane
();
JComponent
component
=
new
JLabel
(
"constrain me, please..."
);
GridBagConstraints
constraints
=
new
GridBagConstraints
();
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
...
content
.
add
(
component
,
constraints
);
You can also add a component to a GridBagLayout
using the single argument
add()
method and then calling the
layout’s setConstraints()
method
directly to pass it the GridBagConstraints
object for that
component:
add
(
component
);
...
myGridBagLayout
.
setConstraints
(
component
,
constraints
);
In either case, the set of constraints is copied when it is
applied to the component. It’s the individual constraints that apply to
the component, not the GridBagConstraints
object. Therefore, you’re
free to create a single set of GridBagConstraints
, modify it as needed, and
apply it as needed to different objects. You might want to create a
helper method that sets the constraints appropriately, then adds the
component with its constraints to the layout. That’s the approach we’ll
take in our examples; our helper method is called addGB()
, and it takes a component plus a pair
of coordinates as arguments. These coordinates become the gridx
and gridy
values for the constraints. We could
expand upon this later and overload addGB()
to take more parameters for other
constraints that we often change from component to component.
One of the biggest surprises in the GridBagLayout
is that there’s no way to
specify the size of the grid. There doesn’t have to be. The grid size is
determined implicitly by the constraints of all the objects; the layout
manager picks dimensions large enough so that everything fits. Thus, if
you put one component in a layout and set its gridx
and gridy
constraints each
to 25
, the layout manager creates a
virtual 25 × 25 grid, with rows and columns numbered from 0 to 24. If
you then add a second component with a gridx
of 30
and a gridy
of 13
, the virtual grid’s dimensions change to 30
× 25. You don’t have to worry about setting up an appropriate number of
rows and columns. The layout manager does it automatically as you add
components.
With this knowledge, we’re ready to create some simple displays.
We’ll start by arranging a group of components in a cross shape. We
maintain explicit x
and y
local variables, setting them as we add the
components to our grid. This is partly for clarity, but it can be a
handy technique when you want to add a number of components in a row or
column. You can simply increment gridx
or gridy
before adding each component. This is a
simple and problem-free way to achieve relative placement. (Later, we’ll
describe GridBagConstraints
’s
RELATIVE
constant, which performs
relative placement automatically.) The following code shows the first
layout (see Figure 19-7):
//file: GridBag1.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
GridBag1
extends
JPanel
{
GridBagConstraints
constraints
=
new
GridBagConstraints
();
public
GridBag1
()
{
setLayout
(
new
GridBagLayout
());
int
x
,
y
;
// for clarity
addGB
(
new
JButton
(
"North"
),
x
=
1
,
y
=
0
);
addGB
(
new
JButton
(
"West"
),
x
=
0
,
y
=
1
);
addGB
(
new
JButton
(
"Center"
),
x
=
1
,
y
=
1
);
addGB
(
new
JButton
(
"East"
),
x
=
2
,
y
=
1
);
addGB
(
new
JButton
(
"South"
),
x
=
1
,
y
=
2
);
}
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"GridBag1"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
225
,
150
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
GridBag1
());
frame
.
setVisible
(
true
);
}
}
The buttons in this example are “clumped” together in the center of their display area. Each button is displayed at its preferred size, without stretching to fill the available space. This is how the layout manager behaves when the “weight” constraints are left unset. We’ll talk more about weights in the next two sections.
Let’s make the buttons expand to fill the entire JFrame
window. To do so, we must take two
steps: we must set the fill
constraint for
each button to the value BOTH
, and we
must set the weightx
and weighty
to nonzero
values, as shown in this example:
//file: GridBag2.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
GridBag2
extends
JPanel
{
GridBagConstraints
constraints
=
new
GridBagConstraints
();
public
GridBag2
()
{
setLayout
(
new
GridBagLayout
());
constraints
.
weightx
=
1.0
;
constraints
.
weighty
=
1.0
;
constraints
.
fill
=
GridBagConstraints
.
BOTH
;
int
x
,
y
;
// for clarity
addGB
(
new
JButton
(
"North"
),
x
=
1
,
y
=
0
);
addGB
(
new
JButton
(
"West"
),
x
=
0
,
y
=
1
);
addGB
(
new
JButton
(
"Center"
),
x
=
1
,
y
=
1
);
addGB
(
new
JButton
(
"East"
),
x
=
2
,
y
=
1
);
addGB
(
new
JButton
(
"South"
),
x
=
1
,
y
=
2
);
}
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"GridBag2"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
225
,
150
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
GridBag2
());
frame
.
setVisible
(
true
);
}
}
Figure 19-8 shows the resulting layout.
BOTH
is one of the constants of
the GridBagConstraints
class; it
tells the component to fill the available space in both directions. Here
are the constants you can use to set the fill
field:
We set the weight constraints to 1.0
; in this example, it doesn’t matter what
they are, provided each component has the same nonzero weight. Filling
doesn’t occur if the component’s weight in the direction you’re filling
is 0
, which is the default
value.
One of the most important features of GridBaglayout
is that it lets you create
arrangements in which components span two or more rows or columns. To do
so, set the gridwidth
and
gridheight
variables of
the GridBagConstraints
. The following
example creates such a display; button one spans two columns vertically
and button four spans two horizontally. Figure 19-9 shows the resulting layout.
//file: GridBag3.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
GridBag3
extends
JPanel
{
GridBagConstraints
constraints
=
new
GridBagConstraints
();
public
GridBag3
()
{
setLayout
(
new
GridBagLayout
());
constraints
.
weightx
=
1.0
;
constraints
.
weighty
=
1.0
;
constraints
.
fill
=
GridBagConstraints
.
BOTH
;
int
x
,
y
;
// for clarity
constraints
.
gridheight
=
2
;
// span two rows
addGB
(
new
JButton
(
"one"
),
x
=
0
,
y
=
0
);
constraints
.
gridheight
=
1
;
// set it back
addGB
(
new
JButton
(
"two"
),
x
=
1
,
y
=
0
);
addGB
(
new
JButton
(
"three"
),
x
=
2
,
y
=
0
);
constraints
.
gridwidth
=
2
;
// span two columns
addGB
(
new
JButton
(
"four"
),
x
=
1
,
y
=
1
);
constraints
.
gridwidth
=
1
;
// set it back
}
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"GridBag3"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
200
,
200
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
GridBag3
());
frame
.
setVisible
(
true
);
}
}
The size of each element is controlled by the gridwidth
and gridheight
values of its constraints. For
button one, we set gridheight
to
2
; therefore, it is two cells high.
Its gridx
and gridy
positions are both 0
, so it occupies cell (0,0) and the cell
directly below it, (0,1). Likewise, button four has a gridwidth
of 2
and a gridheight
of 1
, so it occupies two cells horizontally. We
place this button in cell (1,1), so it occupies that cell and its
neighbor, (2,1).
In this example, we set the fill
to BOTH
and weightx
and weighty
to 1
for all components. By doing so, we tell
each button to occupy all the space available and give them all equal
weighting. Strictly speaking, this isn’t necessary. However, it makes it
easier to see exactly how much space each button occupies.
The weightx
and weighty
variables of a
GridBagConstraints
object determine
how “extra” space in the container is distributed among the columns or
rows in the layout. As long as you keep things simple, the effect these
variables have is fairly intuitive: the larger the weight, the greater
the amount of space allocated to the component, relative to its peers.
Figure 19-10 shows what happens if we
vary the weightx
constraint from
0.1
to 1.0
as we place three buttons in a row.
Here’s the code:
//file: GridBag4.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
GridBag4
extends
JPanel
{
GridBagConstraints
constraints
=
new
GridBagConstraints
();
public
GridBag4
()
{
setLayout
(
new
GridBagLayout
());
constraints
.
fill
=
GridBagConstraints
.
BOTH
;
constraints
.
weighty
=
1.0
;
int
x
,
y
;
// for clarity
constraints
.
weightx
=
0.1
;
addGB
(
new
JButton
(
"one"
),
x
=
0
,
y
=
0
);
constraints
.
weightx
=
0.5
;
addGB
(
new
JButton
(
"two"
),
++
x
,
y
);
constraints
.
weightx
=
1.0
;
addGB
(
new
JButton
(
"three"
),
++
x
,
y
);
}
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"GridBag4"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
300
,
100
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
GridBag4
());
frame
.
setVisible
(
true
);
}
}
The specific values of the weights are not meaningful; it is only
their relative proportions that matter. After the preferred sizes of the
components (including padding and insets—see the next section) are
determined, any extra space is doled out in proportion to the
component’s weights. For example, if each of our three components had
the same weight, each would receive a third of the extra space. To make
this more obvious, you may prefer to express the weights for a row or
column as fractions totaling 1.0—for example: 0.25, 0.25, 0.50.
Components with a weight of 0
receive
no extra space.
The situation is a bit more complicated when there are multiple
rows or columns and when there is even the possibility of components
spanning more than one cell. In the general case, GridBagLayout
calculates an effective overall
weight for each row and column and then distributes the extra space to
them proportionally. Note that the previous single-row example is just a
special case where the columns each have one component. The gory details
of the calculations follow.
For a given row or column (“rank”), GridBagLayout
first considers the weights of
all the components contained strictly within that rank—ignoring those
that span more than one cell. The greatest individual weight becomes
the overall weight of the row or column. Intuitively, this means that
GridBagLayout
is trying to
accommodate the needs of the weightiest component in that rank.
Next, GridBagLayout
considers
the components that occupy more than one cell and things get a little
weird. GridbagLayout
wants to
evaluate them to see whether they affect the determination of the
largest weight in a row or column. However, because these components
occupy more than one cell, GridBagLayout
divides their weight among the
ranks (rows or columns) that they span.
GridBagLayout
tries to
calculate an effective weight for the portion of the component that
occupies each row or column. It does this by trying to divide the
weight of the component among the ranks in the same proportions that
the length (or height) of the component will be shared by the ranks.
But how does it know what the proportions will be before the whole
grid is determined? That’s what it’s trying to calculate, after all.
It simply guesses based on the row or column weights already
determined. GridBagLayout
uses the
weights determined by the first round of calculations to split up the
weight of the component over the ranks that it occupies. For each row
or column, it then considers that fraction of the weight to be the
component’s weight for that rank. That weight then contends for the
“heaviest weight” in the row or column, possibly changing the overall
weight of that row or column, as we described earlier.
If a component is smaller than the space available for it,
it is centered by default. But centering isn’t the only possibility. The
anchor
constraint tells a grid bag
layout how to position a component within its cell in the grid. Possible
values are GridBagConstraints.CENTER
, NORTH
, NORTHEAST
, EAST
, SOUTHEAST
, SOUTH
, SOUTHWEST
, WEST
, and NORTHWEST
. For example,
an anchor of GridBagConstraints.NORTH
centers a component at the top of its display area; SOUTHEAST
places a component at the
bottom-right corner of its area.
Another way to control the behavior of a component in a
grid bag layout is to use padding and insets. Padding is determined by
the ipadx
and ipady
fields of
GridBagConstraints
. They specify
horizontal and vertical “growth factors” for the component. In Figure 19-11, the West button is larger because
we have set the ipadx
and ipady
values of its constraints to 25
. Therefore, the layout manager gets the
button’s preferred size and adds 25 pixels in each direction to
determine the button’s actual size. The sizes of the other buttons are
unchanged because their padding is set to 0
(the default), but their spacing is
different. The West button is unnaturally tall, which means that the
middle row of the layout must be taller than the others.
//file: GridBag5.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
GridBag5
extends
JPanel
{
GridBagConstraints
constraints
=
new
GridBagConstraints
();
public
GridBag5
()
{
setLayout
(
new
GridBagLayout
());
int
x
,
y
;
// for clarity
addGB
(
new
JButton
(
"North"
),
x
=
1
,
y
=
0
);
constraints
.
ipadx
=
25
;
// add padding
constraints
.
ipady
=
25
;
addGB
(
new
JButton
(
"West"
),
x
=
0
,
y
=
1
);
constraints
.
ipadx
=
0
;
// remove padding
constraints
.
ipady
=
0
;
addGB
(
new
JButton
(
"Center"
),
x
=
1
,
y
=
1
);
addGB
(
new
JButton
(
"East"
),
x
=
2
,
y
=
1
);
addGB
(
new
JButton
(
"South"
),
x
=
1
,
y
=
2
);
}
void
addGB
(
Component
component
,
int
x
,
int
y
)
{
constraints
.
gridx
=
x
;
constraints
.
gridy
=
y
;
add
(
component
,
constraints
);
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"GridBag5"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
250
,
250
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
GridBag5
());
frame
.
setVisible
(
true
);
}
}
Notice that the horizontal padding, ipadx
, is added on both the left and right
sides of the button. Therefore, the button grows horizontally by twice
the value of ipadx
. Likewise, the
vertical padding, ipady
, is added on
both the top and the bottom.
Insets add space between the edges of the component and its cell.
They are stored in the insets
field
of GridBagConstraints
, which is an
Insets
object. An Insets
object has four fields to specify the
margins on the component’s top
,
bottom
, left
, and right
. The relationship between insets and
padding can be confusing. As shown in Figure 19-12, padding is added to the component
itself, increasing its size. Insets are external to the component and
represent the margin between the component and its cell.
Padding and weighting have an odd interaction with each other. If
you use padding, it’s best to use the default weightx
and weighty
values for each component.
In all our grid bag layouts so far, we have specified the
gridx
and gridy
coordinates of
each component explicitly using its constraints. Another alternative is
relative positioning.
Conceptually, relative positioning is simple:
we just say, “put this component to the right of (or below) the previous
component.” To do so, you can set gridx
or gridy
to the constant GridBagConstraints.RELATIVE
. Unfortunately,
it’s not as simple as this. Here are a couple of warnings:
To place a component to the right of the previous one, set
gridx
toRELATIVE
and use the same value forgridy
that you used for the previous component.Similarly, to place a component below the previous one, set
gridy
toRELATIVE
and leavegridx
unchanged.Setting both
gridx
andgridy
toRELATIVE
places all the components in one row, not in a diagonal line, as you might expect. (This is the default.)
In other words, if gridx
or
gridy
is RELATIVE
, you had better leave the other value
unchanged. RELATIVE
makes it easy to
arrange a lot of components in a row or a column. That’s what it was
intended for; if you try to do something else, you’re fighting against
the layout manager, not working with it.
GridBagLayout
allows another
kind of relative positioning in which you specify where, in a row or a
column, the component should be placed overall using the gridwidth
and gridheight
fields of GridBagConstraints
. Setting either of these to
the constant REMAINDER
says that the
component should be the last item in its row or column and, therefore,
should occupy all the remaining space. Setting either gridwidth
or gridheight
to RELATIVE
says that it should be the second to
the last item in its row or column. Unfortunately, you can use these
constants to create constraints that can’t possibly be met; for example,
you can say that two components must be the last component in a row. In
these cases, the layout manager tries to do something reasonable, but it
will almost certainly be something you don’t want done.
Sometimes things don’t fall neatly into little boxes. This
is true of layouts as well as life. For example, if you want to use some
of GridBagLayout
’s weighting features
for part of your GUI, you could create separate layouts for different
parts of the GUI and combine them with yet another layout. That’s how
we’ll build the pocket calculator interface in Figure 19-13. We will use three grid bag
layouts: one for the first row of buttons (C, %, +), one for the last
(0, ., =) and one for the window itself. The master layout (the
window’s) manages the text field we use for the display, the panels
containing the first and last rows of buttons, and the 12 buttons in the
middle.[43]
Here’s the code for the Calculator
example. It implements only the
user interface (i.e., the keyboard); it collects everything you type in
the display field until you press C (clear). Figuring out how to connect
the GUI to some other code that would perform the operations is up to
you. One strategy would be to send an event to the object that does the
computation whenever the user presses the equals sign. That object could
read the contents of the text field, parse it, do the computation, and
display the results.
//file: Calculator.java
import
java.awt.*
;
import
java.awt.event.*
;
import
javax.swing.*
;
public
class
Calculator
extends
JPanel
implements
ActionListener
{
GridBagConstraints
gbc
=
new
GridBagConstraints
();
JTextField
theDisplay
=
new
JTextField
();
public
Calculator
()
{
gbc
.
weightx
=
1.0
;
gbc
.
weighty
=
1.0
;
gbc
.
fill
=
GridBagConstraints
.
BOTH
;
ContainerListener
listener
=
new
ContainerAdapter
()
{
public
void
componentAdded
(
ContainerEvent
e
)
{
Component
comp
=
e
.
getChild
();
if
(
comp
instanceof
JButton
)
((
JButton
)
comp
).
addActionListener
(
Calculator
.
this
);
}
};
addContainerListener
(
listener
);
gbc
.
gridwidth
=
4
;
addGB
(
this
,
theDisplay
,
0
,
0
);
// make the top row
JPanel
topRow
=
new
JPanel
();
topRow
.
addContainerListener
(
listener
);
gbc
.
gridwidth
=
1
;
gbc
.
weightx
=
1.0
;
addGB
(
topRow
,
new
JButton
(
"C"
),
0
,
0
);
gbc
.
weightx
=
0.33
;
addGB
(
topRow
,
new
JButton
(
"%"
),
1
,
0
);
gbc
.
weightx
=
1.0
;
addGB
(
topRow
,
new
JButton
(
"+"
),
2
,
0
);
gbc
.
gridwidth
=
4
;
addGB
(
this
,
topRow
,
0
,
1
);
gbc
.
weightx
=
1.0
;
gbc
.
gridwidth
=
1
;
// make the digits
for
(
int
j
=
0
;
j
<
3
;
j
++)
for
(
int
i
=
0
;
i
<
3
;
i
++)
addGB
(
this
,
new
JButton
(
""
+
((
2
-
j
)*
3
+
i
+
1
)
),
i
,
j
+
2
);
// -, x, and divide
addGB
(
this
,
new
JButton
(
"-"
),
3
,
2
);
addGB
(
this
,
new
JButton
(
"x"
),
3
,
3
);
addGB
(
this
,
new
JButton
(
"\u00F7"
),
3
,
4
);
// make the bottom row
JPanel
bottomRow
=
new
JPanel
();
bottomRow
.
addContainerListener
(
listener
);
gbc
.
weightx
=
1.0
;
addGB
(
bottomRow
,
new
JButton
(
"0"
),
0
,
0
);
gbc
.
weightx
=
0.33
;
addGB
(
bottomRow
,
new
JButton
(
"."
),
1
,
0
);
gbc
.
weightx
=
1.0
;
addGB
(
bottomRow
,
new
JButton
(
"="
),
2
,
0
);
gbc
.
gridwidth
=
4
;
addGB
(
this
,
bottomRow
,
0
,
5
);
}
void
addGB
(
Container
cont
,
Component
comp
,
int
x
,
int
y
)
{
if
((
cont
.
getLayout
()
instanceof
GridBagLayout
)
==
false
)
cont
.
setLayout
(
new
GridBagLayout
());
gbc
.
gridx
=
x
;
gbc
.
gridy
=
y
;
cont
.
add
(
comp
,
gbc
);
}
public
void
actionPerformed
(
ActionEvent
e
)
{
if
(
e
.
getActionCommand
().
equals
(
"C"
))
theDisplay
.
setText
(
""
);
else
theDisplay
.
setText
(
theDisplay
.
getText
()
+
e
.
getActionCommand
());
}
public
static
void
main
(
String
[]
args
)
{
JFrame
frame
=
new
JFrame
(
"Calculator"
);
frame
.
setDefaultCloseOperation
(
JFrame
.
EXIT_ON_CLOSE
);
frame
.
setSize
(
200
,
250
);
frame
.
setLocation
(
200
,
200
);
frame
.
setContentPane
(
new
Calculator
());
frame
.
setVisible
(
true
);
}
}
Once again, we use an addGB()
helper method to add components with their constraints to the layout.
Before discussing how to build the layout, let’s take a look at addGB()
. We said earlier that three layout
managers are in our user interface: one for the application panel
itself, one for the panel containing the first row of buttons (topRow
), and one for the panel containing the
bottom row of buttons (bottomRow
). We
use addGB()
for all three layouts;
its first argument specifies the container to which to add the
component. Thus, when the first argument is this
, we’re adding an object to the content
pane of the JFrame
. When the first
argument is topRow
, we’re adding a
button to the first row of buttons. addGB()
first checks the container’s layout
manager and sets it to GridBagLayout
if it isn’t already set properly. It sets the object’s position by
modifying a set of constraints, gbc
,
and then uses these constraints to add the object to the
container.
We use a single set of constraints throughout the example,
modifying fields as we see fit. The constraints are initialized in
Calculator
’s constructor. Before
calling addGB()
, we set any fields of
gbc
for which the defaults are
inappropriate. Thus, for the answer display, we set the grid width to 4
and add the answer display directly to the application panel (this
). The add()
method, which is called by addGB()
, makes a copy of the constraints, so
we’re free to reuse gbc
throughout
the application.
The first and last rows of buttons motivate the use of multiple
GridBagLayout
containers, each with
its own grid. These buttons appear to straddle grid lines, but you
really can’t accomplish this using a single grid. Therefore, topRow
has its own layout manager, with three
horizontal cells, allowing each button in the row to have a grid width
of 1
. To control the size of the
buttons, we set the weightx
variables
so that the clear and plus buttons take up more space than the percent
button. We then add the topRow
as a
whole to the application, with a grid width of 4
. The bottom row is built similarly.
To build the buttons for the digits 1–9, we use a doubly nested
loop. There’s nothing particularly interesting about this loop, except
that it’s probably a bit too cute. The minus, multiply, and divide
buttons are also simple: we create a button with the appropriate label
and use addGB()
to place it in the
application. It’s worth noting that we used a Unicode constant to
request a real division sign rather than wimping out and using a
slash.
That’s it for the user interface; what’s left is event handling.
Each button generates action events; we need to register listeners for
these events. We’ll make the application panel, the Calculator
, the listener for all the buttons.
To register the Calculator
as a
listener, we’ll be clever. Whenever a component is added to a container,
the container generates a ContainerEvent
. We use an anonymous inner
class ContainerListener
to register
listeners for our buttons. This means that the Calculator
must register as a ContainerListener
for itself and for the two
panels, topRow
and bottomRow
. The componentAdded()
method is very simple. It
calls getChild()
to find out what
component caused the event (i.e., what component was added). If that
component is a button, it registers the Calculator
as an ActionListener
for that button.
actionPerformed()
is called
whenever the user presses any button. It clears the display if the user
pressed the C button; otherwise, it appends the button’s action command
(in this case, its label) to the display.
Combining layout managers is an extremely useful trick. Granted,
this example verges on overkill. You won’t often need to create a
composite layout using multiple grid bags. Composite layouts are common,
however, with BorderLayout
; you’ll
frequently use different layout managers for each of a border layout’s
regions. For example, the CENTER
region might be a ScrollPane
, which
has its own special-purpose layout manager; the EAST
and SOUTH
regions might be panels managed by grid
layouts or flow layouts, as appropriate.
[43] If you’re curious, this calculator is based on the ELORG-801, encountered in an online “calculator museum”.
Get Learning Java, 4th Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.