Containers

A container is a kind of component that holds and manages other components. JComponent objects can be containers, because the JComponent class descends from the Container class.

Three of the most useful container types are JFrame , JPanel, and JApplet. A JFrame is a top-level window on your display. JFrame is derived from JWindow, which is pretty much the same but lacks a border. A JPanel is a generic container element used to group components inside of JFrames and other JPanels. The JApplet class is a kind of container that provides the foundation for applets that run inside web browsers. Like every other JComponent, a JApplet has the ability to contain other user interface components. You can also use the JComponent class directly, like a JPanel, to hold components inside of another container. With the exception of JFrame and JWindow, all the components and containers in Swing are lightweight.

A container maintains the list of “child” components that it manages, and has methods for dealing with those components. Note that this child relationship refers to a visual hierarchy, not a subclass/superclass hierarchy. By themselves, most components aren’t very useful until they are added to a container and displayed. The add( ) method of the Container class adds a component to the container. Thereafter, this component can be displayed in the container’s display area and positioned by its layout manager. You can remove a component from a container with the remove( ) method.

Layout Managers

A layout manager is an object that controls the placement and sizing of components within the display area of a container. A layout manager is like a window manager in a display system; it controls where the components go and how big they are. Every container has a default layout manager, but you can install a new one by calling the container’s setLayout( ) method.

Swing comes with a few layout managers that implement common layout schemes. The default layout manager for a JPanel is a FlowLayout, which tries to place objects at their preferred size from left to right and top to bottom in the container. The default for a JFrame is a BorderLayout, which places a limited number of objects at named locations within the window, such as NORTH, SOUTH, and CENTER. Another layout manager, GridLayout , arranges components in a rectangular grid. The most general (and difficult to use) layout manager is GridBagLayout, which lets you do the kinds of things you can do with HTML tables. (We’ll get into the details of all of these layout managers in Chapter 16.)

When you add a component to a container, you’ll often use the version of add( ) that takes a single Component as an argument. However, if you’re using a layout manager that uses “constraints,” like BorderLayout or GridBagLayout, you must specify additional information about where to put the new component. For that you can use the version that takes a constraint object. For example, here’s how to place a component at the top edge of a container that uses a BorderLayout manager:

myContainer.add(myComponent, BorderLayout.NORTH);

In this case, the constraint object is the static member variable NORTH. GridBagLayout uses a much more complex constraint object to specify positioning.

Insets

Insets specify a container’s margins; the space specified by the container’s insets won’t be used by a layout manager. Insets are described by an Insets object, which has four public int fields: top, bottom, left, and right. You normally don’t need to worry about the insets; the container will set them automatically, taking into account extras like the menu bar that may appear at the top of a frame. To find out the insets, call the component’s getInsets( ) method, which returns an Insets object.

Z-Ordering (Stacking Components)

With the standard layout managers, components are not allowed to overlap. However, if you use custom-built layout managers or absolute positioning, components within a container may overlap. If they do, the order in which components were added to a container matters. When components overlap they are “stacked” in the order in which they were added: the first component added to the container is on top; the last is on the bottom. To give you more control over stacking, two additional forms of the add( ) method take an additional integer argument that lets you specify the component’s exact position in the container’s stacking order.

The revalidate( ) and doLayout( ) Methods

A layout manager arranges the components in a container only when asked to. Several things can mess up a container after it’s initially laid out:

  • Changing its size

  • Resizing or moving one of its child components

  • Adding, showing, removing, or hiding a child component

Any of these actions cause the container or its components to be marked invalid . This means it needs to have its child components readjusted by its layout manager. In most cases, Swing will re-layout container automatically. There are a few cases where you may need to tell Swing to fix things. One example is when you change the preferred size of a component. To fix up the layout, call the revalidate( ) method. revalidate( ) marks a component (or container) invalid and calls Container’s doLayout( ) method, which asks the layout manager to do its job. In addition, revalidate( ) also notes that the Container has been fixed (i.e., it’s valid again) and looks at each child component of the container, recursively validating any containers or components that are also messed up.

So if you have a small JPanel—say a keypad holding some buttons—and you change the preferred size of the JPanel by calling its setPreferredSize( ) method, you should also call revalidate( ) on the panel or its container. The layout manager of the panel’s container may then reposition or resize the keypad. It also automatically calls revalidate( ) for the keypad itself, so that it can rearrange its buttons to fit inside its new area.

All components, not just containers, maintain a notion of when they are valid or invalid. If the size, location, or internal layout of a component changes, its revalidate( ) will automatically be called.

Child containers are validated only if they are invalid. That means that if you have an invalid component nested inside a valid component and you validate a container above both, the invalid component may never be reached. To help avoid this situation, the invalidate( ) method that marks a container as dirty automatically marks parent containers as well, all the way up the container hierarchy.

Managing Components

There are a few additional tools of the Container class that we should mention:

Component[] getComponents ( )

Returns the container’s components in an array.

void list (PrintWriter out , int indent )

Generates a list of the components in this container and writes them to the specified PrintWriter.

Component getComponentAt (int x , int y )

Tells you what component is at the specified coordinates in the container’s coordinate system.

Listening for Components

You can use the ContainerListener interface to automate the setting up of a container’s new components. A container that implements this interface can receive an event whenever it gains or loses a component. This facility makes it easy for a container to micro-manage its components.

Windows and Frames

Windows and frames are the top-level containers for Java components. A JWindow is simply a plain, graphical screen that displays in your windowing system. Windows have no frills; they are mainly suitable for making “splash” screens and dialogs. JFrame , on the other hand, is a subclass of JWindow that has a border and can hold a menu bar. You can drag a frame around on the screen and resize it, using the ordinary controls for your windowing environment. Figure 13.2 shows a JFrame on the left and a JWindow on the right.

A frame and a window

Figure 13-2. A frame and a window

All other Swing components and containers must be held, at some level, inside of a JWindow or JFrame. Applets are a kind of Container. Even applets must be housed in a frame or window, though normally you don’t see an applet’s parent frame because it is part of (or simply is) the browser or appletviewer displaying the applet.

JFrames and JWindows are the only components that can be displayed without being added or attached to another Container. After creating a JFrame or JWindow, you can call the setVisible( ) method to display it. The following short application creates a JFrame and a JWindow and displays them side by side, just like in Figure 13.2.

//file: TopLevelWindows.java
import javax.swing.*;

public class TopLevelWindows {
  public static void main(String[] args) {
    JFrame f = new JFrame("The Frame");
    f.setSize(300, 300);
    f.setLocation(100, 100);
    
    JWindow w = new JWindow( );
    w.setSize(300, 300);
    w.setLocation(500, 100);
    
    f.setVisible(true);
    w.setVisible(true);
  }
}

The JFrame constructor can take a String argument that supplies a title, displayed in the JFrame’s title bar. (Another approach would be to create the JFrame with no title and call setTitle( ) to supply the title later.) The JFrame’s size and location on your desktop is determined by the calls to setSize( ) and setLocation( ). After creating the JFrame, we create a JWindow in almost exactly the same way. The JWindow doesn’t have a title bar, so there are no arguments to the JWindow constructor.

Once the JFrame and JWindow are set up, we call setVisible(true) to get them on the screen. The setVisible( ) method returns immediately, without blocking. Fortunately, our application does not exit, even though we’ve reached the end of the main( ) method, because the windows are still visible. You can close the JFrame by clicking on the close button in the title bar. JFrame’s default behavior is to hide itself when you click on the box by calling setVisible(false). You can alter this behavior by calling the setDefaultCloseOperation( ) method, or by adding an event listener, which we’ll cover later.

There’s no way to close the JWindow inside our application. You will have to hit Ctrl-C or whatever keystroke kills a process on your machine to stop execution of the TopLevelWindows application.

Other Methods for Controlling Frames

The setLocation( ) method of the Component class can be used on a JFrame or JWindow to set its position on the screen. The x and y coordinates are relative to the screen’s origin (the top left corner).

You can use the toFront( ) and toBack( ) methods to place a JFrame or JWindow in front of, or behind, other windows. By default, a user is allowed to resize a JFrame, but you can prevent resizing by calling setResizable(false) before showing the JFrame.

On most systems, frames can be "iconified”; that is, they can be represented by a little icon image. You can get and set a frame’s icon image by calling getIconImage( ) and setIconImage( ). As you can with all components, you set the cursor by calling the setCursor( ) method.

Using Content Panes

Windows and frames don’t behave exactly like regular containers. With other containers, you can add child components with the add( ) method. JFrame and JWindow have some extra stuff in them (mostly to support Swing’s peerless components), so you can’t just add( ) components directly. Instead, you need to add the components to the associated content pane. The content pane is just a Container that covers the visible area of the JFrame or JWindow. Whenever you create a new JFrame or JWindow, a content pane is automatically created for you. You can retrieve it with getContentPane( ) . Here’s another example that creates a JFrame and adds some components to its content pane:

//file: MangoMango1.java
import java.awt.*;
import javax.swing.*;

public class MangoMango1 {
  public static void main(String[] args) {
    JFrame f = new JFrame("The Frame");
    f.setLocation(100, 100);
    
    Container content = f.getContentPane( );
    content.setLayout(new FlowLayout( ));
    content.add(new JLabel("Mango"));
    content.add(new JButton("Mango"));
    
    f.pack( );
    f.setVisible(true);
  }
}

The call to JFrame’s pack( ) method tells the frame window to resize itself so it exactly fits its components. Instead of having to determine the size of the JFrame, we just tell it to be “just big enough.” If you do ever want to set the absolute size of the JFrameyourself, call setSize( ) instead.

If you create your own Container, you can make it the content pane of a JFrame or JWindow by passing it to setContentPane( ) . Using this strategy, you could rewrite the previous example as follows:

//file: MangoMango2.java
import java.awt.*;
import javax.swing.*;

public class MangoMango2 {
  public static void main(String[] args) {
    JFrame f = new JFrame("The Frame");
    f.setLocation(100, 100);
    
    Container content = new JPanel( );
    content.add(new JLabel("Mango"));
    content.add(new JButton("Mango"));
    f.setContentPane(content);
    
    f.pack( );
    f.setVisible(true);
  }
}

We’ll cover labels and buttons in Chapter 14 and layouts in Chapter 16. The important thing to remember is that you can’t add components directly to a JFrame or JWindow. Instead, add them to the automatically created content pane, or create an entirely new content pane.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.