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.