11.5 The SpringLayout Class


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 11-17.

Figure 11-17. The SpringLayout manager classes
figs/swng2.1117.gif

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 our own Jim Elliott. The complete package with docs, tutorial, and source code can be found on this book's web site, http://www.oreilly.com/catalog/jswing2/.)

11.5.1 Springs and Struts

Now that you're here for the long haul, let's look at the core of the SpringLayout manager's approach to component layout: spring and struts. A spring is effectively a triplet representing a range of values. It contains its minimum, preferred, and maximum lengths. A strut is a spring with all the spring removed its minimum, preferred, and maximum lengths are identical. With SpringLayout at the helm, you use springs and struts to specify the bounds (x, y, width, height) of all your components. (You can 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 11-18 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 GridBagLayout; the 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 11-18. A SpringLayout-managed container at two different sizes
figs/swng2.1118.gif

11.5.2 Constants

The SpringLayout class thinks of components in terms of their edges. Several constants have been defined for the edges, as shown in Table 11-14.

Table 11-14. SpringLayout constants

Constant

Type

Description

NORTH

String

The top edge of the component. Corresponds to the y value of the component's bounding box.

SOUTH

String

The bottom edge of the component. Corresponds to the y value of the bounding box plus the height of the component.

WEST

String

The left edge of the component. Corresponds to the x value of the component's bounding box.

EAST

String

The right edge of the component. Corresponds to the x value of the bounding box plus the width of the component.

11.5.3 Constructor

The only constructor for SpringLayout is the default constructor. Similar to the way one uses GridBagLayout and CardLayout, you'll want to keep a reference to your SpringLayout manager handy.

public SpringLayout( )

Create a new SpringLayout manager.

11.5.4 Constraint Methods

As with other layout managers, a majority of the methods in SpringLayout are devoted to meeting the contract of the LayoutManager and LayoutManager2 interfaces. The methods that make this manager interesting, however, are the methods dealing with components' constraints.

public SpringLayout.Constraints getConstraints(Component c)

This 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 Spring getConstraint(String edgeName, Component c)

This method returns a particular spring for the specified edge (edgeName) of the given component (c).

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)

These methods place a constraint (spring) between two edges. The first method is just a convenience method that uses pad to create a strut. e1 and c1 are associated with the dependent component while e2 and c2 refer to the anchor.

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 11-18. 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 putConstraint( ) calls:

c.add(viewport); // The order here is important. You 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 sum( ) and minus( ) methods in the section on the Spring class itself.

// CompassButtons.java // import javax.swing.*; import java.awt.*; public class CompassButtons extends JFrame {   JButton nb = new JButton("North");   JButton sb = new JButton("South");   JButton eb = new JButton("East");   JButton wb = new JButton("West");   JViewport viewport = new JViewport( );   public CompassButtons( ) {     super("SpringLayout Compass Demo");     setSize(500,300);     setDefaultCloseOperation(EXIT_ON_CLOSE);     SpringLayout sl = new SpringLayout( );     Container c = getContentPane( );     c.setLayout(sl);     int offset = 50;  // Gap between buttons and outside edge     int w      = 80;  // Width of buttons     int h      = 26;  // Height of buttons     int border =  3;  // Border around viewport     Spring offsetS     = Spring.constant(offset);     Spring borderS     = Spring.constant(border);     Spring widthS      = Spring.constant(w);     Spring halfWidthS  = FractionSpring.half(widthS);     Spring heightS     = Spring.constant(h);     Spring halfHeightS = FractionSpring.half(heightS);     Spring leftEdgeS   = sl.getConstraint(SpringLayout.WEST, c);     Spring topEdgeS    = sl.getConstraint(SpringLayout.NORTH, c);     Spring rightEdgeS  = sl.getConstraint(SpringLayout.EAST, c);      Spring bottomEdgeS = sl.getConstraint(SpringLayout.SOUTH, c);      Spring xCenterS    = FractionSpring.half(rightEdgeS);     Spring yCenterS    = FractionSpring.half(bottomEdgeS);     Spring leftBorder  = Spring.sum(leftEdgeS, borderS);     Spring topBorder   = Spring.sum(topEdgeS, borderS);          Spring northX = Spring.sum(xCenterS, Spring.minus(halfWidthS));     Spring southY = Spring.sum(bottomEdgeS, Spring.minus(Spring.sum(heightS,                                                                      offsetS)));     Spring eastX = Spring.sum(rightEdgeS, Spring.minus(Spring.sum(widthS, offsetS)));     Spring eastY = Spring.sum(yCenterS, Spring.minus(halfHeightS));     c.add(nb, new SpringLayout.Constraints(northX, offsetS, widthS, heightS));     c.add(sb, new SpringLayout.Constraints(northX, southY, widthS, heightS));     c.add(wb);     sl.getConstraints(wb).setX(offsetS);     sl.getConstraints(wb).setY(eastY);     sl.getConstraints(wb).setWidth(widthS);     sl.getConstraints(wb).setHeight(heightS);          c.add(eb);     sl.getConstraints(eb).setX(eastX);     sl.getConstraints(eb).setY(eastY);     sl.getConstraints(eb).setWidth(widthS);     sl.getConstraints(eb).setHeight(heightS);     c.add(viewport); // This sets a bounds of (0,0,pref_width,pref_height)     // The order here is important. You 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);     ImageIcon icon = new ImageIcon(getClass( ).getResource("terrain.gif"));     viewport.setView(new JLabel(icon));     // Hook up the buttons. See the CompassScroller class (online) for details     // on controlling the viewport.     nb.setActionCommand(CompassScroller.NORTH);     sb.setActionCommand(CompassScroller.SOUTH);     wb.setActionCommand(CompassScroller.WEST);     eb.setActionCommand(CompassScroller.EAST);     CompassScroller scroller = new CompassScroller(viewport);     nb.addActionListener(scroller);     sb.addActionListener(scroller);     eb.addActionListener(scroller);     wb.addActionListener(scroller);     setVisible(true);   }   public static void main(String args[]) {     new CompassButtons( );   } }

11.5.5 The SpringLayout.Constraints Inner Class

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 x, y, width, and height properties.

11.5.5.1 Properties

The Constraints inner class consists entirely of the properties shown in Table 11-15. 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 11-14 for a list of valid edge names and their respective relationships to the x, y, width, and height properties.

Table 11-15. SpringLayout.Constraints properties

Property

Data type

get

is

set

Default value

constrainti

Spring

·

 

·

null

height

Spring

·

 

·

null

width

Spring

·

 

·

null

x

Spring

·

 

·

null

y

Spring

·

 

·

null

iindexed (by String values; see Table 11-14).

11.5.5.2 Constructors

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 leaves the width and height properties null.

11.5.6 The Spring Class

So what are these Spring objects we keep seeing everywhere? Recall that they are essentially a collection of three values: a minimum, a maximum, and a preferred value. You can 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. 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 might have 25 for its minimum, maximum, and preferred height.

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 subsprings, which 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.

Consider the y property for the South button in the application shown in Figure 11-18. We can use subtraction to keep the button 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 11-19 shows the details of each spring behind this button. (You can refer back to the code for the syntax when creating all the springs you see in the diagram.)

Figure 11-19. Constructing the x, y, width, and height springs (and struts) for the South button
figs/swng2.1119.gif
11.5.6.1 Constant

Only one constant is defined for the Spring class, as shown in Table 11-16.

Table 11-16. Spring constant

Constant

Type

Description

UNSET

int

An indicator that this property has not yet been calculated. (Its internal value is Integer.MIN_VALUE.) Can be used as a notification to recalculate any cached values.

11.5.6.2 Properties

The properties for Spring shown in Table 11-17 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 a more dynamic spring.

Table 11-17. Spring properties

Property

Data type

get

is

set

Default value

maximum

int

·

   

UNSET

minimum

int

·

   

UNSET

preferred

int

·

   

UNSET

value

int

·

 

·

UNSET

11.5.6.3 Creating springs

The default constructor for Spring is protected. It's (obviously) meant for subclasses. To create a spring, use the factory-style constant( ) methods:

public static Spring constant(int pref)

This method creates a strut. The minimum, preferred, and maximum properties are all set to pref.

public static Spring constant(int min, int pref, int max)

This method creates a spring. The minimum, preferred, and maximum properties are set from min, pref and max, respectively.

11.5.6.4 Manipulation methods

Three manipulations are defined for springs. Recall that the calculated values are not actually hardcoded into the resulting springs. References to s1 and 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.")

public static Spring max(Spring s1, Spring s2)

This method returns a new spring whose properties represent the Math.max( ) of the respective properties of s1 and s2. In other words, the minimum property of the new spring is the max of s1.getMinimum( ) and s2.getMinimum( ).

public static Spring minus(Spring s)

This method returns a new spring whose properties represent the negative of the respective properties of s1. In other words, the minimum property of the new spring is -s1.getMinimum( ).

public static Spring sum(Spring s1, Spring s2)

This method returns a new spring whose properties represent the sum of the respective properties of s1 and s2. In other words, the minimum property of the new spring is equal to s1.getMinimum( ) + s2.getMinimum( ).

11.5.6.5 Other operations

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 s1 and s2. The Math.min( ) function can be mimicked using Spring.minus(Spring.max(Spring.minus(s1), Spring.minus(s2))). Try it on paper it really works!

11.5.7 Arranging Components

The combination of Spring math operations and constraints can make certain layouts easy to create (and easy to manipulate). We say "easy" in the "if you're designing a GUI builder" sense, of course. Figure 11-20 shows four buttons laid out in a vertical row with various Spring constraints holding them in place.

Figure 11-20. Vertically stacked buttons in a SpringLayout
figs/swng2.1120.gif

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 is placed at // (10,10). c.add(b1); sl.getConstraints(b1).setX(offsetS); sl.getConstraints(b1).setY(offsetS);      // b2 is 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 is 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 is 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 can use any one of the techniques shown on all the buttons, if you are 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 b3 and b4, however, are dependent. If b2 moves down, so does b3 and if b3 moves down, so does b4.

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 b3, b4 follows.

11.5.8 Custom Springs

There are some things that cannot be duplicated using sum( ), minus( ), and max( ). For those things, you can simply extend the Spring class. The compass navigation example in Figure 11-18 keeps the North and South buttons horizontally centered. (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:

// FractionSpring.java // import javax.swing.Spring; public class FractionSpring extends Spring {   protected Spring parent;   protected double fraction;   public FractionSpring(Spring p, double f) {     if (p == null) {       throw new NullPointerException("Parent spring cannot be null");     }     parent = p;     fraction = f;   }   public int getValue( ) {     return (int)Math.round(parent.getValue( ) * fraction);   }   public int getPreferredValue( ) {      return (int)Math.round(parent.getPreferredValue( ) * fraction);   }   public int getMinimumValue( ) {      return (int)Math.round(parent.getMinimumValue( ) * fraction);   }   public int getMaximumValue( ) {      return (int)Math.round(parent.getMaximumValue( ) * fraction);   }   public void setValue(int val) {     // Uncomment this next line to watch when our spring is resized:     // System.err.println("Value to setValue: " + val);     if (val == UNSET) {       return;     }     throw new UnsupportedOperationException("Cannot set value on a derived spring");   }   public static FractionSpring half(Spring s) {     return new FractionSpring(s, 0.5);   } }

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 that you should pay attention to is setValue( ). In several derived springs (like our FractionSpring), the 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 wrap your brain around SpringLayout and Spring math, you can accomplish some complex layouts with one container and a single, efficient layout manager.



Java Swing
Graphic Java 2: Mastering the Jfc, By Geary, 3Rd Edition, Volume 2: Swing
ISBN: 0130796670
EAN: 2147483647
Year: 2001
Pages: 289
Authors: David Geary

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net