AyCe
AyCe

Reputation: 766

Unresizable JFrame pack error

JFrame's pack() method won't work every time when the window is not resizable it seems. Try it for yourself (it may need a few retries) please:

import javax.swing.*;
import java.awt.*;

public class FramePackBug {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                for (int i = 0; i < 20; i++) {
                    JFrame window = new JFrame("Buggy");
                    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    window.setResizable(false);
                    window.setBackground(Color.RED);
                    JPanel jp = new JPanel() {
                        public void paintComponent(Graphics g) {
                            g.setColor(Color.GREEN);
                            g.fillRect(0, 0, 200, 100);
                        }
                    };
                    jp.setPreferredSize(new Dimension(200, 100));
                    window.add(jp);
                    window.pack();
                    window.setLocation((i % 5) * 250, (i / 5) * 150);
                    window.setVisible(true);
                }
            }
        });
    }
}

(You can fix it by making the frame visible directly after making it unresizable.)

Why is that so?

Upvotes: 4

Views: 614

Answers (3)

Marco13
Marco13

Reputation: 54669

EDIT: See below for updates

Sorry, this is not (yet?) the final solution, but I'm still investigating this and wanted to share the first insight, maybe it's helpful for others as well, for a further analysis:

The odd behavior is caused by the getInsets() method of the frame. When overriding the method, one can see that it returns insets where left, right and bottom are 3 in most cases, but they are 4 when the error appears:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Insets;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class FramePackBug {
    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                for (int i = 0; i < 20; i++) {
                    final int ii = i;
                    JFrame window = new JFrame("Buggy")
                    {
                        @Override
                        public Insets getInsets()
                        {
                            Insets insets = super.getInsets();
                            if (insets.left == 4)
                            {
                                System.out.printf(
                                    "Wrong insets in %d : %s\n", ii, insets);
                            }
                            return insets;
                        }
                    };
                    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    window.setResizable(false);
                    window.setBackground(Color.RED);
                    JPanel jp = new JPanel() {
                        public void paintComponent(Graphics g) {
                            g.setColor(Color.GREEN);
                            g.fillRect(0, 0, 200, 100);
                        }
                    };
                    jp.setPreferredSize(new Dimension(200, 100));
                    window.add(jp);
                    window.pack();
                    window.setLocation((i % 5) * 250, (i / 5) * 150);
                    window.setVisible(true);
                }
            }
        });
    }
}

I've already scanned the underlying implementation, which eventually delegates to the WWindowPeer and some native methods that do some odd computations based on the window style (that is, depending on whether the window is resizable or not), and I have identified some candidate reasons for the error, but still no final conclusion. (The fact that this seems to happen randomly does not make debugging easier, of course...)


A side note: Simply calling the pack() method twice caused the frames to always appear properly for me, but of course, this could at best only be considered as a hacky workaround, as long as the root cause for the odd behavior is not identified.


Update

I dug one level deeper, but still did not find a final solution.

The initialization method of the sun.awt.windows.WFramePeer class is implemented as follows:

void initialize() {
    super.initialize();

    Frame target = (Frame)this.target;

    if (target.getTitle() != null) {
        setTitle(target.getTitle());
    }
    setResizable(target.isResizable());
    setState(target.getExtendedState());
}

The super call goes into sun.awt.windows.WWindowPeer, and there it does the following:

void initialize() {
    super.initialize();

    updateInsets(insets_);
    ...
}

The updateInsets call ends at a native method. The native implementation of updateInsets does some suspicious calls, querying the "style" of the window, and adjusting the insets based on a WS_THICKFRAME property, e.g.

if (style & WS_THICKFRAME) {
    m_insets.left = m_insets.right =
        ::GetSystemMetrics(SM_CXSIZEFRAME);

What I can say for sure is that the result of these updateInsets calls is wrong. They are too large for the unresizable frames, which eventually causes the errors. What I can not say for sure is: Where the wrong insets are (sometimes) updated (for some frames) to properly reflect the size for the unresizable frame.


A potential fix?

As it can be seen in the first of the updated snippets, the initialization of the WFramePeer eventually calls

    setResizable(target.isResizable());

The setResizable once more delegates to a native method eventually, and in the native implementation of the setResizable method, there is this style and the WS_THICKFRAME again.

So changing the sun.awt.windows.WFramePeer#initialize() method to first set the "resizable" property, and then passing the call upwards (where it will cause updateInsets to be called) resolved the issue for me:

void initialize() {

    Frame target = (Frame)this.target;
    setResizable(target.isResizable());
    super.initialize();

    if (target.getTitle() != null) {
        setTitle(target.getTitle());
    }
    setState(target.getExtendedState());
}

With this modification, the insets are properly computed, and the frames are always displayed correctly.

Upvotes: 1

trashgod
trashgod

Reputation: 205865

For reference, here's the appearance of a variation on Mac OS X. Note,

  • A one-pixel disparity suggests the possibility of space intended for a focus indicator; but as @Marco13 comments, the occasional appearance suggests a (non-obvious) threading issue.

  • As the appearance seems platform dependent, a label displays the OS and version system properties.

  • The example overrides getPreferredSize() to establish the enclosed panel's geometry, as suggested here.

  • The call to super.paintComponent() is not strictly necessary if the implementation honors the opacity property by filling every pixel.

  • The irregular locations seen in @AyCe's Windows screenshot are consisted with a threading issue suggested by @Marco13.

Mac OS X: image Mac OS X

Windows 7 (@AyCe): image Windows

Ubuntu 14: image Ubuntu

import javax.swing.*;
import java.awt.*;

public class FramePackBug {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    JFrame window = new JFrame("Buggy");
                    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    window.setResizable(false);
                    window.setBackground(Color.RED);
                    JPanel jp = new JPanel() {
                        @Override
                        public void paintComponent(Graphics g) {
                            super.paintComponent(g);
                            g.setColor(Color.GREEN);
                            g.fillRect(0, 0, getWidth(), getHeight());
                        }

                        @Override
                        public Dimension getPreferredSize() {
                            return new Dimension(200, 100);
                        }
                    };
                    window.add(jp);
                    window.add(new JLabel(System.getProperty("os.name") + ", "
                        + System.getProperty("os.version")), BorderLayout.NORTH);
                    window.pack();
                    window.setLocation((i % 5) * (window.getWidth() + 5),
                        (i / 5) * (window.getHeight() + 5) + 20);
                    window.setVisible(true);
                }
            }
        });
    }
}

Upvotes: 3

user3677963
user3677963

Reputation: 34

I am not sure if this is right but you could try putting the setResizable() method after the pack method since the setResizeable(false) might be disabling the pack function.

Upvotes: 0

Related Questions