Processing依存からの脱却−イベントリスナー(2)

 前回はイベントリスナーについて書きました。今回もその続きです。あ、今日からパッケージやライブラリのインポート文などの記述を省略することにします。

 イベントリスナーは前回の方法で実装できました。しかしながら、前回の方法では多少問題点があるようです。

問題点
イベントリスナーが呼び出されたときに呼び出し元のコンポーネントを弄ろうとしても、イベントリスナー側から呼び出し元はどこか分からないため、呼び出し元のコンポーネントを弄くれない。

 例えば、前回のプログラムでは"clicked!"というのをコンソール画面に表示していましたが、"Click me!"と書かれたボタンをクリックしたときに、ボタンの文字が"clicked!"に変わるようにしたくても、イベントリスナーは「どこから呼び出された」という情報を受け取っていないため、どこを書き換えればいいのかわからないのです。

 この問題を解決するためにはいくつか方法があります。

方法1.イベントリスナーを内部クラスにする。

 イベントリスナーから呼び出し元がわからないのはそれぞれが独立したクラスだったからです。ならば、内部クラスにしてしまえば、それは解決してしまいます。

public class Main extends Frame {
	Button b;

	public Main(){
		super();
		this.setSize(200, 100);
		b = new Button("Click me!");
		b.addActionListener(new ButtonListener());
		this.add(b,BorderLayout.CENTER);
		this.setVisible(true);
	}

	public static void main(String[] args) {
		new Main();
	}

	class ButtonListener implements ActionListener{
		public void actionPerformed(ActionEvent e){
			b.setLabel("clicked!");
		}
	}
}

 前回のプログラムのままでは、bのスコープがコンストラクタの中だけだったため、内部クラス内で使うためにMainのフィールド領域にbの宣言をしておきました。


方法2.クラス内に呼び出し元を保存するフィールド変数を用意する

 イベントリスナーが呼び出し元が分からないのなら、教えてやればいいだけの話です。

public class Main extends Frame {
	Button b;

	public Main(){
		super();
		this.setSize(200, 100);
		b=new Button("Click me!");
		ButtonListener bl = new ButtonListener();
		bl.main = this;
		b.addActionListener(bl);
		this.add(b,BorderLayout.CENTER);
		this.setVisible(true);
	}

	public static void main(String[] args) {
		new Main();
	}
}

class ButtonListener implements ActionListener{
	Main main;

	public void actionPerformed(ActionEvent e){
		main.b.setLabel("clicked!");
	}
}

ここで、重要な部分は

ButtonListener bl = new ButtonListener();
bl.main = this;

という部分です。ここで、イベントリスナーをblとしてインスタンス化し、そのフィールド変数mainに自分自身を代入することで、イベントリスナーに呼び出し元を教えています。


方法3.呼び出し元自身をイベントリスナーにする。

 イベントリスナーとはインタフェースです。インターフェースの利点は多重継承が許されないJAVAにおいて、唯一、多重継承(っぽいこと)ができることです。それでは、Mainクラス自体にイベントリスナーを付け加えてはどうでしょうか。

public class Main extends Frame implements ActionListener{
	Button b;

	public Main(){
		super();
		this.setSize(200, 100);
		b=new Button("Click me!");
		b.addActionListener(this);
		this.add(b,BorderLayout.CENTER);
		this.setVisible(true);
	}

	public static void main(String[] args) {
		new Main();
	}

	public void actionPerform(ActionEvent e){
		b.setLabel("clicked");
	}
}

この方法では

b.addActionListener(this);

の部分で自分自身をイベントリスナーとしてボタンに与えています。そして、Main自身の中にactionPerformメソッドを加えています。

方法4.無名クラスとして組み込む

 発想としては内部クラスの方法に近く、独立だからダメなんだ。だから中に入れちゃえってやつです。

public class Main extends Frame implements ActionListener{
	Button b;

	public Main(){
		super();
		this.setSize(200, 100);
		b=new Button("Click me!");
		b.addActionListener(new ActionListener(){
			public void actionPerformed(ActionEvent e){
				b.setLabel("clicked!");
			}
		});
		this.add(b,BorderLayout.CENTER);
		this.setVisible(true);
	}

	public static void main(String[] args) {
		new Main();
	}
}

この方法では

b.addActionListener(new ActionListener(){
	public void actionPerformed(ActionEvent e){
		b.setLabel("clicked!");
	}
});

の部分でイベントリスナーを無名クラスとして与えています。無名クラスは見てのとおり分かりにくいものです。ですので、次回に出てくる、ウィンドウクローズボタンを押すときにプログラムを終了するというような数行ですむようなもの以外では用いないほうがいいようです。