{page12(update:2018/01/09)}


[ prev | next | index ]

12. イベント処理 

プログラムの実行中に外からいろいろと操作するための手法として、javaのライブラリはイベントモデルを採用しています。 今回はイベント処理をプログラムに組み込む部分を説明します。

  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秒間隔で押してもらって、その正確さを評価するプログラム

TextActionEvent.java (TextActionEvent.jar

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

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

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

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

public class TestActionEvent extends Frame implements ActionListener
{
	static public void main(String args[])//テスト用
	{
		TestActionEvent frame=new TestActionEvent();
		frame.init();
		frame.setSize(400,300);
		frame.setVisible(true);
		frame.addWindowListener(frame.new Control());
	}
	class Control extends WindowAdapter{
		public void windowClosing(WindowEvent e){
			System.exit(0);
		}
	}
	Button button=new Button("1秒間隔で11回押してください");
	TextField textfield=new TextField("挑戦者の名前をここに");
	TextArea textarea=new TextArea();
	long data[]=new long[11];
	int counter=0;
	//
	public TestActionEvent()
	{
		super("ActionEventのテスト");
	}
	public void init()
	{
		Panel panel=new Panel();
		panel.setLayout(new BorderLayout());
		panel.add(new Label("名前:"),BorderLayout.WEST);
		panel.add(textfield,BorderLayout.CENTER);
		//
		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(内部クラスを利用して)

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

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

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

TestActionEvent2.java (TestActionEvent2.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) //コンポーネント上でマウスカーソルを移動した
}

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

TestMouseEvent.java (TestMouseEvent.jar)

 

import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

public class TestMouseEvent extends Frame implements MouseListener,MouseMotionListener
{
	static public void main(String args[])//テスト用
	{
		TestMouseEvent frame=new TestMouseEvent();
		frame.setSize(600,400);
		frame.setVisible(true);
		frame.addWindowListener(frame.new Control());
	}
	class Control extends WindowAdapter
	{
		public void windowClosing(WindowEvent e)
		{
			System.exit(0);
		}
	}
	Vector circles=new Vector();
	Circle baggage=null;//荷物
	int x0,y0;//荷物をつかんだ位置
	//
	//コンストラクタ
	public TestMouseEvent()
	{
		super("Mouse関連イベントのテスト");
		//リスナーの登録
		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();
	}
}

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);//円
	}
}

目次

12.4 KeyEvent

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

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

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

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

キーイベントを表示するプログラム

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

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

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

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

TestKeyEvent.java (TestKeyEvent.jar)

 

import java.awt.*;
import java.awt.event.*;

public class TestKeyEvent extends Frame implements KeyListener
{
	static public void main(String args[])//テスト用
	{
		TestKeyEvent frame=new TestKeyEvent();
		frame.setSize(800,300);
		frame.setVisible(true);
		frame.addWindowListener(frame.new Control());
	}
	class Control extends WindowAdapter
	{
		public void windowClosing(WindowEvent e)
		{
			System.exit(0);
		}
	}
	TextField textfield=new TextField("この場所でのKeyEventを捕まえます");
	TextArea textarea=new TextArea();
	//
	public TestKeyEvent()
	{
		super("KeyEventのテスト");
		//
		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を元にして、下記の動作確認のように矢印キーで上下左右に正しく加速する円を表示するプログラムを完成させなさい。

キー入力に反応しないときは,ウィンドウを一度マウスでクリックしてください。そのあとで上下左右の矢印キーを押してみましょう。

 

用意した初期ファイルP11.javaのプログラムでは、
1)円が画面からはみ出さないように、円が枠の縁にぶつかると反射します。
2)移動速度が早くなりすぎないように、上限を決めて、これを超えないようにしています。
3)KeyEventを受け取る部分は不完全です。
必要なフィールド(変数)の定義やメソッド(関数)の中身の変更を行ってください。

動作確認

1)上下左右のキーを1個だけ押している状態では上下左右に円が加速する。

2)上キーを押した状態で下キーを押すと,上に加速していた状態から加速しない状態に変わる。

3)上下や左右のキーを同時に押している状態では上下や左右に加速しない。

4)上下の2つを押した状態から上のキーだけを離すと,下に加速する。左右も同様。

5)左と上を同時に押した状態では左上に加速する。左下,右上,右下も同様。

ヒント

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

左右のボタンの状態と加速度の関係は

左右キーの状態と加速度 左が押されている 左が離されている
右が押されている 加速しないax=0 右に加速ax=1
右が離されている 左に加速ax=-1 加速しないax=0

イベントは状態変化を知らせてくれるだけ

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

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

例えば,左右のキーに対して
変数ax1に右のキーが押されている状態を記憶し 押されているときax1=1 離されているときをax1=0。
変数ax2に左のキーが押されている状態を記憶し 押されているときax2=1 離されているときをax2=0。
のようにすれば、加速度axを
ax=ax1-ax2
と計算式で知ることができます。


新たな変数を作らずに、左が押されたときax=0ならax=-1にax=-1ならax=0にするといったプログラムも可能ですが,複雑なif文を書くことになりかねません。お勧めできません。


[ prev | next | index ]