无法呈现的JFrame包错误
每当窗口看不到可resize时, JFrame
的pack()
方法就不起作用。 请亲自尝试(可能需要重试一次)请:
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:
Windows 7(@AyCe):
Ubuntu 14:
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()
方法引起的。 覆盖方法时,可以看到它在大多数情况下返回left
, right
和bottom
为3
插图,但是当出现错误时它们为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
方法的本机实现中 ,再次存在此style
和WS_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函数。