WisdomSoft - for your serial experiences.

5.2 アプリケーション

AWT 及び Swing で、PC のデスクトップ環境で動作するアプリケーションを作成します。

5.2.1 AWT アプリケーション

これまでのサンプルコードは全てアプレットビューワ、またはブラウザ上で表示するアプレットプログラミングでした。なぜアプレットから開始したかというと、アプリケーションを開発するにはウィンドウの制御など、面倒な手続きが存在するためです。

Swing によるアプリケーションは、アプレットとは異なり、実行するためにアプレットビューワやブラウザは必要ありません。システムに最新の Java 仮想マシンがインストールされていれば、単独のソフトウェアとして正しく機能することができます。特に、どこでも動く Java を用いて GUI アプリケーションを開発することには、限りない可能性があるのです。

Java で GUI アプリケーションを開発するには、トップレベルのウィンドウが必要になります。アプリケーションウィンドウは、必ず java.awt.Window クラスを継承します。Window クラスはフレームの無い作業領域だけの重量コンテナです。

java.awt.Window クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--java.awt.Window
public class Window extends Container implements Accessible

アプリケーションが独自のウィンドウを表示する場合は、この Window クラスを継承したクラスを実装する必要があります。実装方法は基本的にアプレットと同様で、paint() メソッドをオーバーライドしたり、リスナを登録するなどして、ウィンドウ独自の図を描画したり、動作を追加することができます。

Window クラスは作業領域だけのフレームの無いコンテナなので、システム標準のアプリケーション終了ボタンや、ウィンドウの最大化、最小化ボタンなどが存在しません。java.awt.Frame クラスは、これにシステム標準のフレーム(タイトルバー、ボタン、サイズ変更可能な外枠など)を追加した Window クラスの直接のサブクラスです。

java.awt.Frame クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--java.awt.Window
                    |
                    +--java.awt.Frame
public class Frame extends Window implements MenuContainer

Frame クラスは、実行する環境が提供する一般的なアプリケーションウィンドウを提供する重量コンテナです。このウィンドウの見た目や機能は、OS の ユーザーインタフェースによって大きく異なります。この Frame クラスを拡張すれば AWT によるアプリケーションを作成することができるようになります。AWT による GUI アプリケーションは、Frame クラスのウィンドウをトップレベルのアプリケーションウィンドウとします。通常、アプリケーションウィンドウが閉じられた時点でアプリケーションの終了とします。

GUI アプリケーションは Window クラスを実装するインスタンスを main() 関数が終了するまでの間に生成しなければなりません。アプリケーションウィンドウが正しく表示されれば、コードによって明示的に終了するまでアプリケーションはプロセスを解放しません。

本書がアプレットを使って解説を進めてきた理由は、アプリケーションウィンドウに要求されるイベント処理が面倒だからです。アプリケーションの場合は、アプレットと異なり、ウィンドウフレームの終了ボタンが押されたときなどにウィンドウを閉じてアプリケーションを終了させなければなりません。この処理も、独自にリスナを実装して実現する必要があるため、アプリケーションの開発方法をこの章まで控えてきたのです。

まず、Swing のアプリケーションを開発する前に、基本となる AWT の GUI アプリケーションを作成してみましょう。AWT の GUI アプリケーションは Frame クラスを実装するインスタンスを生成する必要があります。アプレットのように Frame クラスを継承しても良いですし、独自の拡張を行わないのであれば Frame() コンストラクタを呼び出してインスタンスを直接生成してもかまいません。

Frame クラスのインスタンスは Frame() コンストラクタから生成することができます。Frame クラスは、Window クラスで定義されているウィンドウの基本動作に加えて、ウィンドウの枠やタイトルバーに関連するメソッドが定義されています。

表1 Frame クラスのコンストラクタとメソッド(抜粋)
コンストラクタ 解説
public Frame() 初期状態で可視ではない Frame の新しいインスタンスを生成します。
public Frame(String title) 指定されたタイトルで、初期状態で可視ではない新しい Frame オブジェクトを構築します。
メソッド 解説
public String getTitle() フレームのタイトルを返します。タイトルは、フレームのボーダに表示されます。
public void setTitle(String title) このフレームのタイトルを、指定された文字列に設定します。
public Image getIconImage() このフレームの最小化されたアイコンに表示されるイメージを返します。
public void setIconImage(Image image) このフレームの最小化されたアイコンに表示されるイメージを設定します。
public boolean isResizable() ユーザがこのフレームのサイズを変更できるかどうかを示します。
public void setResizable(boolean resizable) ユーザがこのフレームのサイズを変更できるかどうかを設定します。

Frame コンストラクタは、初期状態では可視ではないウィンドウを生成します。つまり、Frame インスタンスを生成するだけでは、ウィンドウは画面に表示されません。生成したウィンドウを表示するには Window クラスの show() メソッドを用いて可視状態に移行する必要があります。逆に、hide() メソッドを使えばウィンドウを隠すことができます。ウィンドウの表示状態は isShowing() メソッドで取得することができます。

Window クラス show() メソッド
public void show()
Window クラス hide() メソッド
public void hide()
Window クラス isShowing() メソッド
public boolean isShowing()

ウィンドウが可視状態になれば、ディスプレイにアプリケーションウィンドウが表示されます。ただし、アプリケーションウィンドウはアプレットのように終了処理を管理してはくれません。アプレットの場合、アプレットビューワはブラウザがコンテナとなっていたため、終了時に Applet クラスの destory() メソッドが呼び出されて、自動的に破棄されました。しかし、GUI アプリケーションの場合は、ウィンドウの終了処理を独自に実装しなければなりません。

Frame クラスは、OS がサポートするタイトルバーや終了ボタンなどが表示されるので、通常は終了ボタンが押されたタイミングでウィンドウを破棄します。ウィンドウの最大化、最小化、終了ボタンなどが押されたことを知るには Window クラスにウィンドウリスナを登録する必要があります。ウィンドウリスナは addWindowListener() メソッドから登録し、removeWindowListener() メソッドで解除することができます。登録されているウィンドウリスナは getWindowListeners() メソッドから得ることができます。

Window クラス addWindowListener() メソッド
public void addWindowListener(WindowListener l)
Window クラス removeWindowListener() メソッド
public void removeWindowListener(WindowListener l)
Window クラス getWindowListeners() メソッド
public WindowListener[] getWindowListeners()

ウィンドウリスナは java.awt.event.WindowListener インタフェースを実装しなければなりません。WindowListener インタフェースは、ウィンドウの可視状態や、最大化、最小化などの変化に応じて呼び出されるメソッドを宣言しています。

表2 WindowListener インタフェースのメソッド
メソッド 解説
public void windowOpened(WindowEvent e) ウィンドウが最初に可視になったときに呼び出されます。
public void windowClosing(WindowEvent e) システムメニューでウィンドウを閉じようとしたときに呼び出されます。
public void windowClosed(WindowEvent e) ウィンドウがクローズされたときに呼び出されます。
public void windowIconified(WindowEvent e) ウィンドウが通常の状態から最小化された状態に変更されたときに呼び出されます。
public void windowDeiconified(WindowEvent e) ウィンドウが最小化された状態から通常の状態に変更されたときに呼び出されます。
public void windowActivated(WindowEvent e) Window がアクティブ Window に設定されると呼び出されます。
public void windowDeactivated(WindowEvent e) Window がアクティブ Window でなくなったときに呼び出されます。

幸い、Frame クラスはウィンドウに必要な基本動作のほとんどをサポートしています。この基本動作は、システムに依存した重量コンポーネントなので、アプリケーションを実行しているシステムによって多少の差異が生じる可能性がありますが、基本的には、ウィンドウの移動、サイズの変更、最大化、最小化といった動作が存在します。

ただし、ウィンドウの終了動作についてはアプリケーションが実装しなければなりません。フレームの終了ボタンが押されると、ウィンドウリスナの windowClosing() メソッドが呼び出されますが、ウィンドウが破棄されるわけではありません。終了ボタンが押された結果がウィンドウの破棄とするか、ウィンドウを隠すだけなのかは、アプリケーションが独自に実装することができます。

終了ボタンが押されて windowClosing() メソッドが呼び出された場合、それがアプリケーションウィンドウであれば、通常はウィンドウを破棄してアプリケーションを終了させるでしょう。この処理を実現するには、Window クラスの dispose() メソッドを呼び出します。

Window クラス dispose() メソッド
public void dispose()

dispose() メソッドが呼び出されると、そのウィンドウに含まれていた子コンポーネントも含めて、システムに割り当てられていたリソースが解放されます。dispose() メソッドによってウィンドウが破棄されると、WindowListener の windowClosed() メソッドが呼び出され、ウィンドウが完全に終了したことを知ることができます。

重要なデータを扱うビジネス系のソフトウェアだと、編集していた最新のデータが未保存の状態でウィンドウを閉じようとすると、データが保存しないまま終了しても良いかどうかを確認するメッセージを表示するアプリケーションを見かけることがあります。Java でこうした終了処理を実装するのであれば、windowClosing() メソッドで確認のメッセージを表示して、ユーザーが拒否すれば何もせずにメソッドを終了させ、ウィンドウを終了しても良いのであれば、dispose() メソッドを呼び出すようなコードを組むことになるでしょう。

ウィンドウリスナの各メソッドは、引数にイベント情報を格納している java.awt.event.WindowEvent クラスのオブジェクトを受け取ります。

java.awt.event.WindowEvent クラス
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--java.awt.AWTEvent
              |
              +--java.awt.event.ComponentEvent
                    |
                    +--java.awt.event.WindowEvent
public class WindowEvent extends ComponentEvent

WindowEvent クラスでは、イベント発生元のウィンドウを提供する getWindow() メソッドなどが宣言されています。

コード1
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Test {
	public static void main(String args[]) {
		//アプリケーションウィンドウの生成
		Frame win = new Frame("Kitty on your lap");
		win.setBounds(10 , 10 , 400 , 300);
		win.show();		//ウィンドウを表示
		win.addWindowListener(new WindowListener() {
//WindowListener の実装(匿名内部クラス)////////////
			public void windowOpened(WindowEvent e) {
				System.out.println(e.paramString());
			}
			public void windowClosing(WindowEvent e) {
				System.out.println(e.paramString());
				e.getWindow().dispose();
			}
			public void windowClosed(WindowEvent e) {
				System.out.println(e.paramString());
			}
			public void windowIconified(WindowEvent e) {
				System.out.println(e.paramString());
			}
			public void windowDeiconified(WindowEvent e) {
				System.out.println(e.paramString());
			}
			public void windowActivated(WindowEvent e) {
				System.out.println(e.paramString());
			}
			public void windowDeactivated(WindowEvent e) {
				System.out.println(e.paramString());
			}
////////////////////////////////////////////////
		});
	}
}
実行結果
コード1 実行結果

コード1は、単純なアプリケーションウィンドウを表示するだけのプログラムです。このプログラムは、アプレットビューワではなく、直接 java コマンドから実行することができる GUI アプリケーションであることが特徴です。

main() 関数が実行されると、プログラムは Frame クラスのインスタンスを生成し、ウィンドウの位置とサイズを設定してから show() メソッドでウィンドウを表示しています。ウィンドウの終了動作を正しく処理するために、WindowListener() を実装する匿名内部クラスを生成して addWindowListener() メソッドから登録していることに注目してください。このウィンドウリスナの実装は、イベントの情報を標準出力に表示します。

GUI アプリケーションは、アプリケーションウィンドウとなっている Frame オブジェクトの破棄、または System.exit() メソッドの呼び出しで終了させることができます。これがコードの適切な位置で実行されなければ、ウィンドウが隠されて何もできない状態に陥ってもプロセスが生き続け、システムのリソースが解放されません。

特別な理由がない限り、Window クラスの paint() メソッドをオーバーライドするべきではありません。Window クラスはあくまで GUI アプリケーションの基盤となる重量コンテナのウィンドウを提供するものであって、その具体的な描画内容や動作は子コンポーネントに委ねるべきでしょう。

また、Frame クラスの場合はウィンドウの枠も含めた左上隅を原点としていますが、システムが描画している枠を上書きできるわけではありません。Java アプリケーションが描画できるのはウィンドウの作業領域のみです。

ウィンドウの作業領域を表す矩形を取得したい場合、タイトルバーや境界線の幅と高さを取得し、ウィンドウの原点やサイズと計算して取得しなければなりません。ウィンドウの境界をサイズを取得するには Container クラスの getInsets() メソッドを使います。

Container クラスの getInsets() メソッド
public Insets getInsets()

このメソッドは、コンテナの周囲に描画されている境界の幅を返します。Frame オブジェクトでこのメソッドを使った場合は、ウィンドウのタイトルバーを含む境界線の幅を取得することができるため、これを基にウィンドウのクライアント領域を算出することができるでしょう。

java.awt.Insets クラスはコンテナの境界幅を提供します。

java.awt.Insets クラス
java.lang.Object
  |
  +--java.awt.Insets
public class Insets extends Object implements Cloneable, Serializable

このクラスを使えば、特定の矩形が他の矩形との間に空けなければならないマージンを表現することができます。Insets() コンストラクタには上下左右の境界線の幅を表す値を指定します。

Insets クラスのコンストラクタ
public Insets(int top, int left, int bottom, int right)

Insets オブジェクトが保有する上下左右の境界幅は、公開されている left フィールド、top フィールド、right フィールド、bottom フィールドから取得することができます。

5.2.2 Swing アプリケーション

Frame クラスは AWT に分類されるコンポーネントなので Swing アーキテクチャをサポートしていません。Swing コンポーネントを用いた GUI アプリケーションを開発するには Frame クラスに Swing のルールを適用した javax.swing.JFrame クラスを用います。

javax.swing.JFrame クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--java.awt.Window
                    |
                    +--java.awt.Frame
                          |
                          +--javax.swing.JFrame
public class JFrame extends Frame
	implements WindowConstants, Accessible, RootPaneContainer

JFrame クラスは Frame クラスに Swing アーキテクチャを導入したクラスです。JApplet クラスと同様に、JFrame クラスは Frame クラスに加え、Swing コンポーネントを正しく管理し、Swing の恩恵を受けられる機能が追加されています。Swing アーキテクチャの基本である、あらゆるプラットフォームで可能な限り同じ動作を保障しているのです。

JFrame クラスのコンストラクタは Frame クラスのコンストラクタと同じです。

コード2
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Test extends JFrame {
	public static void main(String args[]) {
		JFrame win = new Test();
		win.setBounds(10 , 10 , 400 , 300);
		win.setTitle("Kitty on your lap");
		win.show();
	}

	public Test() {
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				e.getWindow().dispose();
			}
		});
	}
}
実行結果
コード1 実行結果

コード2は、Swing アーキテクチャを採用した GUI アプリケーションの例です。このプログラムでは、JFrame を継承した Test クラスのインスタンスを生成し、show() メソッドで表示しています。Test クラスは、ウィンドウの終了ボタンが押されると、アプリケーションが終了するように拡張されています。ここで用いられている java.awt.event.WindowAdapter クラスは、WindowListener インタフェースのメソッドを空実装しているアダプタクラスです。

アダプタクラスを使ったとしても、やはりインタフェースを実装したり、新しいクラスを作成するという作業は設計面でもコード面でも面倒な手続きが必要です。JFrame クラスはこの問題を解決するために、ウィンドウの閉じるボタンを押したとき、どのように動作するべきかを表すプロパティを新たに追加しています。この値を設定すれば、わざわざウィンドウリスナを実装しなくても、ウィンドウを閉じることができるようになります。ウィンドウの終了動作を設定するには setDefaultCloseOperation() メソッドを、取得するには getDefaultCloseOperation() メソッドを使います。

JFrame クラス setDefaultCloseOperation() メソッド
public void setDefaultCloseOperation(int operation)
JFrame クラス getDefaultCloseOperation() メソッド
public int getDefaultCloseOperation()

これらの関数が扱う数値は、JFrame クラスが実装している javax.swing.WindowConstants インタフェースで定義されています。DISPOSE_ON_CLOSE はウィンドウを破棄、DO_NOTHING_ON_CLOSE は何も行わない、EXIT_ON_CLOSE はアプリケーションを終了する、そして HIDE_ON_CLOSE はウィンドウを隠すということを表します。JFrame では HIDE_ON_CLOSE が設定されています。

コード3
import javax.swing.*;
import java.awt.*;

public class Test {
	public static void main(String args[]) {
		JFrame win = new JFrame();
		win.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		win.setBounds(10 , 10 , 400 , 300);
		win.show();
	}
}
実行結果
コード3 実行結果

コード3は、setDefaultCloseOperation() 関数でデフォルトの終了動作を変更し、DISPOSE_ON_CLOSE を設定しています。これは、ウィンドウの終了ボタンが押されるとウィンドウを dispose() メソッドで破棄します。

5.2.3 フレームレスウィンドウ

JFrame クラスは Frame クラスのサブクラスなので、枠やシステムメニューなど、プラットフォーム固有の部品が付属しますが、Frame クラスのスーパークラスである Window クラスは、作業領域だけで構成されるシステムのネイティブなウィンドウを定義するのみで、ウィンドウの境界線などは付きません。

Swing では、Window クラスを継承した javax.swing.JWindow クラスを提供しています。

javax.swing.JWindow クラス
java.lang.Object
  |
  +--java.awt.Component
        |
        +--java.awt.Container
              |
              +--java.awt.Window
                    |
                    +--javax.swing.JWindow
public class JWindow extends Window implements Accessible, RootPaneContainer

このクラスは、Swing アーキテクチャを採用した枠の無い作業領域だけのウィンドウを定義しています。JWindow クラスは Window クラスのコンストラクタに加え、引数を指定しないコンストラクタも定義しています。JWindow クラスの引数を指定しない場合は、Swing の共有フレームが親となります。

表3 JWindow クラスのコンストラクタ
コンストラクタ 解説
public JWindow() オーナを指定しないでウィンドウを作成します。
public JWindow(Frame owner) オーナフレームを指定してウィンドウを作成します。
public JWindow(Window owner) オーナウィンドウを指定してウィンドウを作成します。

Window クラスのコンストラクタは、必ず親ウィンドウを指定しなければなりませんでした。JWindow は Window を継承する重量コンポーネントなので、このルールに逆らうことはできません。AWT の開発者は、Frame オブジェクトを表示させずに Window クラスのウィンドウだけを表示させたい場合、不可視の Frame オブジェクトを生成して、これを親に設定していました。つまり、次のようなコードです。

new Window(new Frame());

しかし、複数のウィンドウでこのような隠蔽したフレームを生成しては効率が悪く、システムのリソースを無意味に消費してしまう可能性があります。そこで、Swing では見えざる共通フレームというものをあらかじめ定義し、親ウィンドウを null とした場合は共通フレームを親ウィンドウとするように定めているのです。すなわち JWindow() コンストラクタは JWindow((Frame)null) に等しいのと考えることができます。ただし、このルールは Swing で採用されたものであり、Window クラスでは null の親は許されていません。null の親を持つ Window を生成しようとした場合は例外が発生するように仕組まれています。

引数の無いコンストラクタで生成した JWindow だけの GUI アプリケーションを共通フレームを使って生成した場合、JWindow ウィンドウを dispose() メソッドで破棄しても、共通フレームの見えないアプリケーションウィンドウが残されるため、アプリケーションが終了することはありません。そのため、アプリケーションを終了させるには System クラスの exit() メソッドを使いましょう。

コード4
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class Test extends JWindow {
	public static void main(String args[]) {
		Window win = new Test("kitty on your lap");
		win.setFont(new Font("Serif" , Font.BOLD , 30));
		win.setForeground(Color.RED);
		win.setBounds(10 , 10 , 400 , 100);
		win.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				System.exit(0);
			}
		});
		win.show();
	}

	private String title = "";
	public Test(String title) { this.title = title; }

	public void paint(Graphics g) {
		super.paint(g);
		g.drawString(title , 0 , g.getFontMetrics().getAscent());
	}
}
実行結果
コード4 実行結果

コード4は、フレームを持たない単純なウィンドウを画面に表示させるプログラムです。このウィンドウは終了ボタンなどのシステムに関連する機能を何も持たないため、最大化、最小化、サイズの変更、移動などの基本的な動作も持ちません。それらの動作を実現するには、アプリケーションで実装する必要があります。このプログラムでは、ウィンドウをマウスでクリックすると exit() メソッドを呼び出したアプリケーションを終了させます。

実行結果は Windows でコード4を実行した状態です。Window クラスから派生したウィンドウはフレームを持たず、作業領域の矩形をデスクトップに表示させます。一般的なソフトウェアでは、このようなウィンドウを使うことは望まれません。ユーザーにとっても、操作に慣れたシステムが提供するユーザーインタフェースの方が使いやすいでしょう。しかし、ゲームなどの演出にこだわる一部の分野では、こうしたウィンドウで独自にウィンドウの機能を実装したいというケースもあるでしょう。