Saturday, May 26, 2012


Designing Swing Framework - Layout Managers


Why so much fuss about Java swings layout managers?


Windows programmers always tend to think about why Java makes so much fuss about layout managers. In Windows, its not a big task. You just have to use a dialogue editor to drag and drop components and place them appropriately to get a very professional looking GUI.

The problem with this approach is that the resulting layout must be manually updated if the size of the components changes. Why would the component size change? There are two common cases. First, a user may choose a larger font for button labels and other dialog text. If you try this out for yourself in Windows, you will find that many applications deal with this exceedingly poorly. The buttons do not grow, and the larger font is simply crammed into the same space as before. The same problem can occur when the strings in an application are translated to a foreign language. For example, the German word for “Cancel” is “Abbrechen.” If a button has been designed with just enough room for the string “Cancel”, then the German version will look broken, with a clipped command string.

Why don’t Windows buttons simply grow to accommodate the labels? Because the designer of the user interface gave no instructions in which direction they should grow. After the dragging and dropping and arranging, the dialog editor merely remembers the pixel position and size of each component. It does not remember why the components were arranged in this fashion.

Layout Managers in Java Swings

The Java Swings layout managers are a much better approach to component layout. With a layout manager, the layout comes with instructions about the relationships among the components. This was particularly important in the original AWT, which used native user interface elements. The size of a button or list box in Motif, Windows, and the Macintosh could vary widely, and an application or applet would not know a priori on which platform it would display its user interface. To some extent, that degree of variability has gone away with Swing. 

GridBagLayout is the lay out provided in JDK 1.0 and is the most successful layout manager developed so far.GridBagLayout is one of the most flexible and complex  layout managers the Java platform provides. A GridBagLayout places components in a grid of rows and columns, allowing specified components to span multiple rows or columns. Not all rows necessarily have the same height. Similarly, not all columns necessarily have the same width. Essentially, GridBagLayout places components in rectangles (cells) in a grid, and then uses the components' preferred sizes to determine how big the cells should be.

GridBag Layout appears quite complex to a new programmer.In order to cater to this problem Swing designers came up with the box layout. However, because each box is laid out independently, you cannot use box layouts to arrange neighboring components both horizontally and vertically. Hence it failed.

Java SE 1.4 saw yet another attempt to design a replacement for the grid bag layout—the spring layout. You use imaginary springs to connect the components in a container. As the container is resized, the springs stretch or shrink, thereby adjusting the positions of the components. This sounds tedious and confusing, and it is. The spring layout quickly sank into obscurity.

In 2005, the NetBeans team invented the Matisse technology, which combines a layout tool and a layout manager. A user interface designer uses the tool to drop components into a container and to indicate which components should line up. The tool translates the designer’s intentions into instructions for the group layout manager. This is much more convenient than writing layout management code by hand. The group layout manager is now a part of Java SE 6. This background code for this layout manager quickly becomes quite lenghty and complex when few components are added vertically and horizontally with some deletion of compenets during development.


All about GridBagLayout Manager


The way java program specifies the size and position characteristics of its components is by specifying constraints for each component. The preferred approach to set constraints on a component is to use the Container.add variant, passing it a GridBagConstraints object.
Following section is straight from sun Java documentation

gridx, gridy:

Specify the row and column at the upper left of the component. The leftmost column has address gridx=0 and the top row has address gridy=0. Use GridBagConstraints.RELATIVE (the default value) to specify that the component be placed just to the right of (for gridx) or just below (for gridy) the component that was added to the container just before this component was added. We recommend specifying the gridx and gridy values for each component rather than just using GridBagConstraints.RELATIVE; this tends to result in more predictable layouts.

gridwidth, gridheight:

Specify the number of columns (for gridwidth) or rows (for gridheight) in the component's display area. These constraints specify the number of cells the component uses, not the number of pixels it uses. The default value is 1. Use GridBagConstraints.REMAINDER to specify that the component be the last one in its row (for gridwidth) or column (for gridheight). Use GridBagConstraints.RELATIVE to specify that the component be the next to last one in its row (for gridwidth) or column (for gridheight). We recommend specifying the gridwidth and gridheight values for each component rather than just using GridBagConstraints.RELATIVE and GridBagConstraints.REMAINDER; this tends to result in more predictable layouts.

fill:

Used when the component's display area is larger than the component's requested size to determine whether and how to resize the component. Valid values (defined as GridBagConstraints constants) include NONE (the default), HORIZONTAL (make the component wide enough to fill its display area horizontally, but do not change its height), VERTICAL (make the component tall enough to fill its display area vertically, but do not change its width), and BOTH (make the component fill its display area entirely

ipadx, ipady:

Specifies the internal padding: how much to add to the size of the component. The default value is zero. The width of the component will be at least its minimum width plus ipadx*2 pixels, since the padding applies to both sides of the component. Similarly, the height of the component will be at least its minimum height plus ipady*2 pixels.

insets:

Specifies the external padding of the component -- the minimum amount of space between the component and the edges of its display area. The value is specified as an Insets object. By default, each component has no external padding.

anchor:

Used when the component is smaller than its display area to determine where (within the area) to place the component. Valid values (defined as GridBagConstraints constants) are CENTER (the default), PAGE_START, PAGE_END, LINE_START, LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_END, and LAST_LINE_START.


Making it Simple


NetBeans provides a graphical representation to remeber the meaning of above constraints . see the figure below.

Avoiding GridBagLayout Issues


The AWT documentation recommends that instead of setting the gridx and gridy values to absolute positions, you set them to the constant GridBagConstraints.RELATIVE. Then, add the components to the grid bag layout in a standardized order, going from left to right in the first row, then moving along the next row, and so on.
You still specify the number of rows and columns spanned, by giving the appropriate gridheight and gridwidth fields. Except, if the component extends to the last row or column, you aren’t supposed to specify the actual number, but the constant GridBagConstraints.REMAINDER. This tells the layout manager that the component is the last one in its row.


The way framework programmer works


The most tedious aspect of the grid bag layout is writing the code that sets the constraints. Most programmers write helper functions or a small helper class for this purpose. Following is an example of one such helper class. Following are its features. Full class has been provided at the end of this section.

-Its name is short: GBC instead of GridBagConstraints.

-It extends GridBagConstraints, so you can use shorter names such as GBC.EAST for the constants.

-Use a GBC object when adding a component, such as
add(component, new GBC(1, 2));

-There are two constructors to set the most common parameters: gridx and gridy, or gridx, gridy, gridwidth, and gridheight.
add(component, new GBC(1, 2, 1, 4));

-There are convenient setters for the fields that come in x/y pairs:
add(component, new GBC(1, 2).setWeight(100, 100));

-The setter methods return this, so you can chain them:
add(component, new GBC(1, 2).setAnchor(GBC.EAST).setWeight(100, 100));

-The setInsets methods construct the Insets object for you. To get one-pixel insets, simply call
add(component, new GBC(1, 2).setAnchor(GBC.EAST).setInsets(1));

GBC.Java

package com.example.layout;

import java.awt.GridBagConstraints;
import java.awt.Insets;

public class GBC extends GridBagConstraints {

public GBC (int gridx, int gridy) {
this.gridx=gridx;
this.gridy=gridy;
}
public GBC (int gridx, int gridy, int gridwidth, int gridheight) {
this.gridx=gridx;
this.gridy=gridy;
this.gridwidth=gridwidth;
this.gridheight=gridheight;
}
public GBC setFill(int fill) {
this.fill=fill;
return this;
}
public GBC setAnchor(int anchor) {
this.anchor=anchor;
return this;
}
public GBC setInsets(int distance) {
this.insets=new Insets(distance, distance, distance, distance);
return this;
}
public GBC setInsets(int top, int left, int bottom, int right ) {
this.insets=new Insets(top, left, bottom, right);
return this;
}
public GBC setWeight(double weightx, double weighty) {
this.weightx=weightx;
this.weighty=weighty;
return this;
}
public GBC setIpad(int ipadx, int ipady) {
this.ipadx=ipadx;
this.ipady=ipady;
return this;
}
}

Test Example Class

package com.example.layout;

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;


public class OWLODF {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
OdfFrame frame = new OdfFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

}
/**
 * Default values
 * Remainder= 0, Relative= -1
 * gridx = Default:GridBagConstraints.RELATIVE=-1, it should be a non-negative value.gridx defines which column
 * gridy = Default:GridBagConstraints.RELATIVE=-1, it should be a non-negative value.gridy defines which row
 * gridwidth = Default: value=1.Use REMAINDER to specify that the component's display area will be from gridx to the last cell in the row. 
 *   Use RELATIVE to specify that the component's display area will be from gridx to the next to the last one in its row.
 * Default value=1, it should be non-negative. gridwidth defines span in terms of column.In other words how many columns it should occupy
 * gridheight = Default: value=1.Use REMAINDER to specify that the component's display area will be from gridy to the last cell in the column. 
 * Use RELATIVE to specify that the component's display area will be from gridy to the next to the last one in its column
 * gridheight should be a non-negative value and the default value is 1.gridheight defines span in terms of row.In other words how many rows it should occupy
 * weightx = Default 0, non-negative. If 1 it will expand horizontally .Zero means it wont expand in x dir. Better to keep it 0 and disbale resizing of frame
 * weighty = Default 0, non-negative. If 1 it will expand vertically .Zero means it wont expand in y dir. Better to keep it 0 and disbale resizing of frame
 * fill = Default:NONE
 * anchor = Default: CENTRE. There are three possible orientations, namely relative(e.g.PAGE_START, LINE_START etc), baseline relative (e.g BASELINE_LEADING, BASELINE_TRAILING etc) and absolute(e.g. NORTH, SOUTH etc).
 * insets = Default: Insets(0, 0, 0, 0). this is for external padding
 * ipadx = Default: 0
 * ipady = Default: 0
 * @author Lenovo
 *
 */
class OdfFrame extends JFrame{
int G_XY_DEFAULT = -1;
int G_WH_DEFAULT = 1;
int REMAINDER= GridBagConstraints.REMAINDER;//0
int RELATIVE=GridBagConstraints.RELATIVE;//-1
double WT_X_DEFAULT=0;
double WT_Y_DEFAULT=0;
int IPDX_DEFAULT=0;
int IPDY_DEFAULT=0;
OdfFrame(){
this.setTitle("ODF BB Client");
JPanel mainPanel = new JPanel();
mainPanel.setBorder(new TitledBorder("Main Panel"));
mainPanel.setName("Main Panel");
GridBagLayout mainGBL = new GridBagLayout();
mainPanel.setLayout(mainGBL);
JPanel panel_1 = new JPanel();
panel_1.setBorder(new TitledBorder("Process_1"));
GridBagLayout procGBL_1 = new GridBagLayout();
panel_1.setLayout(procGBL_1);
JLabel owlAgent_1 = new JLabel("OWL Agent_1");
JTextField owlAgentF_1 = new JTextField(15);
owlAgentF_1.setText("owlAgentF_1");
JLabel system_1 = new JLabel("System_1");
JTextField systemF_1 = new JTextField(15);
systemF_1.setText("systemF_1");
JLabel process_1 = new JLabel("Process_1");
JTextField processF_1 = new JTextField(15);
processF_1.setText("processF_1");
JLabel instance_1 = new JLabel("Instance_1");
JTextField instanceF_1 = new JTextField(15);
instanceF_1.setText("instanceF_1");
/////
JPanel panel_2 = new JPanel();
panel_2.setBorder(new TitledBorder("Process_2"));
GridBagLayout procGBL_2 = new GridBagLayout();
panel_2.setLayout(procGBL_2);
JLabel owlAgent_2 = new JLabel("OWL Agent_2");
JTextField owlAgentF_2 = new JTextField(15);
owlAgentF_2.setText("owlAgentF_2");
JLabel system_2 = new JLabel("System_2");
JTextField systemF_2 = new JTextField(15);
systemF_2.setText("systemF_2");
JLabel process_2 = new JLabel("Process_2");
JTextField processF_2 = new JTextField(15);
processF_2.setText("processF_2");
JLabel instance_2 = new JLabel("Instance_2");
JTextField instanceF_2 = new JTextField(15);
instanceF_2.setText("instanceF_2");
////
///////
JPanel panel_3 = new JPanel();
panel_3.setBorder(new TitledBorder("Process_3"));
GridBagLayout procGBL_3 = new GridBagLayout();
panel_2.setLayout(procGBL_3);
JTextArea area = new JTextArea(8,40);
JScrollPane scrollPane = new JScrollPane(area);

area.setText("this is a text area");
area.setWrapStyleWord(true);
area.setLineWrap(true);
addComponentsToPanel(panel_1, owlAgent_1,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 20, 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, owlAgentF_1, defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, system_1,    defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, systemF_1,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, process_1,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, processF_1,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER,    G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, instance_1,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, REMAINDER   , 5, 20, 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_1, instanceF_1, defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
////
addComponentsToPanel(panel_2, owlAgent_2,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 20, 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, owlAgentF_2, defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, system_2,    defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, systemF_2,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, process_2,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, processF_2,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER,    G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, instance_2,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, REMAINDER   , 5, 20, 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(panel_2, instanceF_2, defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, G_WH_DEFAULT, G_WH_DEFAULT, 5, 5 , 5, 30, WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
////
addComponentsToPanel(panel_3, scrollPane,  defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER, G_WH_DEFAULT, 5, 20, 5, 5 , WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(mainPanel, panel_1,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER   , G_WH_DEFAULT, 5, 5, 5, 5 ,  WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(mainPanel, panel_2,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER   , G_WH_DEFAULT, 5, 5, 5, 5 ,  WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
addComponentsToPanel(mainPanel, panel_3,   defineConstraints(G_XY_DEFAULT, G_XY_DEFAULT, REMAINDER   , G_WH_DEFAULT, 5, 5, 5, 5 ,  WT_X_DEFAULT, WT_Y_DEFAULT, IPDX_DEFAULT, IPDY_DEFAULT, GridBagConstraints.NONE, GridBagConstraints.WEST));
this.add(mainPanel);

}
public void addComponentsToPanel(JPanel panel, JComponent component,GBC constraints) {
panel.setBackground(Color.WHITE);
panel.add(component, constraints);
}
public GBC defineConstraints(int gridx,int gridy,int gridwidth, int gridheight, int topSpace, int leftSpace, int bottomSpace, int rightSpace,double weightx, double weighty,int ipadx, int ipady, int fill, int anchor) {
return new GBC(gridx, gridy, gridwidth, gridheight).setInsets(topSpace, leftSpace, bottomSpace, rightSpace ).setWeight(weightx, weighty).setIpad(ipadx, ipady).setFill(fill).setAnchor(anchor);
}
}

No comments:

Post a Comment