第7週:インタラクティブなアプレット

これまでつくってきたアプレットは、文字や線描や画像を表示するだけで、それ以上のことはなにもできませんでした。今回は、新たに「ユーザーからの入力を受け付ける仕組み」を組み込むことで、ユーザーとアプレットの間のインタラクティブなやりとりを可能にします。

7.1 イベント処理とインタラクティブなプログラム
7.2 マウス・クリックに反応するアプレット
7.3 「リスナー」という考え方について
7.4 マウス・ドラッグに反応するアプレット

7.1 イベント処理とインタラクティブなプログラム

たとえばマウスのクリックやキーボードからの入力に応じて処理を行うといったインタラクティブな動作は、コンピュータの特徴のひとつです。これまで作成してきたアプレットは、一方的に文字や線描や画像を表示するだけで、インタラクティブな仕組みを持っていませんでした。

インタラクティブなアプレットを作成するためには、イベント処理という考え方を理解し、それを実現するプログラムを作成しなければなりません。詳しくは例題を作成しながら学習するとして、まずはあらかじめ次のような考え方を了解しておいてください。

以上3つの概念(イベント、イベントソース、リスナー)を軸に、Javaにおけるイベント処理プログラムの実現の仕方を学習してゆきます。

7.2 マウス・クリックに反応するプログラム

まずはじめに、ユーザーがマウスをクリックするとその位置を中心に小さな円(半径10ピクセル)を表示するアプレットClickle.javaを例に、イベント処理プログラムの基本について詳しく学習しましょう。

→ このプログラムの実行結果

このアプレットを実現するには次のようなプログラムが必要です。

/**
 * マウスをクリックした場所に半径10ピクセルの円を描く
 **/
 
import java.awt.Graphics;
import java.awt.event.*;	// イベント関連のパッケージ
import java.applet.Applet; 
public class Clickle extends Applet implements MouseListener{
	
	int x, y;	// マウスをクリックした位置の座標を記憶する変数
	// initメソッド:アプレットの初期化を行う	
	public void init(){
	
		addMouseListener(this);	// アプレット自身をマウスイベントのリスナーとして指定する
	}

	// paintメソッド:アプレット本体を表示する		
	public void paint(Graphics g){
		g.drawOval( x-10, y-10, 20, 20 );	// マウスクリックの座標を中心に半径10の円を描く
	}
	
	
	// mousePressedメソッド:MouseEventのリスナーメソッドのひとつ。
	// マウスが押されたときに呼び出され、{ }内の処理が実行される
	public void mousePressed( MouseEvent e ){
	
		x = e.getX();	// マウスがクリックされた位置のx座標を取得する
		y = e.getY();	// マウスがクリックされた位置のy座標を取得する
		repaint();		// アプレットの再描画を指示する
		
	}
	
	
	// 以下はMouseEventに関連する、その他のリスナーメソッドたち
	// すべてMouseListenerに登録されている
	// このプログラムの動作には全く関係ないが、Javaの文法上書いておかなければならない
	public void mouseReleased(MouseEvent e){ ; }
	public void mouseClicked(MouseEvent e){ ; }
	public void mouseEntered(MouseEvent e){ ; }
	public void mouseExited(MouseEvent e){ ; }
	
}

このプログラムを参考にしながら、Javaにおけるイベント処理の実現方法の基本を理解しましょう。

まずはじめに、Import文に注目します。

import java.awt.event.*; // イベント関連のパッケージ

イベント処理をおこなう際に利用すべきクラス群は、すべてjava.awt.event以下の階層にあります。「*」の記号(アスタリスク)は、event以下の階層にあるクラス群すべてを一括してimportするという意味があります。イベント関連のクラスはたくさんあるので、このようにしておくと便利です。

つぎに、クラス名の宣言に新しい記述が加わっています。

public class Clickle extends Applet implements MouseListener

ここで「implements MouseListener」という部分に注目してください。これは、このアプレット(Clickle)自身がイベントのリスナーであることを指定するための記述です。「implements 〜」は、「〜クラスに定義されるすべてのメソッドをこのクラス(この場合ではアプレット自身、すなわちClickle)に実装して利用する」ということを意味する記述です。そして、ここでimplementsによって指定されるクラス「MouseListener」は、マウスの動作によって発生するイベントを感知するリスナーの、必ず持っていなければならないメソッド群があらかじめ定義されたクラスです注1。イベント処理の仕組みを持ったアプレットの場合、一般的にはアプレット自身がイベントのリスナーになります。このアプレット自身がどのような種類のイベントに対応するリスナーになる予定なのかをあらかじめ明記するのが、この「implements MouseListener」という記述の役割です。

クラス名を宣言したとで、プログラムの中身の最初の記述は次のようなものです。

int x, y; // マウスをクリックした位置の座標を記憶する変数

これは、このアプレットが「ユーザーがマウスをクリックした位置の座標」という情報をpaintメソッド及びその後に出てくるmousePressedメソッド両方で利用する必要があることから、両メソッドのスコープの外側で宣言したものです。

二つの変数の宣言の次には、initメソッドの定義があります。前回の内容をおさらいすると、このinitメソッドはアプレットがブラウザに読み込まれる際、最初に1回だけ呼び出されるメソッドです。従ってこのメソッドの中身としては、アプレットの初期化動作として必要な処理を記述しておきます。今回の場合は以下のようになってます。

addMouseListener(this); // アプレット自身をマウスイベントのリスナーとして指定する

addMouseListener()」というメソッドが呼び出され、実引数として「this」というものがわたされています。このメソッドは実引数として受け取ったものをマウス関連のイベントのリスナー(MouseListener)として加える働きがあります。では、イベントのリスナーとして指定されているthisとは一体なんでしょうか。これはJava独自の記述法で、あるクラスの定義中に登場するthisはそのクラス自身を指示することになっています。従ってこの場合ではClickle自体を表しています。つまり、「これ(=Clickle)自体をマウスのリスナーに加えます」というのが「addMouseListener(this);」の意味です。

この記述に関しては、もう一つのことを確認しておかなければなりません。このマウスのリスナーを指定するaddMouseListener()というメソッドは、いったい何のクラスの持っているメソッドなのでしょうか。メソッド名の直前に「変数名+.(ピリオド)」が無いので、これはClickleの親クラスであるAppletクラスに定義されているメソッドと考えられます。ここでリスナーの指定に関して次の原則を理解してください。

すなわち、マウスのクリックはアプレット本体の上で行われるので、この場合にイベントソースとなるのはアプレット自身です。Appletクラスは、個々のアプレットがイベントの発生源(イベントソース)となる可能性があるために、あらかじめリスナーを指定するための「add・・・Listener()」というメソッドをいくつか用意しています注2

話を整理すると、このアプレットの場合、イベントソースとイベントのリスナーは、共にこのアプレット自身になるわけです。自分の上で起こったイベントを自分で聞き取るわけですね。

さて、その次のpaintメソッドでは、先に宣言した変数x,yを利用して半径10の円を描くdrawOvalメソッドが記述されています。drawOvalの引数の意味が分からない人は、第5週のページを参照してください。

さらにその次には、次のようなメソッドが定義されています。

// mousePressedメソッド:MouseEventのリスナーメソッドのひとつ。
// マウスが押されたときに呼び出され、{ }内の処理が実行される
public void mousePressed( MouseEvent e ){
	
	x = e.getX();	// マウスがクリックされた位置のx座標を取得する
	y = e.getY();	// マウスがクリックされた位置のy座標を取得する
	repaint();		// アプレットの再描画を指示する
		
}

このmousePressedメソッドは、クラス名の宣言の際にimplementsしたMouseListenerクラスの定義するメソッドで、マウスのボタンが押されたときに呼び出されるようになっています。従ってこの中にマウスが押されたときに実行してほしい処理を書き込んで、アプレット自身のメソッドとして再定義しておくわけです。その意味ではimplementsという仕組みは継承(extends)と少々似ています。

このmousePressedメソッドは引数として必ずMouseEvent型の変数をとることに決まっています。このケースでは変数名として「e」と書かれています。この「MouseEvent型の変数」というのは、イベントソースからイベント発生と同時に送られてくる、イベントそのものに関するデータです。つまりこれがイベントの実体なのです。マウスに関連するイベントはすべてMouseEvent型で定義される変数ですが、これから先いくつかの他の種類のイベントも同じ原理(あるクラスの型によって定義される変数)で表現されることになります。

また、イベントそのものを引数として受け取るmousePressedメソッドのようなメソッドのことを一般にリスナーメソッドと呼びます。あるクラスがイベントのリスナーになるためには、イベントの種類に応じて必要となるリスナーメソッドをすべて実装していなければなりません。そのためには、リスナーメソッドをあらかじめ定義する特定のリスナークラス注3(たとえばMouseListener)をimplementsして、そのリスナークラスが持っているすべてのリスナーメソッドを自分のメソッドとして定義し、必要な処理の記述をしなければならないのです。

このmousePressedの中に書かれている処理について見てみましょう。処理はマウスクリックの起きた場所のx座標とy座標の取得と、アプレットの再描画、というふうになっています。x座標及びy座標の取得は代入文によって行われています。マウスイベントのデータeを通して、MouseEventクラスに定義されるgetX()及びgetY()というメソッドを実行し、その結果を左辺の変数x及びyで受け取るという手順になっています。getXメソッド及びgetYメソッドは、マウスイベントが発生した位置のx座標及びy座標を整数値で返すメソッドです。その後のrepaint()メソッドは先週学習したとおりで、再描画を実行するAppletクラスのメソッドです。単にマウスが押された場所の座標が分かっただけでは、円は描画されません。従って、repaintを用いて強制的に再描画を指示するわけです。

このプログラムの最後には次の記述があります。

// 以下はMouseEventに関連する、その他のリスナーメソッドたち
// すべてMouseListenerに登録されている
// このプログラムの動作には全く関係ないが、Javaの文法上書いておかなければならない
public void mouseReleased(MouseEvent e){ ; }
public void mouseClicked(MouseEvent e){ ; }
public void mouseEntered(MouseEvent e){ ; }
public void mouseExited(MouseEvent e){ ; }

これらはすべて、mousePressed以外にMouseListenerに定義されているメソッドです。あるリスナークラスをimplementsする場合は、そのクラスが定義するすべてのメソッドを実装しなければならないという約束があります。ただし今回mouseReleasedなどのメソッドではなにも処理する必要はありません。そういった場合は「(→なにもしない特殊な命令文)」だけを書き込んでおきます。

というわけで、以上でこのアプレット(Clickle)のプログラムの全貌が解明されたわけです。このプログラムの内容が理解できたら、次の練習問題にチャレンジしてみてください。

練習問題 7-1

・マウスをクリックした位置に画像を表示するアプレットを作成しなさい

→ このプログラムの実行結果

この場合、表示されるのが画像という点を除いて、プログラムの仕組みは先ほどのClickle.javaと全く変わりません。

7.3 「リスナー」という考え方について

Javaにおけるイベント処理のプログラムの基本は先ほどのプログラム例にすべて含まれています。ここでは概念を中心に、大事なポイントをもう一度まとめておきます。

というわけで、イベントの種類、それに対応するリスナークラス、そのリスナークラスが持っているリスナーメソッドの一覧を、以下に表の形でまとめておきました。以降の学習の参考にしてください。

イベントの種類 リスナークラス リスナーメソッド
MouseEvent MouseListener

mouseClicked(MouseEvent e)

mouseEntered(MouseEvent e)

mouseExited(MouseEvent e)

mousePressed(MouseEvent e)

mouseReleased(MouseEvent e)

同上 MouseMotionListener

mouseDragged(MouseEvent e)

mouseMoved(MouseEvent e)

KeyEvent KeyListener

keyPressed(KeyEvent e)

keyReleased(KeyEvent e)

keyTyped(KeyEvent e)

7.4 マウス・ドラッグに反応するアプレット

今度はマウス・ドラッグ(ボタンを押したままマウスを移動しボタンを放す、という一連の動作)に反応するアプレットを作成します。画像の上にマウスのポインタ(矢印)を乗せたままドラッグすると、ポインタの移動にあわせて画像も移動するアプレットを作成しましょう。

まず最初に今回必要となるリスナーメソッドをリストアップします。そのためにはアプレット上でのマウスの動作を分析してみる必要があります。今回は、

という動きがあります。この動きの間処理を行うには、次のメソッドが必要となります。(→ )は、そのリスナーメソッドが登録されているリスナークラスです。

つぎに、それらのリスナーメソッドが呼び出される際にそれぞれどのような処理を割り振るのかを考えます。今回は次のようになります。

「マウスポインタが画像の上にあるか否か」という判定を行うのは、マウスポインタが画像の上にない状態でドラッグされる場合があり得るからです。その場合、画像の位置が変更されてはならないため、いちいちポインタの位置を確かめるのです。

それでは、プログラム自体は一体どのようになるのでしょうか。以下にそのプログラムを示します。ここまでの解説を踏まえて読解してください。

/**
 * マウス・ドラッグの移動軌跡上に画像を表示するアプレット
 **/
 
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.*;	// イベント関連のパッケージ
import java.applet.Applet; 

public class DragPic extends Applet implements MouseListener, MouseMotionListener{
	
	int px, py;	// 画像の表示位置の座標を記憶する変数
	Image img;	// 画像を記憶する変数
	boolean flag;	// マウスのポインタが画像の外側にあるか内側にあるかを判定する変数
	
	// initメソッド:アプレットの初期化を行う	
	public void init(){
	
		px = 250; py = 250;	// 画像の最初の表示位置の座標を決めておく(アプレットの中央にくる)
		img = getImage( getDocumentBase(), "hirame2.gif" );	// 画像を読み込む
		flag = false; 	// 最初はマウスのポインタが画像の外側にあることにしておく
		addMouseListener(this);	// アプレット自身をマウスイベントのリスナーとして指定する
		addMouseMotionListener(this);	// さらに、アプレット自身をマウスモーションイベントのリスナーとして指定する
	}

	// paintメソッド:アプレット本体を表示する		
	public void paint(Graphics g){
		g.drawImage( img, px, py, this );	// マウス・ドラッグの移動軌跡上に画像を表示する
	}
	
	
	// mousePressedメソッド:MouseEventのリスナーメソッドのひとつ
	// マウスが押されたときに呼び出され、{ }内の処理が実行される
	public void mousePressed( MouseEvent e ){
		
		// マウスが押された瞬間に、マウス・ポインタの位置が画像の内側か外側かを判定する
		// 内側にあれば、flagの値をtureに変えて再描画を指示する
		if( px<=e.getX() && e.getX()<=px+100 && py<=e.getY() && e.getY()<=py+100 ){
			flag = true;	// flagをtrueにする。
					// 現在マウス・ポインタが画像の内側にあることを意味する		
			repaint();		// アプレットの再描画を指示する
		}
		
	}
	
	// mouseReleasedメソッド:MouseEventのリスナーメソッドのひとつ
	// 押されていたマウス(のボタン)が放されたときに呼び出され、{ }内の処理が実行される
	public void mouseReleased(MouseEvent e){
	
		flag = false;	//マウスが放された瞬間に、flagの値をfalseに戻す
	 }
	 
	 
	 // mouseDraggedメソッド:MouseEventのリスナーメソッドのひとつ
	 // マウスがドラッグされている間中呼び出され続け、{ }内の処理が繰り返される
	public void mouseDragged(MouseEvent e){
	
		// マウスのポインタが画像の内側にある場合のみ、ドラッグされて移動する
		// マウスポインタの座標を画像の表示位置として繰り返し読み、そのたびに再描画をする
		if(flag==true){
			px = e.getX(); py = e.getY();	// マウス・ポインタの位置を画像の表示位置として読み込む
			repaint();	// アプレットの再描画を指示する
		}
		
	}
	
	// 以下はMouseEventに関連する、その他のリスナーメソッドたち
	// すべてMouseListenerに登録されている
	// このプログラムの動作には全く関係ないが、Javaの文法上書いておかなければならない
	public void mouseClicked(MouseEvent e){ ; }
	public void mouseEntered(MouseEvent e){ ; }
	public void mouseExited(MouseEvent e){ ; }
	
	// 以下は同じくMouseEventに関連する、今回は使われなかったリスナーメソッド
	// これはMouseMotionListenerに登録されている
	public void mouseMoved(MouseEvent e){ ; }
	
}

→ このプログラムの実行結果

まずはじめに、MouseListenerとMouseMotionListenerを同時にimplementsしてることに注意してください。「,(カンマ)」で区切ることで複数のリスナークラスを同時にimplementsすることができます。これはmousePressed及びmouseReleased(MouseListenerのメソッド)とmouseDragged(MouseMotionListenerのメソッド)を同時に使う必要があるからです。

変数の宣言の中では「boolean flag;」に注目してください。「boolean型の変数flagを用意する」意味です。boolean型は、truefalseという二つの値のみを持つデータ型です。これはマウスポインタの位置の二つの状態(画像の上(内側)にあるか否か)を記憶するために利用します。

initメソッドの中で特に注意が必要なのは、先ほどのflagの値の初期値をfalseとして与えていること(つまり普段ポインタは画像の上にはない)と、二つのリスナー指定の記述が行われていること(addMouseListenerとaddMouseMotionListener)です。リスナー指定は、implementsするそれぞれのリスナークラスに応じて行われます。

paintメソッドは、その都度のpx(x座標)py(y座標)の値に応じて画像を表示するようになっています。

mousePressedメソッドにおいては、マウスポインタの位置を判定して画像の内側(つまり画像の上)にあればflagをtrueに変更するという処理が行われています。先ほどまとめておいたmousePressedの処理内容を実際にプログラムにするとこのようになるのです。if文の条件式に注意してください。

if( px<=e.getX() && e.getX()<=px+100 && py<=e.getY() && e.getY()<=py+100 )

&&」は「かつ」の記号です。すなわちここでは4つの条件が同時に成り立つときに真であるという判定が下されます。e.getX()やe.getY()がそのまま記述されているのは、それらのメソッドの実行結果を判定の対象にするという意味です。すなわち一つめの条件は「変数pxの値がe.getX()の実行結果の値と等しいか小さいとき」を表しています。この場合、画像の大きさは100ピクセル×100ピクセルです。現在の画像の表示位置の左上隅を(px, py)、右下隅の座標を(px+100, py+100)とし、その範囲にポインタの現在位置があるかどうかを調べているのです。

mouseReleasedメソッドは、ドラッグが終了したときに呼び出されます。flagをfalseに直すことにより、画像の移動が終わりポインタが画像の上にない(外側にある)ことを知らせます。

最後のmouseDraggedメソッドでは、if文の条件式内でflagの値を参照し、trueならばマウスポインタの座標を取得して再描画をします。このメソッドはドラッグされている間中繰り返し呼び出されるので、事実上、この「ポインタの座標を取得→再描画」を繰り返すことになります。その結果、ドラッグに応じて画像が移動するように見えるわけです。

ということで、上記のプログラムを応用した練習問題にチャレンジしてみてください。

練習問題 7-2

・二つの画像を用意し、その片方を動かない「ターゲット」としてアプレットの右下隅に表示します。さらにもう片方をマウスドラッグに応じて動くものとします。ターゲット上に画像をドラッグして、重ねてドロップすると、その画像がアプレット上から消えてしまうアプレットを作成しなさい。

→ このプログラムの実行結果

ヒント→ 画像がドラッグ&ドロップされた際に、その位置がターゲットの上(内側)であるか否かを判定する必要があります。ドロップされる、とはすなわちドラッグされる間押されていたボタンがリリースされる時のことです。ここから、mouseReleasedメソッドの処理として、ターゲット画像の上にあるか否かを判定すればよいわけです。判定が真であれば、画像を画面上から消す処理を実行します。画像を画面上から消してしまうというのは、たとえば( -100, -100)といった画面上にはあり得ない位置に、画像の表示位置を更新することで実現します。


>> 目次ページへ