In addition to the layout managers provided in Java, you can create your own layout managers. To do so, you need to understand how a layout manager lays out components . A container's setLayout method specifies a layout manager for the container. The layout manager is responsible for laying out the components and displaying them in a desired location with an appropriate size . Every layout manager must directly or indirectly implement the LayoutManager interface. For instance, FlowLayout directly implements LayoutManager , and BorderLayout implements LayoutManager2 , a subclass of LayoutManager . The LayoutManager interface provides the following methods for laying out components in a container:
public void addLayoutComponent(String name , Component comp)
Adds the specified component with the specified name to the container.
public void layoutContainer(Container parent)
Lays out the components in the specified container. In this method, you should provide concrete instructions that specify where the components are to be placed.
public Dimension minimumLayoutSize(Container parent)
Calculates the minimum size dimensions for the specified panel, given the components in the specified parent container.
public Dimension preferredLayoutSize(Container parent)
Calculates the preferred size dimensions for the specified panel, given the components in the specified parent container.
public void removeLayoutComponent(Component comp)
Removes the specified component from the layout.
These methods in LayoutManager are invoked by the methods in the java.awt.Container class through the layout manager in the container. Container contains a property named layout (an instance of LayoutManager ) and the methods for adding and removing components from the container. There are five overloading add methods defined in Container for adding components with various options. The remove method removes a component from the container. The add method invokes addImpl , which then invokes the addLayoutComponent method defined in the LayoutManager interface. The layoutContainer method in the LayoutManager interface is indirectly invoked by validate through several calls. The remove method invokes removeLayoutComponent in LayoutManager . The validate method is invoked to refresh the container after the components it contains have been added to or modified. The relationship of Container and LayoutManager is shown in Figure 28.14.
Let us declare a custom layout manager named DiagonalLayout that places the components in a diagonal. To test DiagonalLayout , the example creates an applet with radio buttons named "FlowLayout," "GridLayout," and "DiagonalLayout," as shown in Figure 28.15. You can dynamically select one of these three layouts in the panel.
The DiagonalLayout class is similar to FlowLayout . DiagonalLayout arranges components along a diagonal using each component's natural preferredSize . It contains three constraints, gap , lastFill , and majorDiagonal , as shown in Figure 28.16. The source code for DiagonalLayout is given in Listing 28.7.
1 import java.awt.*; 2 3 public class DiagonalLayout implements LayoutManager, 4 java.io.Serializable { 5 /** Vertical gap between the components */ 6 private int gap = 10 ; 7 8 /** True if components are placed along the major diagonal */ 9 private boolean majorDiagonal = true ; 10 11 /* True if the last component is stretched to fill the space */ 12 private boolean lastFill = false ; 13 14 /** Constructor */ 15 public DiagonalLayout() { 16 } 17 18 public void addLayoutComponent(String name, Component comp) { 19 // TODO: implement this java.awt.LayoutManager method 20 } 21 22 public void removeLayoutComponent(Component comp) { 23 // TODO: implement this java.awt.LayoutManager method 24 } 25 26 public Dimension preferredLayoutSize(Container parent) { 27 // TODO: implement this java.awt.LayoutManager method 28 return minimumLayoutSize(parent); 29 } 30 31 public Dimension minimumLayoutSize(Container parent) { 32 // TODO: implement this java.awt.LayoutManager method; 33 return new Dimension( , ); 34 } 35 36 public void layoutContainer(Container parent) { 37 // TODO: implement this java.awt.LayoutManager method; 38 int numberOfComponents = parent.getComponentCount(); 39 40 Insets insets = parent.getInsets(); 41 int w = parent.getSize().width - insets.left - insets.right; 42 int h = parent.getSize().height - insets.bottom - insets.top; 43 44 if (majorDiagonal) { 45 int x = 10 , y = 10 ; 46 47 for ( int j = ; j < numberOfComponents; j++) { 48 Component c = parent.getComponent(j); 49 Dimension d = c.getPreferredSize(); 50 51 if (c.isVisible()) 52 if (lastFill && (j == numberOfComponents - 1 )) 53 c.setBounds(x, y, w - x, h - y); 54 else 55 c.setBounds(x, y, d.width, d.height); 56 x += d.height + gap; 57 y += d.height + gap; 58 } 59 } 60 else { // It is subdiagonal 61 int x = w - 10 , y = 10 ; 62 63 for ( int j = ; j < numberOfComponents; j++) { 64 Component c = parent.getComponent(j); 65 Dimension d = c.getPreferredSize(); 66 67 if (c.isVisible()) 68 if (lastFill & (j == numberOfComponents - 1 )) 69 c.setBounds( , y, x, h - y); 70 else 71 c.setBounds(x, d.width, y, d.height); 72 73 x -= (d.height + gap); 74 y += d.height + gap; 75 } 76 } 77 } 78 79 public int getGap() { 80 return gap; 81 } 82 83 public void setGap( int gap) { 84 this .gap = gap; 85 } 86 87 public void setMajorDiagonal( boolean newMajorDiagonal) { 88 majorDiagonal = newMajorDiagonal; 89 } 90 91 public boolean isMajorDiagonal() { 92 return majorDiagonal; 93 } 94 95 public void setLastFill( boolean newLastFill) { 96 lastFill = newLastFill; 97 } 98 99 public boolean isLastFill() { 100 return lastFill; 101 } 102 } |
The DiagonalLayout class implements the LayoutManger and Serializable interfaces (lines 3 “4). The reason to implement Serializable is to make it a JavaBeans component.
The Insets class describes the size of the borders of a container. It contains the variables left , right , bottom , and top , which correspond to the measurements for the left border , right border , top border , and bottom border (lines 40 “42).
The Dimension class used in DiagonalLayout encapsulates the width and height of a component in a single object. The class is associated with certain properties of components. Several methods defined by the Component class and the LayoutManager interface return a Dimension object.
Listing 28.8 gives a test program that uses DiagonalLayout .
1 import javax.swing.*; 2 import javax.swing.border.*; 3 import java.awt.*; 4 import java.awt.event.*; 5 6 public class ShowDiagonalLayout extends JApplet { 7 private FlowLayout flowLayout = new FlowLayout(); 8 private GridLayout gridLayout = new GridLayout( 2 , 2 ); 9 private DiagonalLayout diagonalLayout = new DiagonalLayout(); 10 11 private JButton jbt1 = new JButton( "Button 1" ); 12 private JButton jbt2 = new JButton( "Button 2" ); 13 private JButton jbt3 = new JButton( "Button 3" ); 14 private JButton jbt4 = new JButton( "Button 4" ); 15 16 private JRadioButton jrbFlowLayout = 17 new JRadioButton( "FlowLayout" ); 18 private JRadioButton jrbGridLayout = 19 new JRadioButton( "GridLayout" ); 20 private JRadioButton jrbDiagonalLayout = 21 new JRadioButton( "DiagonalLayout" , true ); 22 23 private JPanel jPanel2 = new JPanel(); 24 25 public ShowDiagonalLayout() { 26 // Set default layout in jPanel2 27 jPanel2.setLayout(diagonalLayout); 28 jPanel2.add(jbt1); 29 jPanel2.add(jbt2); 30 jPanel2.add(jbt3); 31 jPanel2.add(jbt4); 32 jPanel2.setBorder( new LineBorder(Color.black)); 33 34 JPanel jPanel1 = new JPanel(); 35 jPanel1.setBorder( new TitledBorder( "Select a Layout Manager" )); 36 jPanel1.add(jrbFlowLayout); 37 jPanel1.add(jrbGridLayout); 38 jPanel1.add(jrbDiagonalLayout); 39 40 ButtonGroup buttonGroup1 = new ButtonGroup(); 41 buttonGroup1.add(jrbFlowLayout); 42 buttonGroup1.add(jrbGridLayout); 43 buttonGroup1.add(jrbDiagonalLayout); 44 45 add(jPanel1, BorderLayout.SOUTH); 46 add(jPanel2, BorderLayout.CENTER); 47 48 jrbFlowLayout.addActionListener( new ActionListener() { 49 public void actionPerformed(ActionEvent e) { 50 jPanel2.setLayout(flowLayout); 51 jPanel2.validate(); 52 } 53 }); 54 jrbGridLayout.addActionListener( new ActionListener() { 55 public void actionPerformed(ActionEvent e) { 56 jPanel2.setLayout(gridLayout); 57 jPanel2.validate(); 58 } 59 }); 60 jrbDiagonalLayout.addActionListener( new ActionListener() { 61 public void actionPerformed(ActionEvent e) { 62 jPanel2.setLayout(diagonalLayout); 63 jPanel2.validate(); 64 } 65 }); 66 } 67 } |
The TestDiagonalLayout class enables you to dynamically set the layout in jPanel2 . When you select a new layout, the layout manager is set in jPanel2 , and the validate() method is invoked (lines 51, 57, 63), which in turn invokes the layoutContainer method in the LayoutManager interface to display the components in the container.