4

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?

AyCe
  • 727
  • 2
  • 11
  • 30
  • Have you tried to pack before setting the frame not resizable? – mins Mar 22 '15 at 00:47
  • @mins: Yes. In that case, all windows are wrong. – AyCe Mar 22 '15 at 00:47
  • 1+, this is weird. When I test (using JDK7 on Windows 7) the first frame is always correct, but after that its mostly wrong. I only get 3-4 windows at the proper size. I even changed your code to use a Timer with a delay of 500ms to create each window. I also changed the code to use a JPanel without overriding the paintComponent() method. – camickr Mar 22 '15 at 01:45
  • It's a common source of confusion that a non-resizable frame has a border that is 1 pixel thinner than that of a resizable frame (with default LaF, on Windows, at least), and people call `setResizable(false)` improperly and wonder why their content area is always off by 2 pixels. But in this case, the sequence of calls is clear and correct. The fact that *sometimes* *some* windows are wrong would usually indicate some threading issue, but this can also not be the case here. This is indeed weird, looking forward to see the answer. – Marco13 Mar 22 '15 at 02:08
  • @camickr Yes, that is what I've observed as well. The code was much bigger originally, but I reduced it a lot, to discover that different calls to invokeLater, a delay, etc. don't do anything. – AyCe Mar 22 '15 at 02:21
  • Just noticed that it seems to work fine when using a JDialog. Maybe this is another reason to use this advice: [The Use of Multiple JFrames, Good/Bad Practice?](http://stackoverflow.com/questions/9554636/the-use-of-multiple-jframes-good-bad-practice) :) – camickr Mar 22 '15 at 02:35
  • `super.paintComponent()`? – trashgod Mar 22 '15 at 02:48
  • @trashgod It causes the borders to be white instead of red. So I did not call it (has no effect on the problem). – AyCe Mar 22 '15 at 02:58

3 Answers3

3

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);
                }
            }
        });
    }
}
Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • With your code I get [this](http://i.imgur.com/wuK7FqP.jpg). Also, note how the red border cannot be seen this way, because the panel always fills the entire space (but is still bigger than specified). – AyCe Mar 22 '15 at 06:26
  • For reference, I added your screenshot and one from Ubuntu; sorry, I can't explain the effect seen on Windows. – trashgod Mar 22 '15 at 10:49
1

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.

Marco13
  • 53,703
  • 9
  • 80
  • 159
  • Still better than what I did, calling pack(); and setVisible(true); again after 1 second delay. At least we can fix it less-hacky by calling setVisible(true) after setResizable(false) but before pack(). – AyCe Mar 22 '15 at 15:48
  • Thank you for weighting-in on this. I've seen the `pack()`-twice work-around suggested before, but don't have a reference. I wonder if the anomalous positioning may be related to the effect seen [here](http://stackoverflow.com/q/23507422/230513). – trashgod Mar 23 '15 at 00:53
0

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.

  • This has already been proposed in the first comment, but actually doing this will "disable" the pack method. – AyCe Mar 22 '15 at 02:54