印刷ビューの終了

Java SE 7 デスクトップテクノロジのトラブルシューティングガイド

印刷ビュー

ドキュメント情報

はじめに

1. はじめに

2. AWT

3. Java 2D

4. Swing

4.1 Swing の一般的なデバッグヒント

4.2 Swing の具体的なデバッグヒント

4.2.1 不正なスレッド処理

4.2.2 JComponent の子同士のオーバーラップ

4.2.3 表示の更新

4.2.4 モデルの変更

4.2.5 コンポーネントの追加または削除

4.2.6 不透明のオーバーライド

4.2.7 Graphics に対する永続的な変更

4.2.8 カスタムペイントとダブルバッファリング

4.2.9 不透明なコンテンツペイン

4.2.10 パフォーマンス:セルごとのレンダラの呼び出し

4.2.11 発生する可能性のあるリーク

4.2.12 重量コンポーネントと軽量コンポーネントの混在

4.2.13 Synth を使用するためのヒント

4.2.14 イベントディスパッチスレッド上のアクティビティーの追跡

4.2.15 さまざまなデフォルトレイアウトマネージャー

4.2.16 リスナーオブジェクトはもっとも深いコンポーネントにディスパッチされる

4.2.17 コンテンツペインへのコンポーネントの追加

4.2.18 Swing でのドラッグ&ドロップサポート

4.2.19 コンポーネントの親は一度に 1 つだけ

4.2.20 JFileChooser と Windows のショートカットの問題

5. 国際化

6. Java Sound

7. アプレットと Java Web Start アプリケーション

8. バグレポートの送信

A. Java 2D のプロパティー

B. 致命的エラーログ

第 4 章

Swing

この章では、Java SE Swing API で発生する可能性のあるもっとも一般的な問題のいくつかをトラブルシューティングするための、具体的な手順に関する情報と指針を提供します。

4.1 Swing の一般的なデバッグヒント

Java SE 6 では Swing のペイントインフラストラクチャーが大幅に変更されました。Java SE 6 以降のリリースに固有のペイントアーティファクトに気づいた場合は、新機能をオフにしてみることができます。これはプロパティー swing.bufferPerWindow で行えます。

なにかしらのメニューがポップアップされた状態で実行される Swing コードのデバッグを行う場合、デバッガをリモートで使用することをお勧めします。それ以外の場合、デバッグプロセスとアプリケーション実行とが互いにブロックし合い、システムでの後続の作業が行えなくなります。それが発生した場合に取れる唯一のアクションは、Linux および Solaris の X サーバーを終了させることです。詳細については、バグデータベース内の次のバグを参照してください。

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6517045

Swing のいくつかの一般的な問題:

いくつかの考慮すべき事項:

不正なレンダラの特定:

4.2 Swing の具体的なデバッグヒント

次の各サブセクションでは、Swing の問題をトラブルシューティングするためのいくつかのヒントを示します。

4.2.1 不正なスレッド処理

例外やペイント問題がランダムに発生するのは通常、Swing の不正なスレッド使用の結果です。Swing コンポーネントへのすべてのアクセスは、Javadoc で特に明記されていないかぎり、イベントディスパッチスレッド上で行われる必要があります。これには、Swing コンポーネントに接続されるすべてのモデル (TableModelListModel など) も含まれます。

Swing の不正な使用をチェックする最良の方法は、次のコードで示される計測 RepaintManager を使用することです。

public class CheckThreadViolationRepaintManager extends RepaintManager {
     // it is recommended to pass the complete check
     private boolean completeCheck = true;

     public boolean isCompleteCheck() {
         return completeCheck;
     }

     public void setCompleteCheck(boolean completeCheck) {
         this.completeCheck = completeCheck;
     }

     public synchronized void addInvalidComponent(JComponent component) {
         checkThreadViolations(component);
         super.addInvalidComponent(component);
     }

     public void addDirtyRegion(JComponent component, int x, int y, int w, int 
h) {
         checkThreadViolations(component);
         super.addDirtyRegion(component, x, y, w, h);
     }

     private void checkThreadViolations(JComponent c) {
         if (!SwingUtilities.isEventDispatchThread() && (completeCheck || 
c.isShowing())) {
             Exception exception = new Exception();
             boolean repaint = false;
             boolean fromSwing = false;
             StackTraceElement[] stackTrace = exception.getStackTrace();
             for (StackTraceElement st : stackTrace) {
                 if (repaint && st.getClassName().startsWith("javax.swing.")) {
                     fromSwing = true;
                 }
                 if ("repaint".equals(st.getMethodName())) {
                     repaint = true;
                 }
             }
             if (repaint && !fromSwing) {
                 //no problems here, since repaint() is thread safe
                 return;
             }
             exception.printStackTrace();
         }
     }
}

4.2.2 JComponent の子同士のオーバーラップ

JComponent の子同士のオーバーラップを許可した場合にも、ペイント問題が発生する可能性があります。この場合、親が isOptimizedDrawingEnabled をオーバーライドして false を返すようにする必要があります。isOptimizedDrawingEnabled をオーバーライドしなかった場合、どのコンポーネントで再ペイントが呼び出されたかに応じて、コンポーネントがほかのコンポーネントの上にランダムに表示される可能性があります。

4.2.3 表示の更新

表示を更新する必要があるときに再ペイントを正しく呼び出さなかった場合にも、ペイント問題が発生する可能性があります。Swing コンポーネントの可視プロパティー (フォントなど) を変更すると、再ペイントまたは再有効化がトリガーされます。カスタムコンポーネントを記述する場合には、表示やサイズ設定の情報が更新されるたびに再ペイント (とおそらく再有効化) を呼び出す必要があります。そうしないと、再ペイントが次回どこかでトリガーされるまで表示が更新されません。

これを診断する良い方法は、ウィンドウのサイズを変更することです。サイズ変更後にコンテンツが表示された場合、コンポーネントが再ペイントや再有効化を正しく呼び出さなかったことを意味しています。

4.2.4 モデルの変更

Swing コンポーネントの可視プロパティーの変更時に再ペイントを呼び出す必要がないのと同じく、モデルの変更時にも再ペイントを呼び出す必要はありません。モデルが正しい変更通知を送出すると、JComponent が必要に応じて再ペイントや再有効化を呼び出します。

ただし、モデルを変更したのに通知を送出しなければ、再ペイントイベントが動作すらしない可能性があります。特に、これは JTree では動作しません。行うべき正しいことは、適切なモデル通知を送出することです。その診断は通常、やはりウィンドウのサイズを変更し、表示が正しく更新されなかったことを確認することで行えます。

4.2.5 コンポーネントの追加または削除

コンポーネントを追加または削除した場合は、再ペイントまたは再有効化を呼び出す必要があります。Swing と AWT はこうした状況では再ペイントや再有効化を呼び出さないため、ユーザー自身がそれらを呼び出す必要があります。

4.2.6 不透明のオーバーライド

ペイント問題の別の潜在的な領域は、コンポーネントが不透明をオーバーライドしない場合です。これについて警告するドキュメントを、次に示します。

さらに、super の実装を呼び出さない場合は、不透明プロパティーを尊重する必要があります (このコンポーネントが不透明な場合は、不透明でない色のバックグラウンドを完全に塗りつぶす必要があります)。不透明プロパティーを尊重しない場合は、視覚的なアーティファクトが見える場合があります。

これをチェックする唯一の方法は、コンポーネントが再ペイントを呼び出す際に一貫性のある視覚的なアーティファクトを探すことです。

4.2.7 Graphics に対する永続的な変更

paintpaintComponent、または paintChildren に渡される Graphics に対する永続的な変更は、一切行わないでください。これについて警告するドキュメントを、次に示します。

これをサブクラスでオーバーライドする場合は、渡された Graphics に永続的な変更を行わないようにしてください。たとえば、クリップ Rectangle を変更したり、変換を変更したりすべきではありません。このような操作が必要な場合は、渡された Graphics から新しい Graphics を作成し、それを操作するほうが容易でしょう。

この制限を尊重しない場合は、クリッピングなどの奇妙な視覚的アーティファクトが発生します。

4.2.8 カスタムペイントとダブルバッファリング

paint をオーバーライドし、そのオーバーライド内でカスタムペイントを行うことも可能ですが、代わりに paintComponent をオーバーライドすべきです。JComponent.paint メソッドは、ペイントがダブルバッファーに対して発生することを保証します。paint を直接オーバーライドすると、ダブルバッファリングが失われる可能性があります。

4.2.9 不透明なコンテンツペイン

Swing のペイントアーキテクチャーでは不透明なコンテンツペインが必要です。次にドキュメントを示します。

Swing のペイントアーキテクチャーでは、不透明な JComponent が包含関係の階層の中でほかのすべてのコンポーネントの上に存在する必要があります。通常、これはコンテンツペインによって提供されます。コンテンツペインを置き換える場合は、setOpaque(true) によってコンテンツペインを不透明にすることをお勧めします。また、コンテンツペインによって paintComponent がオーバーライドされる場合は、paintComponent でバックグラウンドを不透明な色で完全に塗りつぶす必要があります。

4.2.10 パフォーマンス:セルごとのレンダラの呼び出し

レンダラはセルごとにペイントされるので、レンダラの処理は極力少なくしてください。レンダラ内での速度低下はすべて、すべてのセルにわたって拡大されます。たとえば、50x20 の可視セルから成るテーブルの可視領域を再ペイントすると、レンダラが 1000 回呼び出されます。

4.2.11 発生する可能性のあるリーク

モデルのライフサイクルが、モデルを使用するコンポーネントを含むウィンドウのライフサイクルよりも長い場合、Swing コンポーネントのモデルを明示的に null に設定する必要があります。モデルを null に設定しなかった場合、モデルが Component への参照を保持しているため、ウィンドウ内のコンポーネントはすべて、ガベージコレクションの対象外となります。たとえば、次のコードを考えてみましょう。

TableModel myModel = ...;
JFrame frame = new JFrame();
frame.setContentPane(new JScrollPane(new JTable(myModel)));
frame.dispose();

アプリケーションがまだ myModel への参照を保持している場合、frame とそのすべての子は引き続き、JTable によって myModel にインストールされたリスナー経由で到達できます。解決方法は、table.setModel(new DefaultTableModel()) を呼び出すことです。

4.2.12 重量コンポーネントと軽量コンポーネントの混在

特定のシナリオでは重量コンポーネントと軽量コンポーネントを混在させることができます。重量コンポーネントが既存の Swing コンポーネントとオーバーラップしないかぎり、ほとんどの場合で問題はありません。たとえば、重量コンポーネントは内部フレームでは動作しません。ユーザーが内部フレームをドラッグしていろいろな場所に移動させたときに、ほかの内部フレームとオーバーラップしてしまうからです。重量コンポーネントを使用する場合は、次のメソッドを呼び出します。

4.2.13 Synth を使用するためのヒント

Synth は空のキャンバスです。Synth を使用するには、Look & Feel を構成する完全な XML ファイルを用意するか、あるいは SynthLookAndFeel を拡張して独自の SynthStyleFactory を提供します。

4.2.14 イベントディスパッチスレッド上のアクティビティーの追跡

Swing アプリケーションがイベントディスパッチスレッド上で実行を試みる処理が多すぎると、アプリケーションの動作が遅くなり、無反応になります。

この状況を検出する方法の 1 つは、処理時間の長すぎるイベントが発生した際にロギング情報を出力できる、新しい EventQueue をプッシュすることです。このアプローチは、フォーカスイベントやモーダリティーに問題があるので完璧とは言えませんが、アドホックなテストには有効です。

4.2.15 さまざまなデフォルトレイアウトマネージャー

1 つの Swing コンポーネント上にさまざまなデフォルトレイアウトマネージャークラスがあると、問題が生じる可能性があります。たとえば、JPanel クラスのデフォルトは FlowLayout ですが、JFrame クラスのデフォルトは BorderLayout です。この状況は、1 つの LayoutManager を指定することで簡単に解決されます。

4.2.16 リスナーオブジェクトはもっとも深いコンポーネントにディスパッチされる

MouseListener オブジェクトは、MouseListener オブジェクトを持つ (または MouseEvent オブジェクトを有効化した) もっとも深いコンポーネントにディスパッチされます。このため、ユーザーが MouseListener をコンポーネントに接続しても、そのコンポーネントに MouseListener オブジェクトを持つ子孫が含まれていれば、ユーザーの MouseListener オブジェクトが呼び出されることは決してありません。

このことは、編集可能な JComboBox のような複合コンポーネントで容易に再現されます。JComboBox には MouseListener を持つ子コンポーネントが含まれているため、編集可能な JComboBox に接続された MouseListener が通知を受け取ることは決してありません。

ユーザーの MouseListener が突然イベントを取得しなくなった場合、それは、アプリケーション内で何らかの変化が生じ、それによって子孫コンポーネントの 1 つが MouseListener を持つようになった結果である可能性があります。これをチェックする良い方法は、子孫に対して繰り返し処理を実行し、各子孫がマウスリスナーを持っているかどうかを確認することです。

KeyListener クラスでも似たようなシナリオが発生します。KeyListener オブジェクトは、フォーカスされたコンポーネントにしかディスパッチされません。

JComboBox のケースはこの状況のもう 1 つの例です。編集可能な JComboBox の場合、JComboBox ではなくエディタがフォーカスを得ます。結果として、編集可能な JComboBox に接続された KeyListener が通知を受け取ることは決してありません。

4.2.17 コンテンツペインへのコンポーネントの追加

J2SE 1.5 より前は、JFrameJWindowJDialog、または JApplet にコンポーネントを追加することはできませんでした。代わりに、コンテンツペインにコンポーネントを追加する必要がありました。J2SE 1.5 以降でも、トップレベルの Swing コンポーネントに追加されるコンポーネントはコンテンツペインに追加される必要があることに変わりはありませんが、これらのクラスの add メソッド (およびいくつかのほかのメソッド) はコンテンツペインにリダイレクトされます。つまり、frame.getContentPane().add(component)frame.add(component) と同じです。

次のメソッドはコンテンツペインに自動的にリダイレクトされます。add (およびそのバリアント)、remove (およびそのバリアント)、setLayout

これは非常に便利ですが、混乱を招く恐れもあります。特に、getChildrengetLayout、およびその他の各種メソッドは、コンテンツペインにリダイレクトされません。

この変更は、処理対象のコンポーネントが 1 つだけである LayoutManager (GroupLayoutBoxLayout など) に影響を与えます。たとえば、new GroupLayout(frame) は動作しません。代わりに、new GroupLayout(frame.getContentPane()) を実行する必要があります。

4.2.18 Swing でのドラッグ&ドロップサポート

Swing を使用する際には、Swing のドラッグ&ドロップサポート (TransferHandler によって提供されるもの) を使用すべきです。それ以外の場合、選択やその他のさまざまな問題を管理する必要が生じます。

4.2.19 コンポーネントの親は一度に 1 つだけ

コンポーネントは一度に 1 つの親の中にしか存在できません。メニュー間でメニュー項目を共有しようとすると、問題が発生します。たとえば、JMenuItem はコンポーネントなので、一度に 1 つのメニューの中にしか存在できません。

4.2.20 JFileChooser と Windows のショートカットの問題

JFileChooser クラスは、Windows OS のショートカット (.lnk ファイル) をサポートしません。JFileChooser では標準の Windows ファイルチューザと違って、ファイルシステムの参照時に Windows ショートカットを辿ることができません。ファイルへの正しいパスが表示されないからです。

この問題を再現するには、次の手順を実行します。

  1. デスクトップで、たとえば MyFile.txt という名前のテキストファイルを作成します。テキストファイルを開き、何らかのテキスト、たとえば This is the contents of MyFile.txt を入力します。

  2. 新しいテキストファイルへのショートカットを、次のようにして作成します。マウスの右ボタンでファイルをデスクトップの別の場所にドラッグし、「ショートカットをここに作成」を選択します。

  3. JFileChooser テストアプリケーションを実行し、デスクトップを参照して「MyFile.txt へのショートカット」を選択し、「開く」を押します。

  4. 結果の File はデスクトップへのパス\MyFile.txt へのショートカット.lnk になっていますが、これはデスクトップへのパス\MyFile.txt であるべきです。

  5. さらに、テキスト領域の結果の File の内容としてファイル MyFile.txt へのショートカット.lnk の内容が表示されますが、この内容は、最初のステップで入力した This is the contents of MyFile.txt になるべきです。