問題
スタンドアロンの Swing アプリケーションは、いくつかの特定のイベント (ボタンのクリックなど) でモーダル JDialogを表示します。ダイアログには、他の Swing コンポーネント (JLabels、JButtons、...) が含まれています。JDialog.setBounds(...)
呼び出すメソッドを介してその次元を明示的に設定するのではなくJDialog.pack()
(またはJOptionPane
のshowDialog(...)
メソッドが暗黙的に呼び出します)。メソッドによって計算されるサイズpack()
は常に一定です (例: 300x100 px)。残念ながら、表示されるダイアログの実際のサイズが 1x1 px ( JDialog.getSize().equals(new Dimension(1, 1))
) になる場合があります。
JDialog の初期化
アプリケーションで sを初期化する方法は 2 つありJDialog
ます。両方の初期化メソッドが常に EventDispatchThread から呼び出されることを確認しました。
最初の方法
ADialog
のサブクラスであるインスタンスを作成するだけですJDialog
。以下は、初期化手順の抜粋です。
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 */ }
}
2番目の方法
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 < N < 1000) 行い、かつてダイアログのサイズが 1x1 (スレッドの問題、競合状態の兆候) でした。この問題の性質は非常に確率論的であるため、ループ内でダイアログを表示および非表示にする単純なjava.awt.Robotスクリプトを作成しました。手動で行うよりも快適です。
2 番目の初期化方法で問題を再現する
手順は最初の方法と同じです。ダイアログを表示してから N 回非表示にします。残念ながら、開発環境で再現することはできませんが、実稼働 PC では非常に簡単に再現できます (開発環境とは異なる CPU があり、システム負荷を永続的に作成するウイルス対策がインストールされているなど)。
これまでのところ、ある種のテスト/サンプル プロジェクトで問題を再現することはできません。これは、アプリケーションに問題があることを示唆している可能性があります。ただし、問題は Swing のネイティブ コードのどこかにあるようです (「トレース」セクションを参照)。
トレース
64 ビット OpenJDK 1.6.0_24 での問題の原因を追跡しました。JDialog
の次元がによってXConfigureEvent
起動されていることがわかりましたXToolkit
。XToolkit.run(boolean)
イベントは、ネイティブ メソッド呼び出しから returnig 後のメソッドのイベント ループで構築されます。
XToolkit
簡単にするために、トレースの結果を使用して のイベント ループ メカニズムを示すコード スニペットのみをここに投稿します。また、完全なソース コードはこちらでご覧いただけます。
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 (...) {
...
}
}
}
...
}
このバグを修正する/より深く追跡する/より確実に再現する方法について何かアイデアはありますか?
このバグは本当に苦痛なので、アイデアをいただければ幸いです。