无法呈现的JFrame包错误

每当窗口看不到可resize时, JFramepack()方法就不起作用。 请亲自尝试(可能需要重试一次)请:

 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); } } }); } } 

(您可以通过在使框架无法调整后直接显示框架来修复它。)

为什么会这样?

作为参考,这是Mac OS X上的变体外观。注意,

  • 一个像素的差异表明空间可能用于焦点指示器; 但是正如@ Marco13 评论的那样 , 偶尔的外观暗示了一个(非显而易见的)线程问题。

  • 由于外观似乎与平台有关,因此标签显示操作系统和版本系统属性。

  • 该示例重写getPreferredSize()以建立封闭面板的几何,如此处所示 。

  • 如果实现通过填充每个像素来表达opacity属性 ,则对super.paintComponent()的调用不是必需的。

  • @ AyCe的Windows屏幕截图中显示的不规则位置包含@ Marco13建议的线程问题。

Mac OS X: 图像Mac OS X.

Windows 7(@AyCe): 图像Windows

Ubuntu 14: 图像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); } } }); } } 

编辑:请参阅下面的更新

对不起,这不是(还是?)最终的解决方案,但我仍在调查这个并希望分享第一个见解,也许它对其他人也有帮助,以便进一步分析:

奇怪的行为是由帧的getInsets()方法引起的。 覆盖方法时,可以看到它在大多数情况下返回leftrightbottom3插图,但是当出现错误时它们为4

 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); } } }); } } 

我已经扫描了底层实现,最终委托给WWindowPeer和一些基于窗口样式进行奇怪计算的本机方法(也就是说,取决于窗口是否可resize),我已经确定了一些候选者错误的原因,但仍然没有最终结论。 (这似乎是随机发生的事实并不能使调试变得更容易,当然......)


旁注:简单地调用pack()方法两次会导致帧总是正常显示给我,但当然,这最多只能被认为是一个hacky解决方法,只要奇怪行为的根本原因不是鉴定。


更新

我深入挖了一层,但仍未找到最终解决方案。

sun.awt.windows.WFramePeer类的初始化方法实现如下:

 void initialize() { super.initialize(); Frame target = (Frame)this.target; if (target.getTitle() != null) { setTitle(target.getTitle()); } setResizable(target.isResizable()); setState(target.getExtendedState()); } 

super调用进入sun.awt.windows.WWindowPeer ,并执行以下操作:

 void initialize() { super.initialize(); updateInsets(insets_); ... } 

updateInsets调用以本机方法结束。 updateInsets本机实现执行一些可疑调用,查询窗口的“样式”,并根据WS_THICKFRAME属性调整insets,例如

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

我可以肯定地说,这些updateInsets调用的结果是错误的 。 它们对于不可见的帧而言太大,最终导致错误。 我不能肯定的是:哪里(有时)更新错误的插图(对于某些帧)以正确反映不可调整帧的大小。


一个潜在的修复?

正如在第一个更新的片段中可以看到的那样, WFramePeer的初始化最终会调用

  setResizable(target.isResizable()); 

setResizable最终再次委托给本机方法,并且在setResizable方法的本机实现中 ,再次存在此styleWS_THICKFRAME

所以改变sun.awt.windows.WFramePeer#initialize()方法首先设置“resizable”属性,然后向上传递调用(它将导致调用updateInsets )解决了我的问题:

 void initialize() { Frame target = (Frame)this.target; setResizable(target.isResizable()); super.initialize(); if (target.getTitle() != null) { setTitle(target.getTitle()); } setState(target.getExtendedState()); } 

通过此修改,可以正确计算插入,并始终正确显示帧。

我不确定这是否正确但您可以尝试在pack方法之后放置setResizable()方法,因为setResizeable(false)可能会禁用pack函数。