Concepts / Java Window Basics with JFrame

Java Window Basics with JFrame

The pack() method resizes a window to match the preferred size of its contents, and it works correctly only when every component reports a correct preferred size.

  • Programming

Two Questions Every Window Must Answer

When a program opens a window, two questions come up almost immediately: how big should the window be, and where should it appear on the screen? One answer is to size the window by hand with a fixed number of pixels. That approach breaks down as soon as the contents change, because a fixed size that suits one set of components may cut off or waste space for another. This article looks at two techniques that answer both questions from inside the window's own constructor. The first is the pack() method, which sizes the window to fit what it contains. The second is a short calculation that places the window in the exact middle of the screen.

Both techniques live in the constructor, the part of a window class that runs when the window object is created. The best way to see what each line contributes is to follow the constructor step by step, so the rest of this article is organized around the order in which those lines run.

Following the Constructor Line by Line

The constructor starts by passing a title to the superclass, JFrame, and then builds the contents of the window. Those setup lines come first because the window's size depends on what they add. The next line that changes the window's dimensions is the call to pack(). Once that call has run, the window has a size. The last two lines set the window's position so that it is centered on the screen. Note that the constructor does not make the window appear; that happens in a separate step covered later.

As you read the code below, ask one question at each line: does this statement decide the size, the position, or the visibility of the window? Each of those three properties is set by a different part of the code. The example is an original sketch written to show the order described in the source, not a listing from the source itself.

java
Notes on the code
  • super("Demo Window") passes the title to JFrame before any other setup.
  • setPreferredSize gives the canvas panel a size, because a plain drawing panel has no automatic preferred size.
  • pack() changes the window's size to match its contents.
  • setLocation() positions the window's upper left corner so the window ends up centered.
  • setVisible(true) is the separate step that makes the window appear.

What do you think happens?

Right after the constructor finishes, is the window on the screen?

  • Yes, it is visible with the correct size and position

  • No, it has a size and position but is not yet visible

Reveal answer
Answer:

No, it has a size and position but is not yet visible

The source states that the constructor creates the window object and sets its size and position, but the visual representation has not yet been made visible. A separate call to setVisible(true) is required.

How pack() Turns Preferred Sizes into a Window Size

The statement window.pack() resizes the window so that its size matches the preferred size of its contents. The source describes this as usually the best way to set a window's size. Instead of choosing a width and height in advance, you let the components inside the window report how big they want to be, and pack() gathers those preferred sizes into the window's overall size.

Window (JFrame)final size set by pack()Content panelholds the componentsJLabelreports a preferred sizeJButtonreports a preferred sizeDrawing panelneeds setPreferredSizecontainsreportsreportsreports
How do the preferred sizes of the leaf components combine up through a nested panel so that pack() produces the final window size?

Read the tree from the leaves upward. Each leaf component offers a preferred size, the panel that holds them takes those sizes into account, and the window as a whole takes its size from its contents. The diagram is an illustration of the logic in the source rather than a literal reproduction of it. The essential point is that pack() does not guess a size. It asks the contents for their preferred sizes and adopts the result.

When Preferred Sizes Must Be Set by Hand

The source states that pack() works correctly only when every component in the window has a correct preferred size, and it identifies two situations in which this is not automatic. The first is a panel used as a drawing surface, such as a panel where your code paints shapes or images. The second is a panel used as a container with a null layout manager. In both cases the system has no way to determine the correct preferred size on its own, so the programmer has to set it.

JLabelcontent supplies thepreferred sizeDrawing panelno automatic preferred sizeJButtoncontent supplies thepreferred sizeNull-layout panelno automatic preferred size
How does a component with no intrinsic content, such as an empty custom-painted panel, differ from a JLabel or JButton in the size pack() produces?

The source gives one example of setting the size by hand: calling setPreferredSize on the panel with a Dimension of 400 by 250. Once the panel reports that size, pack() can take it into account when sizing the window. In the code shown earlier, the canvas panel receives exactly this treatment before pack() is called, which is why the window is sized from that panel rather than collapsing to nothing.

java
Notes on the code
  • The panel is created with no content of its own, so it has no automatic preferred size.
  • setPreferredSize supplies the 400 by 250 size that pack() will read.
  • pack() must be called after the size is set, so the window takes that size into account.

Tracing the Window Through Its Three States

After the window has its size, the constructor places it on the screen. At the end of the constructor, the window has been created, given a size, and given a position. Even so, it is not yet visible. The source makes this distinction precise: the constructor creates the window object, but the visual representation of that object has not yet been created. Making the window visible is a separate step, performed by calling setVisible(true) on the window.

Window object createdconstructor has startedSized by pack()size matches contentsPositioned bysetLocation()upper left corner placedHiddennot yet visibleVisibleshown on screenpack()setLocation()constructor endssetVisible(true)
What state is the window in after the constructor and pack() run, and what changes when setVisible(true) is called?

Trace the diagram from left to right. The window object comes into existence, pack() gives it a size, and setLocation() gives it a position. At that point it is in the Hidden state. Only the call to setVisible(true) moves it to the Visible state. Because setVisible(true) is usually called from outside the constructor, as in the main method of the example, the window can be fully prepared before the user ever sees it.

Computing the Center Position

Centering requires two numbers: the size of the screen and the size of the window. The screen size can be obtained through the default toolkit. The window's position is then set with setLocation(). The source explains that setLocation() sets the position of the upper left corner of the frame. The coordinates you pass describe where that corner should go, not where the center should go. This is why the calculation has to subtract the window's size before placing the corner.

Rule

In words: subtract the window's width and height from the screen's width and height, halve each result, and pass those two values to setLocation(). The subtraction gives the leftover space on each axis, and halving splits that leftover space equally on both sides of the window.

Worked example

Centering a 400 by 250 Window on a 1920 by 1080 Screen

A screen is 1920 pixels wide and 1080 pixels high. The window's pack() result is 400 pixels wide and 250 pixels high. Find the setLocation() coordinates that center the window.

  1. 1
    Find the leftover width

    1920 minus 400 gives 1520 pixels of space left over horizontally.

  2. 2
    Halve the leftover width

    1520 divided by 2 gives 760. This is the x coordinate of the upper left corner.

  3. 3
    Find the leftover height

    1080 minus 250 gives 830 pixels of space left over vertically.

  4. 4
    Halve the leftover height

    830 divided by 2 gives 415. This is the y coordinate of the upper left corner.

  5. 5
    Call setLocation

    The call is setLocation(760, 415). The window's corner lands at (760, 415), and its far edges sit an equal distance from the screen edges.

Result

setLocation(760, 415)

Mistakes Beginners Make with pack() and Position

  • Common mistake

    Calling pack() on a drawing panel without setting its preferred size

    What it looks like

    A custom-painted panel is added to the window and pack() is called, but the panel never had setPreferredSize called on it.

    Why it fails

    A drawing panel has no automatic preferred size, so pack() has nothing meaningful to size the window from.

    Do this instead

    Call setPreferredSize with the intended dimensions, for example a Dimension of 400 by 250, before calling pack().

  • Common mistake

    Calling setLocation() with the window's center coordinates

    What it looks like

    Passing the screen's center point directly to setLocation() and expecting the window to be centered.

    Why it fails

    setLocation() positions the upper left corner of the frame, so the window would extend down and to the right of that point.

    Do this instead

    Subtract the window's width and height from the screen's, halve each result, and pass those values to setLocation().

  • Common mistake

    Expecting the window to appear as soon as the constructor runs

    What it looks like

    Creating a window in the constructor and assuming the user can see it without any further call.

    Why it fails

    The constructor creates the window object, but the window is not visible until setVisible(true) is called on it.

    Do this instead

    Call setVisible(true) on the window after it has been constructed and prepared.

  • Common mistake

    Calling pack() before the components have been added, so the window is sized with nothing inside it

Practice: Predicting a Window's Size

MEDIUM

A constructor adds a drawing panel to a JFrame. The drawing panel has setPreferredSize called with a Dimension of 400 by 250. Then pack() is called, and afterward setLocation is used to center the window on a 1920 by 1080 screen, assuming the window ends up 400 by 250 pixels. Work out the setLocation() coordinates, and then say whether the window is visible when the constructor finishes.

Hints
  • Use the centering formula: subtract the window's size from the screen's size and halve each result.
  • Remember that setLocation() positions the upper left corner, and that visibility is a separate step controlled by setVisible(true).

Key Takeaways

  • pack() resizes a window to match the preferred size of its contents, and it works correctly only when every component reports a correct preferred size.
  • Drawing panels and panels with null layout managers have no automatic preferred size, so their size must be set by hand with setPreferredSize.
  • To center a window, subtract its width and height from the screen's, halve each result, and pass those values to setLocation(), which positions the upper left corner.
  • A window created by the constructor is not visible until setVisible(true) is called on it.