{page12(update:2017/09/06)}


[ prev | next | index ]

12. イベント処理 

プログラムの実行中に外からいろいろと操作するための手法として、javaのライブラリは10章で紹介したイベントモデルを採用しています。 今回はイベント処理をAppletプログラムに組み込んでみましょう

  1. 事前にイベントの発生源(イベントソース)にイベントを受けとるオブジェクト(イベントリスナ)を登録。
  2. イベントが発生するとイベントソースはイベントオブジェクトを生成しイベントリスナに 配信。
  3. イベントリスナはイベントオブジェクトからイベントの詳細情報を読み出して対応する処理を実行。

目次

目次

12.1 EventObject

イベントは多くの情報を持っています。これをイベント処理関数に構造体などのデータで渡すことにすると、イベントの種類ごとに構造体を定義しなければなりません。イベントの受け手が複数の場合は途中でデータが変更されたりするかもしれません。そこで構造体では無くオブジェクトにイベント情報を持たせて回覧する仕組みが採用されました。

下記のように、イベント情報の共通部分を抽出したEventObjectクラスと、これを継承してた個別のイベントクラスが用意されています。

また、オブジェクト内のデータはカプセル化により変更を制限できるので、勝手に中身が変更される恐れは有りません。

java.util.EventObject

イベントを汎化したクラス。

イベント発生源をメソッドgetSource()で参照できる ので、覚えておいてください。

Object getSource() //Event が最初に発生したオブジェクトを戻す

ここで紹介する赤い文字で示したイベントは以下のようなEventObjectからの継承階層の中に在ります。

目次

12.2 ActionEvent

Buttonが押されたときやTextFieldでエンターキーが押されたときjava.awt.event.ActionEventのインスタンスが生成され配信されます。

 継承元のEventObjectやAWTEventクラスで定義されたメソッドのほかに、下記のようなメソッドがあります。

String getActionCommand() アクションに関連したコマンド文字列を返します。  
int getModifiers() アクションイベントの発生中に押された修飾キーを返します。  
long getWhen() このイベントが発生した時点のタイムスタンプを返します。

このイベントはaddActionListenerを呼び出して登録したActionListenerに配信されます。

ActionListenerはインターフェイスで下記のようにイベントを受け取るメソッドが1個あるだけです。

public interface ActionListener extends EventListener
{
	void actionPerformed(ActionEvent e) //アクションが発生した
}

ボタンを1秒間隔で押してもらって、その正確さを評価するApplet

TestApplet3.java TestApplet3.html

TestApplet3.jar

イベント処理例1(まとめて受け取る)

次のプログラム例は、素直にAppletをイベントリスナーにして作ったものです。しかし、色んなところから来るアクションイベントを同じactionPerformedで受け取ることになるかもしれません。

このプログラムではイベントがどこから来たかで場合分けが必要になります。 この例ではgetSource()でイベントソースを取り出してif文で判定しています。getActionCommand()でコマンドに対応した文字列を取り出して分ける方法もあります 。Buttonからのイベントではボタンに表示された文字列が戻されます。TextFieldからのイベントでは書き込まれたテキストが戻されます。

/*TestApplet3.java*/
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;/* 注意:java.awt.*はjava.awt.event.*を含みません */

public class TestApplet3 extends Applet implements ActionListener
{
	Button button=new Button("1秒間隔で11回押してください");
	TextField textfield=new TextField("挑戦者の名前をここに");
	TextArea textarea=new TextArea();
	long data[]=new long[11];
	int counter=0;
	//
	public TestApplet3()
	{
	
	}
	public void init()
	{
		Panel panel=new Panel();
		panel.setLayout(new BorderLayout());
		panel.add(new Label("名前:"),BorderLayout.WEST);
		panel.add(textfield,BorderLayout.CENTER);
		//
		setLayout(new BorderLayout());
		add(panel, BorderLayout.NORTH);
		add(button, BorderLayout.SOUTH);
		add(textarea, BorderLayout.CENTER);

		//イベントソースのボタンやフィールドにイベントリスナーを登録
		button.addActionListener(this);
		textfield.addActionListener(this);
	}

         //インターフェイスActionListenerの実装部分
	//2箇所からイベントが送られてくるためif文で選別することが必要
	public void actionPerformed(ActionEvent ev){
		Object source=ev.getSource();//どこで発生したイベントなのかを見て分枝
		if(source==textfield){//テキストフィールドからの場合
			textarea.append(textfield.getText()+"さん 下のボタンを1秒間隔で11回押してください ");
			counter=0;
		}else if(source==button){//ボタンからの場合
			textarea.append(""+counter+" ");
			data[counter]=ev.getWhen();
			if(counter==10){
				//平均値
				textarea.append("\n"+textfield.getText()+":");
				double average=(data[10]-data[0])/1000.0/10;
				textarea.append("平均値"+average+":");
				//標準偏差
				double rr=0;
				for(int i=0;i<10;i++){
					double r=(data[i+1]-data[i])/1000.0-average;
					rr+=(r*r);
				}
				rr=Math.sqrt(rr/9);
				rr=((int)(rr*10000))/10000.0;//小数点以下4桁までに
				textarea.append("標準偏差"+rr+"\n");
				counter=0;
			}else counter++;
		}
	}
}

イベント処理例2(内部クラスを利用して)

異なる設計としてテキストフィールドやボタンなどのイベントソース自体をイベントリスナーにして 作ってみました。内部クラスの仕組みを使いますが、if文による処理の分岐はなくなります

Applet側にイベントで実行したいメソッドを用意しますが、色々なところから来るイベントを受け取って場合分けするactionPerformed( )メソッドは書きません。

イベントソースになっているボタンやテキスト自体に自分のイベントのみを受けとるactionPerformed( )メソッドを作り、このメソッドからApplet側の実行したいメソッドを呼ぶようにします。ここでApplet側のメソッドやフィールドを自由に使えるようにするために、ボタンやテキストはAppletの内部クラスにします。

 若干プログラムは長くなっていますが将来機能を拡張したときにif文を使うより楽になることを期待しました。 ソースコードへのリンクを載せておきます。

TestApplet3a.java TestApplet3a.html

TestApplet3a.jar

目次

12.3 MouseEvent

Componentをイベントソースとするイベントで、マウスに関連するイベント情報を運ぶのに使われます。

主なメソッドのみを挙げると、

static String getMouseModifiersText(int modifiers) 
  イベントの発生時に押されていた「Shift キー」や「Ctrl+Shift キー」などの
  修飾キーやマウスボタンを記述する String を返します。  
Point getPoint() 発生元のコンポーネントを基準とする、イベントの相対 x、y 座標値を返します。  
int getX() イベントが発生した位置の X 座標を発生元のコンポーネントに対する相対位置で返します。  
int getY() イベントが発生した位置の Y 座標を発生元のコンポーネントに対する相対位置で返します。  

イベントリスナーは下記のように2種類用意されています。

1)マウスの操作に関連する部分はComponentのaddMouseListenerで登録したMouseListenerに配信されます

public interface MouseListener extends EventListener
{
	void mouseClicked(MouseEvent e) //コンポーネント上でマウスクリック (押して離す) した  
	void mouseEntered(MouseEvent e) //コンポーネントにマウスが入った 
	void mouseExited(MouseEvent e) //コンポーネントからマウスが出た 
	void mousePressed(MouseEvent e) //コンポーネント上でマウスボタンが押された  
	void mouseReleased(MouseEvent e) //コンポーネント上でマウスボタンが離された
}

2)マウスの動きに関連する部分はComponentのaddMouseMotionListenerで登録したMouseMotionListenerに配信されます

public interface MouseMotionListener extends EventListener
{
	void mouseDragged(MouseEvent e) //コンポーネント上でドラッグした  
	void mouseMoved(MouseEvent e) //コンポーネント上でマウスカーソルを移動した
}

画面上の丸をマウスドラッグ可能なApplet

Test.java Test.html

Test.jar

/*Test.java*/
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

class Circle //図形のクラス
{
	int x,y,r;
	Color color=Color.black;
	//コンストラクタ
	public Circle(int x,int y,int r,Color color)
	{
		this.x=x;
		this.y=y;
		this.r=r;
		this.color=color;
	}
	//相対座表で移動
	public void move(int px,int py)
	{
		x+=px;y+=py;
	}
	//点が図形に含まれるか判定 
	public boolean contains(int px,int py)
	{
		double dx=px-x,dy=py-y;
		double rr=dx*dx+dy*dy-r*r;
		if(rr<0)return true;
		else return false;
	}
	//図形の描画
	public void paint(Graphics g)
	{
		g.setColor(color);
		g.fillOval(x-r,y-r,2*r,2*r);//円
	}
}

public class Test extends Applet implements MouseListener,MouseMotionListener
{
	Vector circles=new Vector();
	Circle baggage=null;//荷物
	int x0,y0;//荷物をつかんだ位置
	//
	//コンストラクタ
	public Test()
	{
		//リスナーの登録
		addMouseListener(this);
		addMouseMotionListener(this);
		//3個の円を用意
		circles.addElement(new Circle(100,100,30,Color.red));
		circles.addElement(new Circle(50,150,50,Color.blue));
		circles.addElement(new Circle(200,100,20,Color.green));
	}
	//円を探すメソッド
	Circle search(int px,int py)
	{ 
		//リストの後ろから探し始める。これは描画と逆向き
		int max=circles.size();
		for(int i=max-1;0<=i;i--){
			Circle c=(Circle)circles.elementAt(i);
			if(c.contains(px,py))return c;
		}
		return null;
	}
	//図形の描画
	public void paint(Graphics g)
	{
		//リストの先頭から描画。図形の重なり順に注意
		int max=circles.size();
		for(int i=0;i<max;i++){
			Circle c=(Circle)circles.elementAt(i);
			c.paint(g);
		}
	}

	/////////////////////////////////////////
	//MouseListener,MouseMotionListenerの実装
	//編集操作のイベント処理
	//

	//MouseMotionListenerの実装
	public void mouseDragged(MouseEvent e)
	{
		if(baggage==null)return;
		//荷物をドラッグ中なら荷物を移動して再描画
		int x1=e.getX();
		int y1=e.getY();
		//前回からのカーソル移動量分だけ動かす
		baggage.move(x1-x0,y1-y0);
		x0=x1; y0=y1;//今のカーソル位置を保存
		repaint();
	} 
	public void mouseMoved(MouseEvent e){}

	//MouseListenerの実装
	public void mouseClicked(MouseEvent e){}
	public void mouseEntered(MouseEvent e){}
	public void mouseExited(MouseEvent e)
	{
		baggage=null;/*Componentの外まで持っていかないようにした*/
	} 
	public void mousePressed(MouseEvent e)
	{
		//マウスボタンが押されたとき、カーソル位置に荷物があれば持ち上げる。
		x0=e.getX();
		y0=e.getY();
		baggage=search(x0,y0);
	}
	public void mouseReleased(MouseEvent e)
	{
		if(baggage==null)return;
		//マウスボタンが離されたとき、荷物があれば手放す
		baggage=null;
		repaint();
	}
}

目次

12.4 KeyEvent

キーボードのキーボタン操作のイベントです。

 Aキーを押し下げるとキープレスイベントが生成され、放すと「a」のタイプイベントとキーのリリースイベントが発生します。

キーを押したり放したりするとそのキーのキープレスやキーリリースが発生します。タイプイベントのほうは、同じAキーでもシフトキーが押されているかどうかで「a」または「A」のタイプイベントになります。

 キーイベントでは他のキーの影響を無視できません。たとえばテンキーはナムロックのON/OFFで異なるキーになります。

キーイベントを表示するApplet

KeyEventの配信を受けるKeyListenerのメソッドは下記の3個です。

void keyPressed(KeyEvent e) キーを押しているときに呼び出されます。  
void keyReleased(KeyEvent e) キーを離したときに呼び出されます。  
void keyTyped(KeyEvent e) キーをタイプすると呼び出されます。押して放したとき

これを使ってKeyEventを受け取り送られてきたイベントを文字列に変換して表示するアプレットを作ってみました。まずはいろいろと試してください。

アプレット 上部のテキストフィールドにまずカーソルを入れてください。このアプレットはこのテキストフィールドで発生したキーイベントを下のテキストエリアに表示します。表示を見ることでイベントの中のデータを知ることができます。

TestApplet4.java TestApplet4.html

TestApplet4.jar

/*TestApplet4.java*/
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

public class TestApplet4 extends Applet implements KeyListener
{
	TextField textfield=new TextField("この場所でのKeyEventを捕まえます");
	TextArea textarea=new TextArea();
	//
	public void init()
	{
		setLayout(new BorderLayout());
		//
		add(textfield, BorderLayout.NORTH);
		/*リスナ登録*/
		textfield.addKeyListener(this);
		//
		add(textarea, BorderLayout.CENTER);
	}

	/*KeyListenerの実装*/
	/*ここでは配信されたkeyEventを文字列表現にして表示するだけ*/
	public void keyPressed(KeyEvent e)
	{
		textarea.append(e.toString()+"\n");
	}
	public void keyReleased(KeyEvent e)
	{
		textarea.append(e.toString()+"\n");
	}
	public void keyTyped(KeyEvent e)
	{
		textarea.append(e.toString()+"\n");
	}
}

KeyEventに多くのデータが含まれていますから、これらを参照するメソッドが下記のように準備されています。keyCodeとかkeyCharとかなんだか分からないかもしれませんが、上のアプレットの表示を見て試しながら理解してください。

char getKeyChar() キーの文字を返します。  
int getKeyCode() キーの keyCode を返します。  
int getKeyLocation() キーの位置を返します。 
static String getKeyModifiersText(int modifiers) 「Shift キー」や「Ctrl+Shift キー」などの修飾キーを記述する String を返します。 
static String getKeyText(int keyCode) 「Home キー」、「F1 キー」、または「A キー」などの keyCode を記述する String を返します。  
boolean isActionKey() このイベントのキーが「アクション」キーであるかどうかを返します。  
String paramString() このイベントを特定するパラメータの文字列を返します。  
void setKeyChar(char keyChar) 論理的な文字を示す keyChar 値を設定します。  
void setKeyCode(int keyCode) 物理的なキーを示す keyCode 値を設定します。  

確認事項

  1. AキーのkeyCodeはシフトキーの状態で変わりませんがkeyCharはA/aと変化します。
  2. シフトキーやコントロールキーにはkeyCodeはありますがkeyCharは定義されていません。
  3. シフトキーは左右に2個ありますがkeyCodeは同じです。ただし、keyLocationで区別できます。
  4. テンキーはナムロックのON/OFFでkeyCodeも変化します。
  5. keyCodeの値はKeyEventクラスの定数として定義されている。javaのAPIを調べること。

目次

12.5 課題11(初期ファイル)

用意したソースコードP11.javaを元にして、矢印キーで上下左右に加速する円を表示するアプレットプログラムを作りなさい。HTMLのページはP11.htmlを使用してください。

レポートツールJReport.jarとP11.java、P11.htmlをZドライブ内のフォルダに置いてください。この状態で、このレポートツールを起動してP11.javaとP11.htmlをツールに設定して課題作成とレポート送信を行うこと。

※ディスクトップ内のフォルダに置くと、演習室の環境ではレポートツールがファイルを正しく見つけることができません。

下のアプレットは見本です。キー入力を下のアプレットへ送るために一度アプレットをマウスで選択してください。そのあとで上下左右の矢印キーを押してみましょう。

TestApplet5a.jar

用意した初期ファイルP11.javaのプログラムでは、
1)円が画面からはみ出さないように、円が枠の縁にぶつかると反射します。
2)移動速度が早くなりすぎないように、上限を決めて、これを超えないようにしています。
3)キー入力に反応する部分はコメントのみ

注意

1)上右の2つのキーが押された状態では斜めに加速すること

2)上キーを押して加速しているときに下キーも押されたら加速をやめ、下キーが放されたら再び加速するようにしてください。

3)上キーを押して加速しているときに右キーを押して放したら......

ヒント

左右の加速のみについて考えてみましょう。

1) イベントが教えてくれるのは、ボタンが押されたり、放されたりした変化です。しかし、加速度を決めるのはボタンが押された状態か離された状態かです。イベントは状態の変化を教えてくれるだけです。

押されているかどうかを記憶する変数を上下左右のキーごとに用意すると素直です。


右のキーが押されている状態を ax1=1 離されているときをax1=0。
左のキーが押されている状態を  ax2=1  離されているときをax2=0。
のように状態を変数に記憶し、加速度を
ax=ax1-ax2
と計算式で設定するのも一つの方法です。

2) 左右のボタンの状態と加速度の関係は
左右キーの状態と加速度 左が押されている 左が離されている
右が押されている 加速しないax=0 右に加速ax=1
右が離されている 左に加速ax=-1 加速しないax=0

ちょっと素直ではありませんが、新たな変数を作らずに、左が押されたときax=0ならax=-1にax=-1ならax=0にするといったプログラムも可能です。


[ prev | next | index ]