When trying to create a user interface with Java, one of the most agonising decisions is which Layout Manager to use.
Many an application has made use of nested Border and Grid Layouts, but in the end it either becomes too hard and too nested to work with, or the window expanding properties just don’t work sensibly. More often than not the solution to this to put up with the mutilated window or set the window to a fixed size. Neither option is good as it makes the application look ugly or restricted in its usage.
Fortunately, there is a solution to solve all your problems — GridBagLayout. Unfortunately many see this option as so complex and difficult to learn that they never try. GridBagLayout really can solve almost any interface layout problem and have your windows resize sensibly and more importantly, in the fashion that you wish. All it takes is a little forethought and some patience.
The forethought
GridBagLayout is not useful for toy interfaces. Using GridBagLayout on an interface you want to slap together in a couple of minutes to see what it looks like is akin to putting up scaffolding in your living room to remove a picture hook. For toy applications you are far better off using BorderLayout and GridLayout, if the application moves beyond toy status then you should switch to GridBagLayout. Of course, beginning with an application that you expect to be non-toy in the beginning is more efficient.
Once you have decided on using GridBagLayout the next thing to do is grab a sheet of paper and a pencil, a keystroke should not be struck until you know exactly how the interface is going to look. This means you really should properly plan your application before creating any code.
Our example for learning GridBagLayout will be a small application that will display a series of photos from a Flickr RSS feed. The final interface will look like this:
Here is the original scribble I did of this interface:
As you can see, the final result looks much the same as what was intended.
You should be able to see some lines going down and across the intended interface in the mock. These lines are used to break the interface into columns and rows so we know a grid place to position each component. This would be the “Grid” part of GridBagLayout — the numbers around the interface are the grid numbers.
In one sense, thinking about GridBagLayout is no different to how table based layouts were thought about in the mean old days of HTML 3 and 4, concepts like rowspan and colspan will come into play albeit with a different name.
With our interface and grid set, it is time to layout the interface and get coding.
The work
For this section I am going to assume that you know about basic window and component creation.
The end point we want to reach with this article is the ability to layout components within a frame, in later articles we will improve this interface to make it behave like a proper interface should.
Therefore, to understand where we are heading, here is the completed code we are aiming for.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class GridBagWindow extends JFrame { private JButton searchBtn; private JComboBox modeCombo; private JLabel tagLbl; private JLabel tagModeLbl; private JLabel previewLbl; private JTable resTable; private JTextField tagTxt; public GridBagWindow() { Container contentPane = getContentPane(); GridBagLayout gridbag = new GridBagLayout(); contentPane.setLayout(gridbag); GridBagConstraints c = new GridBagConstraints(); //setting a default constraint value c.fill = GridBagConstraints.HORIZONTAL; tagLbl = new JLabel("Tags"); c.gridx = 0; //x grid position c.gridy = 0; //y grid position gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object contentPane.add(tagLbl); //add it to content pane tagModeLbl = new JLabel("Tag Mode"); c.gridx = 0; c.gridy = 1; gridbag.setConstraints(tagModeLbl, c); contentPane.add(tagModeLbl); tagTxt = new JTextField("plinth"); c.gridx = 1; c.gridy = 0; c.gridwidth = 2; gridbag.setConstraints(tagTxt, c); contentPane.add(tagTxt); String[] options = {"all", "any"}; modeCombo = new JComboBox(options); c.gridx = 1; c.gridy = 1; c.gridwidth = 1; gridbag.setConstraints(modeCombo, c); contentPane.add(modeCombo); searchBtn = new JButton("Search"); c.gridx = 1; c.gridy = 2; gridbag.setConstraints(searchBtn, c); contentPane.add(searchBtn); resTable = new JTable(5,3); c.gridx = 0; c.gridy = 3; c.gridwidth = 3; gridbag.setConstraints(resTable, c); contentPane.add(resTable); previewLbl = new JLabel("Preview goes here"); c.gridx = 0; c.gridy = 4; gridbag.setConstraints(previewLbl, c); contentPane.add(previewLbl); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void main(String args[]) { GridBagWindow window = new GridBagWindow(); window.setTitle("GridBagWindow"); window.pack(); window.setVisible(true); } }
The opening lines until the constructor should not cause too much grief, they are fairly standard imports and variable creation.
When we hit the constructor though, things get interesting:
Container contentPane = getContentPanegetContentPane();
GridBagLayout gridbag = new GridBagLayout();
contentPane.setLayout(gridbag);
We open with grabbing the GridBagWindow’sGridBagWindow’s content pane, creating a GridBagLayout object, exactly the same as how we have created GridLayout and BorderLayout objects in the past and then setting our GridBagLayout object as the content pane’s layout.
GridBagConstraintsGridBagConstraints c = new GridBagConstraintsGridBagConstraints();
Then we come to the unique object within the whole process, GridBagConstraintsGridBagConstraints. This object controls the constraints placed on all components within the GridBagLayout. To add a component into your GridBagLayout, you must first associate it with a GridBagConstraintsGridBagConstraints object.
GridBagConstraintsGridBagConstraints has 11 fields that it is possible to manipulate as well as a number of statics to help you out. Those fields are:
- gridx – the row which the component will occupy within the grid.
- gridy – the column which the component will occupy within the grid.
- gridwidth – the number of columns which the component will occupy, this is analogous to colspan in HTML.
- gridheight – the number of rows which the component will occupy, this is analogous to rowspan in HTML.
- weightx – tells the layout manager how to distribute any extra horizontal space.
- weighty – tells the layout manager how to distribute any extra vertical space.
- anchor – tells the layout manager at which point to place the component within its grid space.
- fill – how the component should behave if the display area is larger than the component. Can fill horizontally, vertically or both ways.
- insets – the external padding between the component and the edges of its grid space.
- ipadx – how many pixels of internal padding to add to the minimum width of the component. The width of the component is at least its minimum width plus ipadx pixels.
- ipady – how many pixels of internal padding to add to the minimum height of the component. The height of the component is at least its minimum width plus ipady pixels.
It is possible for each instance of a component that you create an individual GridBagConstraintsGridBagConstraints for it; however this is not recommended. It is far better to set-up your defaults for the object when you call it, then manipulate the fields you wish for each component. This is generally because some fields like insets, padx, pady and fill generally remain the same for every component, therefore it is easier to set a field and carry the changed field onto the next component. If you want to go back to the original field value after changing it then you should do it before adding the next component. It’s an easy way to keep track of what you are modifying and is easier to follow than a series of object calls with 11 parameters.
If it all appears as clear as mud currently, take solace in the fact that once you understand the GridBagConstraintsGridBagConstraints, then the hard work is done and the rest should fall easily into place.
So now that we have covered the GridBagConstraintsGridBagConstraints in a fair amount of detail, let’s see what we have to do to actually use it:
tagLbl = new JLabel("Tags"); c.gridx = 0; //x grid position c.gridy = 0; //y grid position gridbag.setConstraints(tagLbl, c); //associate the label with a constraint object contentPane.add(tagLbl); //add it to content pane
All we do is instantiate our label, assign it a grid position, associate it with a constraint object and add it do our content pane.
We know do the same for our next label:
tagModeLbl = new JLabel("Tag Mode"); c.gridx = 0; c.gridy = 1; gridbag.setConstraints(tagModeLbl, c); contentPane.add(tagModeLbl);
Notice that even though we have already set the gridx on our constraints object to be 0, we still set it again here — this is for no other reason than readability.
Next we add our text field that store the keyword we want to search on and the combo box that will decide how to search on multiple keywords. The concepts are exactly the same as above, except that we wish to have the text field extend for two columns, then we need to reset that value before we add the combo box.
tagTxt = new JTextField("plinth"); c.gridx = 1; c.gridy = 0; c.gridwidth = 2; gridbag.setConstraints(tagTxt, c); contentPane.add(tagTxt); String[] options = {"all", "any"}; modeCombo = new JComboBox(options); c.gridx = 1; c.gridy = 1; c.gridwidth = 1; gridbag.setConstraints(modeCombo, c); contentPane.add(modeCombo);
After this, we are simply adding the rest of the components onto the content pane using techniques that we have already seen; the rest of the code should not cause any problems.
At this stage we should have an interface that looks somewhat like what we intended.
The Patience
Of course, the interface does not behave in an intelligent matter yet. Resize the window and see what happens. Why is it doing that? It’s because we have yet to give the constraints object sensible values for the weightx, weighty and fill fields.
This is something that we will be covering later, but if you wish to try for yourself then the GridBagLayout and GridBagConstraints API pages are good places to extend your knowledge.