Java GUI
Java で使用できる GUI
現在 Java で利用されている主な GUI (Graphical User Interface) ライブラリは AWT、Swing、SWT の 3 つです。ただし AWT のみによる GUI アプリケーション開発は既にほとんど行われておりません。
- AWT (Abstract Window Toolkit)
-
初期の Java で標準として使用されていた GUI ライブラリ。当時ウィンドウシステムごとに使い方がバラバラだったウィジェットを抽象化し、統一した API で利用できることを目的としていました。Swing が十分熟成された今では AWT のみで GUI を組む必要性もなくなっていますが、組み込み系では標準で使用されています。
- Swing (旧JFC; Java Foundation Classes)
-
実行環境依存のウィジェットに頼らず、全てのコンポーネント描画と状態の管理を Java で実装した GUI ライブラリ。AWT の上に成り立っています。当初は何かと問題がありましたが現在では機能も速度も安定しています。
- SWT (Standard Widget Toolkit)
-
JNI を多用することで過去の Swing で問題となっていたグラフィックのボトルネックを回避した非標準の GUI ライブラリ。しかし今では Swing の高速化によりその優位性もほとんどなく (一部逆転し)、主に以下のような状況で選択します。
- Swing の Look and Feel よりもっとプラットフォームらしいウィジェットが使いたいが AWT では機能が不足している。
- ウィンドウメッセージなど SWT が実装している OS のネイティブ機能を利用したい。または開発者が C/C++ スタイルのウィンドウプログラミングに慣れている。
- Eclipse のプラグイン開発やワークベンチを利用したアプリケーション開発。
軽量コンポーネント vs 重量コンポーネント
AWT ではネイティブとほぼ同じ状態でウィジェットが使用されるため、例えば Windows であれば個々のウィジェットはウィンドウリソースを持ち、OS とメッセージでやり取りを行っています。Windows で AWT のコンポーネントを調べてみると、それぞれにウィンドウハンドルが割り当てられていることが分かります。
一方で Swing は自前で描画処理を行うため、必要な描画領域だけをプラットフォームに要求します。Swing のウィンドウ調べてみると領域の矩形分にだけウィンドウハンドラが割り当てられていることが分かります。
Swing のボタンは GUI コンポーネントでありながらプラットフォームのウィンドウリソースを消費していません。このようなコンポーネントを Light Weight Component (軽量コンポーネント) と呼び、逆にリソースが割り当てられているコンポーネントを Heavy Weight Component (重量コンポーネント) と呼びます。
AWT のコンポーネントは全て重量コンポーネントです。また Swing でも JFrame, JDialog などのいくつかのコンポーネントは重量コンポーネントです。JMenu は表示領域がフレームからはみ出る場合にのみ重量コンポーネントとなります。
Swing はこの軽量化によってウィンドウリソース消費や JNI のオーバーヘッドが軽減され、また描画がウィンドウメッセージを介さずにダイレクトにグラフィック API へ行く構成になったため、理論的には「軽く」なりました (このままグラフィック性能が向上して行けばいつかウィンドウメッセージがボトルネックになるだろうと予想されていました)。
しかし現実問題として重量コンポーネントと軽量コンポーネントの差はネイティブのリソースを割り当てられているかどうかでしかなく、動作速度にまでクリティカルに影響するほどの要因ではありません (重量/軽量という言葉自体が Swing デビューのためのキャッチーな意味合いが強かった)。そもそも重量コンポーネントが遅いならプラットフォームのネイティブアプリケーションが全て遅いと言うことになってしまいますから。
Swing の余談的昔話…
しかしこれはあくまで理論上の話。全ての GUI 処理を Java で実装しなおした Swing は当初からさまざまな問題を孕んでおり、結果的に AWT より遅くてバグだらけの太った GUI ライブラリと評価されます。
- 描画処理を Java 側に持ってきた事によりプラットフォームのハードウェアアクセラレーションの恩恵がほとんど受けられなくなった。Light Weight 化よりもこの影響の方が遥かに大きく出てしまったため、結果として著しく速度が劣化した。
- 当時はまだ PC のメモリも少なく Java VM 実装も貧弱なものだったため、Java で多くの処理を行う事はデメリットでしかなかった。Java に閉じた描画処理と MVC 設計の強制が当時の Java には「理想的だが無駄なもの」として嫌われた。
- いわゆる「初モノ実装」としてあちこちの挙動が不振だった。
J2SE 1.3 での DirectDraw 対応で幾分マシにはなったものの、Swing のダメダメ感に見切りを付けた IBM は実用最優先で GUI 周辺を JNI で固めた Eclipse を開発します。Java の GUI アプリケーションでも十分実用に耐える品質になる事を知らしめた Eclipse の登場は当時非常にセンセーショナルでした。程なくその GUI 部分 (SWT) だけを利用してのアプリケーション開発も行われるようになります。
Eclipse の成功は Java GUI のネックが Swing であるという事を如実に表していました。この SWT の後追いもあり、ハードウェアアクセラレーションへの対応や Windows, X11 などのプラットフォーム依存度を減らすなど GUI 周辺が大幅な改善が行われ、J2SE 1.4 でやっとネイティブアプリケーションと比べても遜色の無い品質に仕上がりました。
ウィジェットの混在方法
AWT, Swing, SWT を混在しての利用はあちこちに落とし穴があるので基本的にお勧めできません。諸事情でどうしてもというのであればそれぞれの特性の違いをよく理解し十分に注意する必要があります。
AWT-Swing 混在の注意点
Swing は AWT の上に成り立っていますが、両者の混在は軽量コンポーネントと重量コンポーネントの描画の違いから問題を引き起こす可能性があります。代表的な例は Swing のメニューが AWT のコンポーネントの下に隠れてしまう問題です。
上記は AWT のテキスト領域が重量コンポーネント (物理的に別のウィジェット) であるため、軽量コンポーネントである Swing のメニューはその下に描画されています。
JMenu の他にも JPopupMenu、JComboBox のドロップダウンリスト、その他意図的に重ね合わせて使用しているコンポーネントは同じ問題が発生します。また同じ Swing 間でも重量コンポーネントである JApplet を一般のコンポーネントと同じように使用すると同様の問題が発生します。
なお上記の例でウィンドウをもう少し小さくしてメニューがフレームからはみ出るよう調整すると、メニューが重量コンポーネントに昇格してテキスト領域の上に表示されます。
AWT-Swing 混在時の問題は重量コンポーネントの上に軽量コンポーネントを描画しないよう十分注意して設計されているのであれば ─ 例えばフレームやダイアログ単位で完全に住み分けができているなら問題ありません。
Java 7 では軽量コンポーネントと重量コンポーネントを混在させても問題が起きないように改良される予定です。
SWT-AWT/Swing の混在
SWT には AWT との相互利用が可能となるように SWT_AWT というクラスが用意されています。しかし両者はイベントキューと EDT (イベントディスパッチスレッド) を個別に持っており、一方の EDT から他方の機能を直接実行しないよう注意が必要です。
- AWT/Swing から SWT 機能を実行する場合は
Display#syncExec()
、Display#asyncExec()
を使用して SWT の EDT で間接的に実行する。 - SWT から AWT/Swing 機能を実行する場合は
SwingUtilities#invokeLater()
、SwingUtilities#invokeAndWait()
を使用して AWT の EDT で間接的に実行する。
SWT_AWT.new_Shell()
を使用することで AWT の Canvas 領域上に SWT の Shell をマッピングし、コンポーネントを埋め込むことができます。ただし Canvas は重量コンポーネントであるため Swing に SWT を埋め込む場合は AWT/Swing の混在と同じメニューの重なりの問題が発生します。
以下は Swing の JFrame 上で SWT の Browser を使用したサンプルです。
final Display display = Display.getDefault();
JFrame frame = new JFrame();
Canvas canvas = new Canvas();
frame.setSize(200, 150);
frame.setLocationRelativeTo(null);
frame.setLayout(new BorderLayout());
frame.add(canvas, BorderLayout.CENTER);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e) {
// AWT イベント処理からは SWT の機能を直接実行しない
display.syncExec(new Runnable(){
public void run(){
display.dispose();
}
});
return;
}
});
// Canvas のピアが生成されるまで待機
while(! canvas.isDisplayable()){
try{
Thread.sleep(100);
} catch(InterruptedException ex){/* */}
}
// Canvas の領域に Browser を埋め込み
Shell shell = SWT_AWT.new_Shell(display, canvas);
shell.setLayout(new FillLayout());
Browser browser = new Browser(shell, SWT.NULL);
browser.setUrl("http://www.google.com");
shell.pack();
// SWT のメッセージディスパッチループ
while(! shell.isDisposed()){
if(! display.readAndDispatch()){
display.sleep();
}
}
display.dispose();