【发布时间】:2012-06-06 04:42:16
【问题描述】:
问题
我们的独立 Swing 应用程序在某些特定事件(按钮单击等)处显示 模态 JDialog
该对话框包含一些其他 Swing 组件(JLabels、JButtons、...)。而不是通过JDialog.setBounds(...) 方法显式设置其维度,我们调用JDialog.pack()(或JOptionPane 的showDialog(...) 方法隐式调用它)。通过pack() 方法计算的尺寸始终是恒定的(例如 300x100 像素。)不幸的是,有时可见对话框的实际尺寸是 1x1 像素(JDialog.getSize().equals(new Dimension(1, 1)))。
JDialog 初始化
我们在应用程序中初始化JDialogs 有两种方式。
我们检查了这两个初始化方法总是从 EventDispatchThread 调用。
第一种方法
我们只是创建一个ADialog 的实例,它是JDialog 的子类。这是我们初始化过程的 sn-p:
ADialog dialog = new ADialog();
dialog.setContentPane(content);
dialog.setVisible(true);
这是我们的ADialog 实现:
public class ADialog extends JDialog implements ComponentListener {
public JfosDialog(Frame owner) {
super(owner);
init();
}
private void init() {
super.addComponentListener(this);
}
@Override
public void componentShown(ComponentEvent e) {
// Calling pack() at this place is really weird, but we
// have to do it since some subclasses put their
// content to dialog in overriden componentShown().
pack();
}
@Override
public void componentMoved(ComponentEvent e) { /** not interested */ }
@Override
public void componentResized(ComponentEvent e) { /** not interested */ }
}
第二种方法
public JOptionPane showDialog(...) {
JOptionPane jop = new JOptionPane(message, msgType, option, null, textMessages);
JDialog dialog = jop.createDialog(owner, titleMsg);
setDialogTraversal(dialog);
dialog.setVisible(true);
dialog.dispose();
return jop;
}
重现性
环境
- Ubuntu 12.04., JRE 1.6 / JRE 1.7 / OpenJDK 1.6
- Windows XP、JRE 1.6.u16
用第一种初始化方法重现问题
我们只显示然后隐藏对话框 N 次 (0 java.awt.Robot 脚本来显示和隐藏循环中的对话框。这比手动操作更舒服。
用第二种初始化方法重现问题
步骤与第一种方法相同:只显示然后隐藏对话框N次。不幸的是,我们无法在我们的开发环境中重现它,但它可以很容易地在生产 PC 中重现(CPU 与我们的开发环境不同,安装了一些防病毒软件会永久造成系统负载等)
到目前为止,我们无法在某种测试/示例项目中重现该问题。这可能表明我们的应用程序有问题。但是,似乎问题可能出在 Swing 的本机代码中(请参阅跟踪部分)。
追踪
我们在 64 位 OpenJDK 1.6.0_24 中追踪了问题的原因。我们发现JDialog 的维度是由XToolkit 触发的XConfigureEvent 修改的。该事件是在从本地方法调用返回后在XToolkit.run(boolean) 方法的事件循环中构造的。
为了简化事情,我在这里只发布了一个代码 sn-p,说明了 XToolkit 的事件循环机制以及我们的跟踪结果。您可能还会看到full source code here。
public class XToolkit ... {
...
public void run(boolean loop) {
XEvent ev = new XEvent();
while(true) {
awtLock();
try {
if (loop == SECONDARY_LOOP) {
...
} else {
...
// ===========================================
// The following invocation of native method sometimes
// updates ev object in a way that ev.get_type() method returns value 22
// indicating that the event's type is XConfigureEvent.
// In such case, as I mentioned in text above, the value of
// ev.get_xconfigure().get_width() / .get_height()
// is sometimes 1.
XlibWrapper.XNextEvent(getDisplay(),ev.pData); // <-----
// ===========================================
}
...
// The XConfigureEvent with get_width() == 1 and
// get_heigth() == 1 is dispatcher here:
dispatchEvent(ev); // <-----
...
} catch (...) {
...
}
}
}
...
}
您有任何想法如何修复此错误/更深入地跟踪它/更健壮地重现它...?
我很欣赏任何想法,因为这个错误真的很痛苦。
【问题讨论】:
-
正如 (@trashgod) 所提到的,您可以做任何事情,以任何方式准备您的 GUI,但是 setVisible 必须在 invokeLater 中,其余方法对于 EDT 和屏幕可见性并不重要,特别是如果 JDialog 和 JOptionPane 从 EDT 中调用,您只能在屏幕上看到工具栏,RootPane 不可见
-
@mKorbel 的 Q&A 引用了两种检测 EDT 违规的方法。
-
我们检查了所有的初始化方法并且在 EDT 中调用了
setVisible(true)。我还修改了我的帖子并添加了我们的初始化代码。 -
你有在
componentShown方法中将其内容添加到对话框的子类的例子吗?