最終更新日:

Chapter11
ボタンをユーザーの動作に反応させる
Eclipse(Indigo)編

ボタンを配置すればクリックを何となく受け付けますが、それ以上のことはしてくれません。そこで、クリックして何かを処理させるようなプログラムを作ってみましょう。クリックをしたときなどを含めて、「何か起こった」という場面でプログラムのある特定の部分を実行させるようなことができます。こうした一連のメカニズムは「イベント」と呼ばれますが、この機能を使うことで、ボタンをクリックすると、何かの処理を行うようなプログラムが作成されます。

11-1 ウインドウでイベントを受け付ける

イベントというメカニズムが、Javaのライブラリ機能に含まれています。ボタンのクリックの受け付けだとか、あるいはTextFieldが修正されたときに何かをするというようなことをさせるには、このイベント処理を組み込む必要があります。イベント処理の利用方法と、1つの組み込み例をまず示しましょう。

クラスを用意する

この章のプログラムは、前の章で作ったSwingGUIと同じプロジェクトに追加して行く事にします。まず、新たにButtonAction1というクラスを作ります。プロジェクトの項目を選択した状態で、新規にクラスを作成します。パッケージに「swinggui」、名前に「ButtonAction1」、スーパークラスに「javax.swing.JFrame」を指定します。「public static void main(String args[])」のチェックボックスはオフのままです。

クラス名は「ButtonAction1」という名前にする

これで、SwingGUIプロジェクトに新たにButtonAction1というクラスが作られます。続いて、Starterクラスで、ButtonAction1のウインドウが表示されるように、次のようにStarterクラスを修正しておきましょう。前の章から続けて作業をしていれば、Starterクラスの実行は、ツールバーの実行ボタンを同じようにクリックすることで可能です。

package swinggui;

public class Starter {
	public static void main( String[] args )	{
		new UseComponents();
		new UseComponentsNoLayout();
		new UseBorderLayout();
		new ButtonAction1();
	}
}

そして、ButtonAction1.javaを以下のようにします。簡単にするために、FlowLayoutを使います。実行させるとボタンが2つだけ出てきますが、いずれもクリックしても何も起こりません。ウインドウの背景はsetBackgroundメソッドで白にしています。そして、JTextAreaのインスタンスを生成してウインドウに組み込んでいます。後から、ボタンをクリックしたときにここに文字が流れるようにしたいのですが、ここでは単にオレンジ色のテキストエリアが表示されるだけです。

package swinggui;

import java.awt.*;
import javax.swing.*;

public class ButtonAction1 extends JFrame 	{
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	JButton activeButton, noActionButton;
	JTextArea messages;

	// フレームのビルド
	public ButtonAction1() {

		this.setBounds(0 ,0, 400, 300);
		this.setTitle("ボタンをクリックすると");

		contentPane.setLayout(new FlowLayout());
		contentPane.setBackground(Color.white);

		activeButton = new JButton("記入する");
		contentPane.add(activeButton);

		noActionButton = new JButton("何もしない");
		contentPane.add(noActionButton);

		messages = new JTextArea("メッセージ開始¥n", 12, 60);
		messages.setLineWrap(true);
		messages.setBackground(Color.orange);
		contentPane.add(messages);

		this.setContentPane(contentPane);
		this.setVisible(true);
	}
}
実行した結果表示されるウインドウ

イベントを受け付けるプログラム

続いて、入力したプログラムを少し改造して、クリックを受け付けるようにします。そのクリックを受け付けるメカニズムは「イベント」としてJavaの実行システムでは利用できるようになっています。この「イベント」は概念的に理解してください。一般には何か変化があったとき、あるいはユーザーが何かのアクションを起こしたとき、Javaの実行システムで「イベント」が発生します。具体的にどうなるのかということも知りたいかもしれませんが、ここはまずは抽象的に理解してください。

たとえばマウスの操作なら、マウスの操作を追い掛けるという処理が、コンピュータのどこかで行われているはずです。一般には、常に監視するというよりも、割り込みといって、たとえばマウスが動いたりクリックしたりという変化が発生すると、プログラムを中断してあらかじめ登録しておいたプログラムを実行するようなことが行われます。こうした複雑な処理が背後にはあるのですが、それはOSがやってくれていることですし、Javaのプログラムから見れば、Javaの実行システム側でこうした作業が行われています。マウスやキーボードなどの周辺機器だとイベントという意味合いがわかりやすいかもしれませんが、たとえばウィンドウというものも、ウィンドウのサイズが変わったという場合にイベントが発生するなど、いろいろな場面でイベントは発生します。いずれも、何かの変化が起こったとき、Javaの実行システム内で何かが起こるのだということを抽象的に理解してください。

イベントの考え方

キーボードの画像は、こちらからいただきました。

先ほど作ったプログラムでも、たとえばボタンをクリックすると、イベントが起こっているはずですが、別に何も表示されません。これはJavaの実行システムでは内部的にはイベント処理が行われているのですが、作ったプログラムの側でそれを一切活用していないので、イベントも泡と消えているという状態なのです。

そこで、どうすれば、システム側で発生しているイベントという機会をとらえることができるのでしょうか。まず基本は、イベントが発生すると、ある決められたメソッドが呼び出されるということです。イベントの発生は抽象的な概念ですが、実際にイベントの発生で何が起こるかと言えばメソッドの呼び出しなのです。こうしたイベント処理メカニズムに対応するためのポイントは、次の点に集約できます。

こうした機能の組み込みに、インタフェースという通常とはちょっと違ったクラスを利用します。イベントとひとくくりに説明してきましたが、マウスのクリックや、あるいはウィンドウのサイズ変更など、実際にはいろいろな種類のイベントがあります。詳細はJavaのリファレンスなどを見ていただくとして、この後のプログラムではその中の「アクションイベント」というイベントを利用します。言い換えれば、Javaの実行システムでは、Buttonをクリックするとアクションイベントが発生するのです。そのアクションイベントに対応するためのクラスとして、ActionListenerというインタフェースクラスが用意されています。インタフェースクラスは単独では使えず、既存のクラスに組み込むような使い方をします。

このクラス名からわかるように、ActionListenerですので、「アクションイベントを受信できる人」のような意味でとらえてください。つまり、イベントはシステム側で発生しますが、その受け手のクラスをプログラムで作ろうというわけです。そのためのインタフェースとしてActionListenerが用意されているというわけです。

イベント処理を組み込んだプログラム

では、実際にButtonAction1.javaにイベント処理を組み込んでみましょう。次のようなプログラムになります。前に入力した、ButtonAction1.javaにプログラムを追加して、以下のような内容になるように修正します。実行結果を確かめてから、プログラムの詳細を説明します。

package swinggui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ButtonAction1 extends JFrame implements ActionListener {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	JButton activeButton, noActionButton;
	JTextArea messages;

	public ButtonAction1() {

		this.setBounds(0 ,0, 700, 300);			//ここも修正
		this.setTitle("ボタンをクリックすると");

		contentPane.setLayout(new FlowLayout());
		contentPane.setBackground(Color.white);

		activeButton = new JButton("記入する");
		activeButton.addActionListener(this);	//ここを追加
		contentPane.add(activeButton);

		noActionButton = new JButton("何もしない");
		contentPane.add(noActionButton);

		messages = new JTextArea("メッセージ開始¥n", 12, 60);
		messages.setLineWrap(true);
		messages.setBackground(Color.orange);
		contentPane.add(messages);

		this.setContentPane(contentPane);
		this.setVisible(true);
	}

	private String alfabets[] = { "A", "B", "C", "D", "E", "F", "G", "H", "I",
			"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
			"W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i",
			"j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
			"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8",
			"9" };

	public void actionPerformed(ActionEvent e) {
		System.out.println("Actionイベントが発生しました。");

		StringBuffer currentText = new StringBuffer(messages.getText());
		for (int i = 0; i < 10; i++) {
			int index = (int) (Math.random() * alfabets.length);
			currentText.append(alfabets[index]);
		}
		currentText.append(" ");
		messages.setText(currentText.toString());
	}

}

大きな違いはactionPerformedメソッドが追加されたことですが、それよりも前に重要なポイントがあります。この説明はあとですぐに行うとして、とにかく実行してみてください。

「何もしないボタン」をクリックしても、特に何も起こらないはずです。「記入する」ボタンをクリックすると、テキストエリアにランダムな文字が10文字追加されて行きます。何度もクリックしてみてください。また、JBuilderのコンソールをみると、「Actionイベントが発生しました。」と表示されているはずです。前のプログラムと比較すると、「ボタンのクリックに反応した」という重要な機能的な違いがあることをよく認識してください。

実行しボタンを何度かクリックしたところ

テキストエリアに文字を入力する

イベント処理の説明をする前に、テキストエリアに文字が追加される部分の説明をしておきましょう。テキストエリアはJTextAreaクラスから生成されたコンポーネントで、テキストエディタのような文字を表示したり編集が可能な領域をウインドウに確保できます。setLineWrapメソッドで設定を行うことで、行の右端で折り返しすることもできるので、長いテキストであっても見やすく表示できます。

actionPerformedメソッドが実際にクリックしたときに呼ばれるメソッドです。テキストエリアから、getTextメソッドを使うと、その中にあるテキストの文字列を得ることができます。これをStringBufferクラスのインスタンスの初期値として設定します。変更や修正が可能なStringBufferを確保しますが、初期値はテキストエリアの内容です。その後、10回の繰り返しが行われています。その中のメソッドをみる前に、配列のalfabetsをみてください。String型の配列で、1つの要素に何かしらの文字が1つ入っています。この配列は、インスタンス変数なので、クラスを生成したときにこのように初期化されます。

変数indexに代入する式をみてください。まず、Math.random()ですが、Mathというのはクラス名で、staticなメソッドrandomによって、0〜1までのdouble型の乱数値が得られます。staticなのでMath.random()という記述をします。そして、alfabets.lengthは配列alfabetsの個数です。乱数と個数をかけてintにキャストすることによって、0〜(配列alfabetsの個数-1)の整数が得られます。

すると、alfabets[index]は、配列alfabetsのどれかが存在する要素ということになり、indexが乱数から求められていることで、要はStringBufferのcurrentTextに、ランダムに文字が10回選ばれて追加されていくということになります。最後は空白を追加し、11文字追加された結果をsetTextでテキストエリアに戻しているというわけです。このような仕組みで、ボタンのクリックにより、ランダムな文字がテキストエリアに増えていくということになります。

11-2 イベントが処理されるしくみ

前の、ButtonAction1.javaのプログラムの内容を続けて説明しましょう。イベントを受け取るようにするには、まず、クラスにイベントを受け取るという機能を組み込みます。

ButtonAction1.javaでは、イベントを組み込む前の全体的な構成を変えないで、イベント処理ができるように組んでみました。ウインドウのクラスであるButtonAction1がイベントを受け取るのです。

ButtonActionがイベントを受け取れるようにするには、クラスを定義するclassキーワードの記述の最後に「implements ActionListener」という記述を付けます。このActionListenerというのが、アクションイベントを処理するという機能の一種のひな形だと思ってください。アクションイベントを受け付ける機能を、ButtonActionクラスに組み込むという意味合いです。

ActionListenerは、java.awt.eventというパッケージに所属しています。ここで、ActionListenerを利用できるようにするために、「import java.awt.event.*;」という記述が最初の部分にあるわけです。

ActionListenerを組み込むと、そのクラスでは必ず、actionPerformedという名前のメソッドを定義しなければならなくなります。このメソッドが定義されていないとエラーになります。また、このメソッドはActionEvent型の引数1つだけを取るというのもやはり定義されていて、それに従わなければなりません。

いずれにしても、implements ActionListenerと、public void actionPerformed(ActionEvent e)というメソッドを定義することで、そのクラスはアクションイベントを受け付けることになります。

クラスをアクションイベント対応にする

そして、ButtonAction1というクラスのインスタンスでアクションイベントを受け付けることをシステムに教えておく必要があります。

そのために使われるのがaddActionListenerというメソッドです。ここではJButtonオブジェクトに対して利用しています。アクションイベントが発生するコンポーネントには、必ずこの名前のメソッドが用意されています。

これにより、オブジェクトにアクションイベントが発生した場合に、addActionListenerの引数で指定したオブジェクトのactionPerformedメソッドが呼び出されることになります。つまり言い換えれば、ボタンをクリックすると、ButtonAction1クラスのactionPerformedメソッドが呼び出されるのです。

アクションイベントよって呼び出すクラスを指定する

プログラムでは、

activeButton.addActionListener(this);

として、this、つまりButtonActionが、「記入する」ボタンをクリックしたときに呼び出されるようになっています。

複数のコンポーネントのアクションイベントがある場合

このような作りのプログラムにした場合、どういう状況でactionPerformedが呼び出されるかをもう少し考えてみましょう。ここでは、アプレットの中にあるコンポーネントのうち、「記入する」ボタンのJButtonオブジェクトだけaddActionListenerで、イベント発生とクラスの結び付きを指定しています。ここで、もう1つの「何もしないボタン」にも、たとえば、

noActionButton.addActionListener(this);

として、アクションイベントに対応するクラスを登録したとします。すると「記入する」も「何もしないボタン」も、どちらもButtonAction1クラスのactionPerformedメソッドを呼び出すことになります。どちらも、this、つまりアプレットが引数に指定されているわけで、ButtonAction1クラスのactionPerformedメソッドが呼び出されるというわけです。

同様に、もっとボタンやTextFieldがあったりして、それらに対してアクションイベントに対応する処理が欲しいとします。このとき、1つの選択肢としては、オブジェクトごとに異なるメソッドを呼び出すようにするという方法があります。一方、それぞれの同一のメソッドを呼び出すActionListenerを組み込むこともできます。後者の場合どのオブジェクトでアクションイベントが発生しても、同一のButtonActionクラスのactionPerformedメソッドが呼び出されることになります。従って、actionPerformedメソッドの呼び出しがあったときに、どのコンポーネントからのアクションメソッドなのかを判断して、コンポーネントごとの処理に分岐する必要が、どうしても出てきます。

どのコンポーネントで発生したオブジェクトなのかを判断するには、actionPerformedメソッドの引数(この場合はe)を利用します。この引数eは、ActionEventクラスのオブジェクトなのですが、このクラスで利用できるgetSourceメソッドを利用すると、イベントが発生したオブジェクトがわかるわけです。そして、イベントに対してgetSourceメソッドを利用してイベントが発生したオブジェクトを調べます。実はこのサンプルのプログラムはあまり汎用性がありません。イベントの発生したオブジェクトがJButtonだと仮定してキャストしてしまい、ボタン名を取り出すgetTextメソッドを利用しています。そして、ボタン名が「記入する」かどうかを、equalsIgnoreCaseメソッドで比較しています。equalsIgnoreCaseメソッドはStringクラスに定義されたメソッドで、equalsと同様ですが、大文字小文字は無視するというものです。もっとも、漢字の文字列ではあまり意味は持ちませんが。

別の方法としてはgetSourceで調べてイベントの発生元と、実際にJButtonなどを参照している変数(ここではdrawButton)を==演算子で比較するということでも対処できます。これだと、オブジェクトの種類に関係なく、どのコンポーネントからのイベントなのかを判断し、分岐させることができます。

このプログラムは、実質的にボタンが1つなので、actionPerformedメソッドで、どのコンポーネントから来るイベントなのかを判断する必要はありません。

11-3 イベント処理をボタンに適用する

同じ動作をするプログラムをもう1つ作ってみますが、イベントとその対応方法の書き方として異なる手法を用います。プロジェクトのSwingGUIの項目を選択して、新たなクラスを作ります。パッケージに「swinggui」、名前に「ButtonAction2」、スーパークラスに「javax.swing.JFrame」を指定します。「public static void main(String args[])」のチェックボックスはオフのままです。

クラス名は「ButtonAction2」という名前にする

新たに作成されたButtonAction2クラスをインスタンス化して実行されるように、Starterクラスを以下のように変更しておきます。

package swinggui;

public class Starter {
	public static void main( String[] args )	{
		new UseComponents();
		new UseComponentsNoLayout();
		new UseBorderLayout();
		new ButtonAction1();
		new ButtonAction2();
	}
}

ButtonAction2.javaというファイルが作成されているはずですので、その内容を、以下のように修正します。ButtonAction1.javaとかなり近いので、一度全部コピー&ペーストして、違う部分だけを書き直してもいいでしょう。

package swinggui;

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class ButtonAction2 extends JFrame {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	JButton activeButton, noActionButton;
	JTextArea messages;

	public ButtonAction2() {

		this.setBounds(0, 0, 700, 300);
		this.setTitle("ボタンをクリックすると");

		contentPane.setLayout(new FlowLayout());
		contentPane.setBackground(Color.white);

		activeButton = new JButton("記入する");
		activeButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				clickButton(e);
			}
		});

		contentPane.add(activeButton);

		noActionButton = new JButton("何もしない");
		contentPane.add(noActionButton);

		messages = new JTextArea("メッセージ開始¥n", 12, 60);
		messages.setLineWrap(true);
		messages.setBackground(Color.green);
		contentPane.add(messages);

		this.setContentPane(contentPane);
		this.setVisible(true);
	}

	private String alfabets[] = { "A", "B", "C", "D", "E", "F", "G", "H", "I",
			"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
			"W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i",
			"j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
			"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8",
			"9" };

	public void clickButton(ActionEvent e) {
		System.out.println("Actionイベントが発生しました。");

		StringBuffer currentText = new StringBuffer(messages.getText());
		for (int i = 0; i < 10; i++) {
			int index = (int) (Math.random() * alfabets.length);
			currentText.append(alfabets[index]);
		}
		currentText.append(" ");
		messages.setText(currentText.toString());
	}

}

実行してみます。動作上はほとんど変わりありません。区別がつくように、JTextAreaはバックをグリーンにしておきました。

実行結果

メソッドの中でクラスを定義

ButtonAction2は動作はButtonAction1とまったく同じです。プログラムの違いをチェックしましょう。まず、ButtonAction1は、classキーワードのところで、implements ActionListnerという記述があり、このクラス自体がイベントを受け付ける機能を持たせるということを記述しました。しかしながら、ButtonAction2はそれがありません。このプログラムは、ボタンそのものをプログラム上でダイレクトにアクションリスナにしてしまっているので、クラス全体にリスナの役割を持たせなくてもいいのです。

ボタンを参照するactiveButtonに対してaddActionListenerによってリスナを追加するのは同じですが、その中はあたかも、新しいクラスの定義のような書き方になっています。さらにそのクラスの定義の前にnewがあり、インスタンス生成が行われています。実際にはこのボタンのアクションを処理するクラスが背後で生成されますが、その点は実行時に行われるので特に気にする必要はありません。アクションイベントが発生したときの「actionPerformed」メソッドが定義されています。このメソッドはclickButtonメソッドを呼び出しています。clickButtonメソッドは、ButtonAction2に定義されたメソッドで、結果的にボタンをクリックするとclickButtonメソッドが呼び出されるわけです。

このように、addActionListenerメソッドの引数内で、あたかもテンポラリな雰囲気でクラスを定義してしまうということも可能です。詳細な文法についてはこのテキストでは説明しません。このような、クラス内でクラスを定義する方法は「内部クラス」などと呼ばれていますが、さらにこのように特に名前を新たに付けないクラスは「匿名」クラスと呼ばれています。

この部分について、プログラムがちょっと複雑な構造になるので、解きほぐしましょう。アクションイベント処理の考え方はこれまでと同様ですが、ここではまず、生成した「記入する」ボタンのJButtonオブジェクトに、addActionListenerで、イベント受け付けクラスを結び付けているというところを見てください。つまり、

activeButton.addActionListener(〈イベント受け付けクラス〉);

という大枠の構成になっているのです。〈イベント受け付けクラス〉の部分が何行にも渡ってちょっと長くなっています。今までは、大概その部分にthisとだけ簡単に記述してすんでいたのですが、この匿名クラスという方法は、addActionListenerの引数内にクラス定義があるようなものと考えてもらえればよいでしょう。

もう少し深く掘り下げると、

activeButton.addActionListener(
	new ActionListener()   {	
		〈クラス定義の中身〉
	}
);

という構造になっています。ここで、クラス定義というよりも、インタフェースクラスを直接生成しているような書き方になっている点に注目してください。ActionListenerはインタフェース名です。implementsとかは使わずに、このように、newで直接インタフェース名を記述し、インタフェースで定義した機能を持つクラスをその場で定義するのです。

ActionListenerを組み込んだクラスでは、actionPerformedメソッドが定義されていなければなりません。それをさらに次のように、new ActionListenerに続いて記述してしまいます。

activeButton.addActionListener(
	new ActionListener()   {	
		public void actionPerformed(ActionEvent e)   {
			〈アクションイベントの処理〉
		}
	}
);

このように、匿名クラスを使った書き方だと、1つの場所にアクションイベントの処理についての記述をまとめることができます。こうした書き方がコンパクトでいいと感じることもあるでしょう。

11-4 「クリックに対応するJButton」を作る

アクションイベントに対応する方法はこれまでに説明した通りです。いずれにしても、規定に従ったクラスを作成するということで総括できます。ウインドウにイベントを受け付ける機能を組み込みましたが、少し発想を変えてみましょう。というか、むしろ直接的に考えれば、JButtonというクリック可能なボタンを、クリックして何かの処理(具体的にはテキストエリアに文字を追加する)を行うようなボタンに改良するということを考えます。つまり、既存のJButtonというクラスがありますが、それを発展させて、クリックしたときに描画処理まで行うように機能拡張するのです。

Javaではというか、オブジェクト指向プログラミング環境では、こうした既存の機能の拡張ができるというのが大きな特徴です。これまでも、JFrameというのを拡張して独自のウインドウを作ってきました。これと同じように、JButtonを拡張して、望む機能を組み込んだボタンを作ります。そのための新しいボタンのクラス定義も行います。

ここでは、新しいボタンはTextButtonという名前にしましょう。従って、

class TextButton extends JButton { ... }

のようなクラス定義が加わることになります。

このTextButtonに、イベントの処理機能を付けることにします。つまり、アクションイベントが発生すると、TextButtonのactionPerformedメソッドを呼び出すということにします。そうなると、TextButtonクラスの定義の大枠は次のようになるでしょう。

class TextButton extends JButton implements ActionListener {
	public void actionPerformed(ActionEvent e)   {	
	}
}

これらがイベントまわりの基本方針です。ただ、ここではボタンから直接外部のテキストエリアの処理をしたいと考えます。そのとき、完全に分離したクラスにするとかえって処理が面倒になりますので、TextButtonクラス自体は、ウインドウのクラスの内部クラスとして定義することにします。

プログラムの作成

プロジェクトのSwingGUIの項目を選択して、新たなクラスを作ります。パッケージに「swinggui」、名前に「TextGenerateButton」、スーパークラスに「javax.swing.JFrame」を指定します。「public static void main(String args[])」のチェックボックスはオフのままです。

クラス名は「TextGenerateButton」という名前にする

新たに作成されたTextGenerateButtonクラスをインスタンス化して実行されるように、Starterクラスを以下のように変更しておきます。

package swinggui;

public class Starter {
	public static void main( String[] args )	{
//		new UseComponents();
//		new UseBorderLayout();
//		new ButtonAction1();
//		new ButtonAction2();
		new TextGenerateButton();
	}
}

TextGenerateButton.javaというファイルが作成されているはずですので、その内容を、以下のように修正します。ButtonAction1.javaやButtonAction2.javaとかなり近いので、一度全部コピー&ペーストして、違う部分だけを書き直してもいいでしょう。

package swinggui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

public class TextGenerateButton extends JFrame {
	private static final long serialVersionUID = 1L;

	JPanel contentPane = new JPanel();
	TextButton activeButton;
	JButton noActionButton;
	JTextArea messages;

	//フレームのビルド
	public TextGenerateButton() {
		this.setBounds(0, 0, 700, 300);
		this.setTitle("ボタンをクリックすると");

		contentPane.setLayout(new FlowLayout());
		contentPane.setBackground(Color.white);

		activeButton = new TextButton("記入する");
		contentPane.add(activeButton);

		noActionButton = new JButton("何もしない");
		contentPane.add(noActionButton);

		messages = new JTextArea("メッセージ開始¥n", 12, 60);
		messages.setLineWrap(true);
		messages.setBackground(Color.pink);
		contentPane.add(messages);

		this.setContentPane(contentPane);
		this.setVisible(true);
	}

	/* これ以降が、テキストを入力する機能を持ったボタンのクラス */
	public class TextButton extends JButton implements java.awt.event.ActionListener {
		private static final long serialVersionUID = 1L;

		public TextButton(String label){
			super(label);
			this.addActionListener(this);
		}
		private String alfabets[] = {"A","B","C","D","E","F","G","H","I","J","K","L",
			"M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d",
			"e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v",
			"w","x","y","z","0","1","2","3","4","5","6","7","8","9"};

		public void actionPerformed(ActionEvent e) {
			System.out.println("Actionイベントが発生しました。");

			StringBuffer currentText = new StringBuffer( messages.getText() );
			for ( int i = 0; i < 10; i++ ){
				int index = (int)(Math.random() * alfabets.length);
				currentText.append( alfabets[index] );
			}
			currentText.append( " " );
			messages.setText( currentText.toString() );
		}
	}
}
実行結果

リストでは、TextButtonがJButtonを拡張したもので、ActionListenerをインプリメントし、actionPerformedメソッドが定義されている点を、プログラムを見てまずチェックしてください。また、「記入する」ボタンを参照する変数activeButtonは、JButton型ではなくTextButton型としてインスタンス変数で定義されているところも確認してください。そして、activeButtonへの代入は、newでTextButtonクラスのインスタンスが生成されていることもポイントです。

アクションイベントが起こったときに、TextButtonクラスのactionPerformedを呼び出すようにするには、addActionListenerメソッドをどう実行すればよいのでしょうか。これは、TextButtonクラスのコンストラクタで記述されています。コンストラクタは、newでTextButtonクラスを作成するときに呼び出されるもので、クラス名と同名のメソッドのような記述をしている部分がコンストラクタです。そこでは、

this.addActionListener(this);

となっています。thisすなわちTextButton自身が、このボタンのアクションイベントの発生時に呼び出されるようにするということを、ここでシステムに教えているわけです。このように、イベント受け付けクラス内で、自分自身を呼び出すということを記述してしまうことができるのです。

このように、特定の機能を持ったクラスを作った場合、アクションイベントで呼び出されたactionPerformedメソッドでは一般には、どのボタンから呼び出されたものかをいちいち判断しなくてすむようにできます。ここでは描画機能を持ったボタンを定義しましたが、違う機能のボタンの場合は、また別のクラスを定義すればすみます。したがって、一般にはボタンごとに処理を分岐するというよりも、ボタンごとにクラスを分けてしまうので、actionPerformedが呼び出された段階で、各クラスでやることは決まっているという状況にすることができるのです。目的によってクラスを分けてしまうことで、プログラム自体を見通しよいものにすることができるわけです。

クラスの内部でクラスを定義した場合、外側のクラスのインスタンス変数が、内部クラスでも利用できるようになります。つまり、インスタンス変数がクラス間で共通に使えるということにつながります。実際、TextButtonクラスで変数messagesを参照していますが、この変数はTextButtonを含むTextGeneratedButtonクラスに定義されているインスタンス変数です。このように、機能的には別のクラスとして分離して定義できますが、共通に使いたいデータは外側のクラスのインスタンス変数として定義することで、手軽にそれぞれのクラスでやりとりができるようになります。

練習問題

11-1:

ウインドウ内に、テキストフィールド(JTextField)が2つ、ドロップダウンリスト(JComboBox)が1つ、ボタンが1つ、ラベル(JLabel)が1つあるようにします。ドロップダウンリストでは、四則演算の記号、+−×÷を選択できるようにしておきます。そして、テキストフィールドに数字を入れて、ボタンをクリックすると、ドロップダウンリストで選択している演算記号に応じて、それぞれ2つのテキストフィールドの値に対して演算を行った結果をラベルに表示するようにすること。(たとえば、テキストボックスにそれぞれ10と3という数値があって、ドロップダウンリストで×が選択されていれば、ボタンをクリックすると、ラベルに30が表示されるようにする。)

ヒント:JComboBoxで何番目が選択されているかを知るには、getSelectedIndex()メソッドを使えばいい。また、選択されている項目は、getSelectedItem()メソッドを使う。いずれも、引数は指定しない。

画面表示をわかりやすくするために、それ以外のコンポーネントを設定してもいい。

応用問題:ドロップダウンリストの演算記号を選択すると、即座にラベルに選択しなおした演算子による計算結果が入るようにすること。(レポート課題には入れていませんが、ここまでできたのなら、レポート課題の1つとして提出してもかまいません)