JFC Swing: The SpringLayout Classby Marc Loy, coauthor of Java Swing, 2nd Edition
With SDK 1.4, a new -- but not really new -- layout manager was added. The
SpringLayout manager uses the notion of springs and struts to keep everything in place. A version of
SpringLayout existed in the early alpha and betas of the Swing package, but it was not included because the Swing team felt it still needed too much work. While it still needs a bit of work, it has come a long way; its inclusion in SDK 1.4 is a testament to that progress. The class diagram for
SpringLayout and its helpers is shown in Figure 1.
Before you dive too deeply into this layout manager, you should know that its purpose in life is to aid GUI builders and other code-generating tools. It can certainly be hand-coded -- and we have the examples to prove it -- but you'll often leave this layout manager to the aforementioned tools. If you want a flexible replacement for the
GridBagLayout, you might want to take a look at the
RelativeLayout manager written by Jim Elliott. The complete package, with docs, tutorial, and source code, can be found on the Java Swing, 2nd Edition book Web page.
Figure 1. The SpringLayout manager classes.
The source files for this article, FractionSpring.java and CompassButtons.java are available in this zip file.
Now that you're here for the long haul, let's look at the core of the
SpringLayout manager's approach to component layout: springs and struts.
SpringLayout at the helm, you use springs and struts to specify the bounds (x, y, width, height) of all of your components. (You could mimic the null layout manager by using only struts.)
The not-so-obvious big win in this layout manager is that springs can be anchored between the edges of components and will maintain their relationship even when the container is resized. This makes it possible to create layouts that would be difficult in other managers. While you could probably use a grand
GridBagLayout to do the trick,
SpringLayout should provide better performance once it's all fixed up and finalized.
Figure 2 shows a simple application that uses
SpringLayout. We position directional buttons over a large picture for navigation. Notice how the North button stays horizontally centered and anchored to the top edge of the application after we resize the frame. The other buttons behave similarly. Just to reiterate, you could certainly accomplish this with nested containers or a properly constructed
SpringLayout should simply prove to be the most maintainable over the long haul. We'll look at the source code for this example after we examine the API in more detail.
Figure 2. A SpringLayout managed container at two different sizes.
SpringLayout class thinks of components in terms of their edges. Several constants have been defined for the edges, as shown in Table 1.
Table 1. SpringLayout Constants.
||String||The top edge of the component. Corresponds to the y value of the component's bounding box.|
||String||The bottom edge of the component. Corresponds to the y value of the bounding box plus the height of the component.|
||String||The left edge of the component. Corresponds to the x value of the component's bounding box.|
||String||The right edge of the component. Corresponds to the x value of the bounding box plus the width of the component.|
The only constructor for
SpringLayout is the default constructor. Similar to the way one uses
CardLayout, you'll want to keep a reference to your
SpringLayout manager handy.
Create a new
As with other layout managers, a majority of the methods in
SpringLayout are devoted to meeting the contract of the
LayoutManager2 interfaces. The methods that make this manager interesting, however, are the methods dealing with components' constraints.
The following method returns the entire set of constraints (the springs on all four edges) for the given component. We discuss the
Constraints inner class in the next section.
public SpringLayout.Constraints getConstraints(Component c)
The following method returns a particular spring for the specified edge (
edgeName) of the given component (
public Spring getConstraint(String edgeName, Component c)
These methods place a constraint (spring) between two edges. The first method is just a convenience method that uses
pad to create a strut.
c1 are associated with the dependent component, while
c2 refer to the anchor.
public void putConstraint(String e1, Component c1, int pad, String e2, Component c2) public void putConstraint(String e1, Component c1, Spring s, String e2, Component c2)
No method exists for setting all of the constraints at one time, but you can use the object returned by the
getConstraints() method to manipulate all of the edges on a component.
Here's the source code for the example application shown in Figure 2. Notice the three primary means of positioning components in a
SpringLayout. We add the North and South buttons to the container with prebuilt constraints. The East and West buttons are positioned by retrieving their existing constraints and setting up the bounding box for the component. For example, the North and East buttons are set up like this:
// Add north button c.add(nb, new SpringLayout.Constraints(northX, offsetS, widthS, heightS)); // Add east button c.add(eb); sl.getConstraints(eb).setX(eastX); sl.getConstraints(eb).setY(eastY); sl.getConstraints(eb).setWidth(widthS); sl.getConstraints(eb).setHeight(heightS);
As an example of the third mechanism for positioning components, the viewport for the graphics image is laid out using several
c.add(viewport); // The order here is important...need to have a valid width and height // in place before binding the (x,y) location sl.putConstraint(SpringLayout.SOUTH, viewport, Spring.minus(borderS), SpringLayout.SOUTH, c); sl.putConstraint(SpringLayout.EAST, viewport, Spring.minus(borderS), SpringLayout.EAST, c); sl.putConstraint(SpringLayout.NORTH, viewport, topBorder, SpringLayout.NORTH, c); sl.putConstraint(SpringLayout.WEST, viewport, leftBorder, SpringLayout.WEST, c);
You might notice some funky springs in this example. We'll explain the centering spring and the
minus() methods in the section on the
Spring class itself.
SpringLayout.Constraints embodies the spring constraints placed on a single component in a
SpringLayout-managed container. It holds the bounding box for a component, but it uses
Spring references rather than
ints for the
Constraints inner class consists entirely of the properties shown in Table 2. With the exception of the
constraint property, these properties mimic the
Rectangle class often used to describe the bounds of components. The
constraint property is indexed by edge name (a
String). See Table 1, SpringLayout Constants, for a list of valid edge names and their respective relationships to the
Table 2. SpringLayout.Constraints Properties.
|Property||Data Type||get||is||set||Default Value|
i is indexed (by String values, see Table 1).
There are several constructors for building a
Constraints object. You might want to do this if you intend to build the constraints before adding the component to its container. (You can use the
Container.add(Component, Object) method to accomplish this.)
public SpringLayout.Constraints() public SpringLayout.Constraints(Spring x, Spring y) public SpringLayout.Constraints(Spring x, Spring y, Spring width, Spring height)
These constructors all build
Constraints objects. Any unspecified property is left as a null value. The first constructor creates a completely empty
Constraints object, while the second one leaves the
height properties null.
So what are these
Spring objects we keep seeing everywhere? As stated earlier, they are essentially a collection of three values: a minimum, a maximum, and a preferred value. You could use a spring to describe the
height property of a text area, for example. Its minimum is 25 pixels; its maximum is the height of the screen (say, 1024), and its preferred height is 8 rows, or 200 pixels. By the same token, you can create a strut by specifying a spring with identical values for all three properties; for example, a text field with 25 for its minimum, maximum, and preferred heights.
Beyond the basic expandability of a spring, you can do some fun things with them. Springs can be manipulated using mathematical concepts (and achieve a semantically-correct result). For example, you can add two springs together. The new spring consists of the sum of the minimums, the sum of the preferred values, and the sum of the preferred maximums. You can also negate a spring and effectively multiply each of its values by -1. Summing with a negated spring, then, becomes a difference operation.
Now here's where things get really interesting. When you sum two springs, the result is not calculated immediately. The resulting spring stores a reference to the two operand springs. When needed, the summed spring queries its sub-springs. This has the practical upshot of making springs dynamic. If you change one spring in a sum, the sum changes, too. This turns out to be very useful in layout managers. Attach a spring to the bottom of the container, and it stretches whenever the container stretches.
y property for the South button in the application shown in Figure 2. We can use subtraction to keep the button about 50 pixels above the bottom of the frame, even after the frame has been resized.
Spring offset = Spring.sum(buttonHeight, Spring.constant(50)); Spring southY = Spring.sum(bottom, Spring.minus(offset));
Figure 3 shows the details of each spring behind this button. (You can refer back to the code for the syntax on creating all of the springs you see in the diagram.)
Figure 3. Constructing the x, y, width, and height springs (and struts) for the South button.
Only one constant is defined for the
Spring class, as shown in Table 3.
Table 3. Spring Constants.
||An indicator that this property has not yet been calculated. (It's internal value is
The properties for
Spring, shown in Table 4, are fairly straightforward. In addition to the minimum, maximum, and preferred properties, you can read and write the current value of the spring. Notice that the minimum, maximum, and preferred properties are read-only. You can extend Spring if you need to have a more dynamic spring.
Table 4. Spring Properties.
|Property||Data Type||get||is||set||Default Value|
The default constructor for Spring is protected. It's obviously meant for subclasses. To create a spring, you use the factory-style
The following method creates a strut. The minimum, preferred, and maximum properties are all set to
public static Spring constant(int pref)
The following method creates a spring. The minimum, preferred, and maximum properties are set from
public static Spring constant(int min, int pref, int max)
Three manipulations are defined for springs. Recall that the calculated values are not actually hard-coded into the resulting springs. References to
s2 are stored, so that if either spring changes, the resulting springs also change. (This is done with the help of a private inner class extension of
Spring known as a "proxy spring.")
This method returns a new spring the properties of which represent the
Math.max() of the respective properties of
s2. In other words, the minimum property of the new spring is the max of
public static Spring max(Spring s1, Spring s2)
This method returns a new spring, the properties of which represent the negative of the respective properties of
s1. In other words, the minimum property of the new spring is
public static Spring minus(Spring s)
This method returns a new spring, the properties of which represent the sum of the respective properties of
s2. In other words, the minimum property of the new spring is equal to
s1.getMinimum() + s2.getMinimum().
public static Spring sum(Spring s1, Spring s2)
As mentioned earlier, you can combine the manipulation methods to create other operations. In particular,
Spring.sum(s1, Spring.minus(s2)) returns the difference of the respective properties in
Math.min() function can be mimicked using
Spring.minus(Spring.max(Spring.minus(s1), Spring.minus(s2))). Try it on paper -- it really works!
The combination of
Spring math operations and constraints can make certain layouts easy to create (and easy to manipulate). That's "easy" in the "if you're designing a GUI builder" sense, of course. Figure 4 shows four buttons laid out in a vertical row with various
Spring constraints holding them in place.
Figure 4. Vertically stacked buttons in a SpringLayout.
To show off more of the
Spring constraint combinations, we varied the layout code for these buttons:
// We'll leave all buttons at their preferred widths and heights // b1 gets placed at (10,10) c.add(b1); sl.getConstraints(b1).setX(offsetS); sl.getConstraints(b1).setY(offsetS); // b2 gets placed at (10, offset + b1.height + offset) c.add(b2); sl.getConstraints(b2).setX(offsetS); sl.getConstraints(b2).setY(Spring.sum(Spring.sum(offsetS, sl.getConstraints(b1).getHeight()), offsetS)); // b3 gets placed at (10, b2.south + offset) c.add(b3); sl.getConstraints(b3).setX(offsetS); sl.getConstraints(b3).setY(Spring.sum(offsetS, sl.getConstraint(SpringLayout.SOUTH, b2))); // b4 gets placed at (b3.west, b3.south + offset) c.add(b4); sl.putConstraint(SpringLayout.WEST, b4, 0, SpringLayout.WEST, b3); sl.putConstraint(SpringLayout.NORTH, b4, offsetS, SpringLayout.SOUTH, b3);
You could use any one of the techniques shown on all of the buttons, if you were so inclined. That said, there are a few consequences to the constraints we created in this example. For example, look at the
y constraint of
b2. It is simply the sum of two offsets and the height of
b1. It is not dependent on the bottom edge of
b1. It doesn't care where
b1 is placed. The
y constraints of
b4, however, are dependent. If
b2 moves down, so does
b3 -- and if
b3 moves down, so does
One other fun constraint in this example is the left edge of
b4. We tied it to the left edge of
b3. If you change the
x constraint of
There are some things that cannot be duplicated using
max(). For those things, you can simply extend the
Spring class. The compass navigation example in Figure 2 keeps the North and South buttons horizontally centered. (Of course, the East and West buttons are vertically centered.) To keep the buttons centered, even after the user resizes the frame, we need a new spring that returns the center of a parent spring. That can be built as we do in the
If you look a bit closer, this class can actually handle any multiplier value. The factory method
half() produces the spring we need most often, but you can use the public constructor to supply an alternative. You could certainly write other factory methods for common values you find useful. Maybe a
goldenMean() method is in your future?
One method you want to pay attention to is
setValue(). In several derived springs (like our
setValue() call does not make sense. Normally, we throw an
UnsupportedOperationException to indicate that this required method from our abstract parent does not really apply; however, the special value
UNSET can be used to help in a particular scenario: value caching.
If the current value of the spring comes from an expensive operation, you can cache that value. If your spring is bound to another spring, such as the border of your container, changing the size of the container causes a chain of
UNSET values to be passed to dependent springs. You could watch for
UNSET and recalculate your spring values only when you receive it. (Try uncommenting the
println() in our
setValue() method and rerun the example. You should see four
UNSET calls each time you resize the frame -- one for each button.)
While it may take a few tries to get your brain around
SpringLayout and spring math, you can accomplish some complex layouts with one container and a single, efficient layout manager.
Marc Loy is a trainer and media specialist in Cincinnati, OH. When he's not working with digital video and DVDs, he's programming in Java.
Return to ONJava.com.
Copyright © 2009 O'Reilly Media, Inc.