WisdomSoft - for your serial experiences.

4.3 マウスイベント

コンポーネント上でマウスボタンが押されるなど、マウスに関連する入力をプログラムで受けて処理する方法を解説します。

4.3.1 マウスリスナ

マウスカーソルがコンポーネント上にある状態でマウスボタンが押されたり、離されるなど、マウスからの入力を処理することができれば、私たちが開発したコンポーネントを用いてユーザーと対話させることができるようになります。これを実現するには AWT や Swing で用いられているイベント処理方法を学習しなければなりません。

多くのシステムでは、このような GUI 上で発生したユーザーからの入力イベントは、コールバック関数を使っています。つまり、イベントが発生したときに呼び出してほしいルーチンをあらかじめシステムに登録して、ユーザーからの入力が発生したときに、そのルーチンがシステムから呼び出されるという仕組みです。このような仕組みを使えば、古い BASIC システムで採用されていた常にユーザーからの入力をループで監視する必要はなくなります。

Java のイベントモデルも同様で、コンポーネントにイベントが発生したときに呼び出してほしいメソッドを登録しておきます。Java では C 言語における関数へのポインタ(参照)というものは作れませんが、インタフェースを使うことでメソッドを参照することができます。マウスの入力を処理するメソッドを宣言するインタフェースを実装し、これをコンポーネントに登録することで、アプリケーションはコンポーネントへの入力を感知することができるのです。AWT や Swing の世界では、イベントが発生したときに呼び出されるインタフェースのことをリスナと呼んでいます。

Component クラスには、マウスの入力を処理するマウスリスナと呼ばれるインタフェースを addMouseListener() メソッドを使って登録することができます。イベントを処理する必要がなくなり登録したインタフェースの呼び出しを解除したい場合は removeMouseListener() メソッドを使います。現在コンポーネントに登録されているマウスリスナを知りたければ getMouseListeners() メソッドから得ることができます。

Component クラス addMouseListener() メソッド
public void addMouseListener(MouseListener l)
Component クラス removeMouseListener() メソッド
public void removeMouseListener(MouseListener l)
Component クラス getMouseListeners() メソッド
public MouseListener[] getMouseListeners()

これらのメソッドで使われる java.awt.event.MouseListener インタフェースがマウス入力を受け取るマウスリスナです。java.awt.event パッケージは AWT で用いられるリスナやイベント情報をパッケージ化するクラスが宣言されています。 MouseListener インタフェースが宣言するメソッドは表1のようなものがあります。

表1 MouseListener インタフェースのメソッド
メソッド 解説
void mouseClicked(MouseEvent e) コンポーネント上でマウスボタンをクリックしたときに呼び出されます。
void mouseEntered(MouseEvent e) コンポーネントにマウスが入ると呼び出されます。
void mouseExited(MouseEvent e) コンポーネントからマウスが出ると呼び出されます。
void mousePressed(MouseEvent e) コンポーネント上でマウスボタンを押すと呼び出されます。
void mouseReleased(MouseEvent e) コンポーネント上でマウスボタンを離すと呼び出されます。

コンポーネントにマウスカーソルが入ったり、コンポーネント上でマウスのボタンを押したり、離したりするとマウスリスナの対応するメソッドがコンポーネントから呼び出されるという仕組みになっています。例えば、マウスボタンを押すとコンポーネントに登録されている全てのマウスリスナの mousePressed() メソッドが呼び出されるのです。

MouseListener インタフェースが宣言するメソッドは MouseEvent オブジェクトを受け取ります。java.awt.event.MouseEvent クラスは、イベントが発生したときのマウスカーソルの座標など、イベントの情報を提供するためのクラスです。

java.awt.event.MouseListener インタフェース
java.lang.Object
  |
  +--java.util.EventObject
        |
        +--java.awt.AWTEvent
              |
              +--java.awt.event.ComponentEvent
                    |
                    +--java.awt.event.InputEvent
                          |
                          +--java.awt.event.MouseEvent
public class MouseEvent extends InputEvent

MouseEvent 以外にも、イベントに応じたイベント情報を提供するクラスがいくつも定義されています。コンポーネントに関連したイベントは ComponentEvent クラスから派生し、マウスやキーボードなど、入力処理に関連するイベントは InputEvent クラスから派生しています。

ComponentEvent クラスはイベントが発生したコンポーネントを提供する getComponent() メソッドと、イベントの内容を文字列で表す paramString() メソッドを提供しています。paramString() メソッドはイベントログの作成やデバッグに貢献するでしょう。

ComponentEvent クラス getComponent() メソッド
public Component getComponent()
ComponentEvent クラス paramString() メソッド
public String paramString()

InputEvent クラスはイベント発生時に Ctrl、Alt、Shift キーなどのキーが押されていたかどうかを提供します。

そして、MouseEvent クラスではより具体的な情報となるカーソルの座標などを提供しています。getX() や getY() メソッドからカーソルの座標を取得することができるほか、クリック数やマウスボタンの情報を取得することができます。

ここで重要な注意点があります。Java アプレットやアプリケーションがマウスを処理するときは 1 ボタンマウスを想定しなければなりません。古い世代の Macintosh など、一部のマウスは右クリックがありません。Java 1.4 からは 3 ボタンマウスの各ボタンを getButton() メソッドで認識することができますが、右クリックがなくても正常に動作するよう工夫が必要です。

表2 MouseEvent クラスの主要なメソッド
メソッド 解説
int getButton() 状態が変更されたマウスボタンがある場合、そのマウスボタンを返します。
int getClickCount() このイベントに関連したマウスクリック数を返します。
static String getMouseModifiersText(int modifiers) 修飾キーを表す文字列を返します。
Point getPoint() イベントが発生した座標を発生元のコンポーネント座標で返します。
int getX() イベントが発生した位置の X 座標を発生元のコンポーネント座標で返します。
int getY() イベントが発生した位置の Y 座標を発生元のコンポーネント座標で返します。

ボタンをあらわす定数など、イベントに関連する値は各イベントクラスの static final int フィールドとして定義されています。

これで、イベントリスナを実装し、リスナをコンポーネントに登録し、イベント発生時にイベントオブジェクトを受け取ってイベントに関連する情報を取得するという、イベント処理に必要な流れがわかりました。AWT や Swing では命名方法が統一されているのでリスナを登録するメソッドは add*Listener()、イベントリスナとなるインタフェースは *Listener、イベントクラスは *Event という名前が付けられています。キーボードのイベントや、そのためのイベントでも、これらの関係は変わりません。

コード1
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

//<applet code="Test.class" width="400" height= "400"></applet>

public class Test extends Applet implements MouseListener {
	private String param = "";

//MouseListener の実装///////
	public void mouseClicked(MouseEvent e) { 
		param = e.paramString();
		repaint();
	}
	public void mousePressed(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
	public void mouseReleased(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
	public void mouseEntered(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
	public void mouseExited(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
////////////////////////////

	public void init() {
		addMouseListener(this); //リスナをアプレットに登録
	}

	public void paint(Graphics g) {
		g.drawString(param , 0 , g.getFontMetrics().getAscent());
	}
}
実行結果
コード1 実行結果

コード1では、Test クラスに MouseListener インタフェースを実装させています。このクラスのオブジェクトはマウスリスナと指定登録することができるため、init() メソッドでアプレットが実行されるときに、このアプレット自身に自らをマウスリスナとして登録しています。アプレットにマウスの入力が行われたとき、MouseListener インタフェースのメソッドが呼び出されます。

Test クラスで実装したマウスリスナのメソッドでは、MouseEvent クラスの paramString() メソッドを呼び出して、イベントの情報を文字列として取得しています。これを paint() メソッドで描画しているため、イベントの種類やマウスの座標などを知ることができます。アプレット上でマウスボタンをクリックするなどして、イベントの発生メカニズムを試してみてください。

4.3.2 アダプタクラス

コンポーネントにインタフェースとしてイベント処理用のメソッドを登録するというメカニズムは、イベントを無限に拡張することができるため理想的な設計だと言えますが、MouseListener インタフェースのように多くのメソッドを宣言するインタフェースを実装するのは非常に面倒です。マウスのクリックだけを処理したい場合でも、他のメソッドを空実装しなければなりません。

このような面倒を解消してくれる役割として、アダプタクラスと呼ばれるクラスが用意されています。アダプタクラスは単純にリスナを空実装しているクラスです。インタフェースを実装していますが、メソッドは何もせずに制御を返すだけです。そこで、アダプタクラスを継承して目的のメソッドだけをオーバーライドすれば、最小限のコードで目的のイベントを処理することができるのです。

マウスリスナを実装するアダプタクラスは java.awt.event.MouseAdapter クラスです。 特定のマウス入力だけを処理したければ、このクラスを継承して目的のメソッドをオーバーライドします。MouseAdapter クラス自体は抽象クラスなのでインスタンス化はできません。

java.awt.event.MouseAdapter クラス
java.lang.Object
  |
  +--java.awt.event.MouseAdapter
public abstract class MouseAdapter extends Object implements MouseListener

アダプタクラスの目的はリスナの実装を簡易化することなので、インタフェースで宣言されているメソッドを空実装している以外に役割はありません。

内部クラスを用いれば、アダプタクラスをスマートに実装してリスナを登録することができます。大規模アプリケーションやライブラリの開発以外ではなかなか内部クラスを用いる機会は少ないのですが、イベントリスナの登録は内部クラスの実用方法の代表的な例と言えます。

コード2
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

//<applet code="Test.class" width="400" height= "400"></applet>

public class Test extends Applet {
	private Point pt = new Point(0 , 0);

	public void init() {
		addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				pt = e.getPoint();
				repaint();
			}
		});
	}

	public void paint(Graphics g) {
		g.drawString("Kitty on your lap" , pt.x , pt.y);
	}
}
実行結果
コード2 実行結果

コード2は MouseAdapter クラスを利用して、マウスクリックだけを処理するリスナをピンポイントで作成しています。MouseAdapter クラスは MouseListener インタフェースを空実装しているため、目的のメソッドをオーバーライドするだけでイベントを処理できます。このプログラムでは、MouseAdapter クラスを匿名内部クラスに継承させることでインスタンスを生成してます。内部クラスは囲んでいるクラスのインスタンスと関連付けられているため、Test クラスとは別のクラスでありながら、暗黙的に Test クラスの pt フィールドや repaint() メソッドにアクセスしています。

Test クラスはすでに Applet クラスを継承しているため MouseAdapter クラスを継承することはできません。かといって、Test クラスの外部に新しいクラスとして宣言した場合は、Test クラスとの通信手段を設ける必要があるため処理が冗長になります。そのため、アダプタクラスを用いた新しいクラスを宣言する手段としては内部クラスが最適なのです。

4.3.3 マウスの移動を処理する

マウスカーソルがコンポーネント上で移動したイベントを処理するには MouseListener ではなく java.awt.event.MouseMotionListener インタフェースを登録しなければなりません。MouseMotionListener を登録するには Component クラスの addMouseMotionListener() メソッドを、解除するには removeMouseMotionListener() メソッドを、そして登録されているリスナを取得するには getMouseMotionListeners() メソッドを使います。

Component クラス addMouseMotionListener() メソッド
public void addMouseMotionListener(MouseMotionListener l)
Component クラス removeMouseMotionListener() メソッド
public void removeMouseMotionListener(MouseMotionListener l)
Component クラス getMouseMotionListeners() メソッド
public MouseMotionListener[] getMouseMotionListeners()

基本的な考え方は、MouseListener インタフェースの登録と同じです。MouseMotionListener インタフェースを実装して、これを登録すれば、マウスカーソルがコンポーネント上を通過したときにメソッドが呼び出されます。MouseMotionListener インタフェースが宣言しているメソッドは、マウスのボタンが押された状態で移動したときに呼び出される mouseDragged() メソッドと、ボタンが押されていない状態で移動したときに呼び出される mouseMove() メソッドの 2 つです。 

MouseMotionListener  インタフェース mouseDragged() メソッド
public void mouseDragged(MouseEvent e)
MouseMotionListener  インタフェース mouseMoved() メソッド
public void mouseMoved(MouseEvent e)

MouseMotionListener に対応するアダプタクラスは java.awt.event.MouseMotionAdapter クラスも存在します。このクラスも MouseAdapter クラスと同じように、MouseMotionListener を空のメソッドで実装する抽象クラスです。MouseMotionListener が宣言するいずれかのメソッドだけを実装したい場合にアダプタクラスを利用すると良いでしょう。

コード3
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

//<applet code="Test.class" width="400" height= "400"></applet>

public class Test extends Applet implements MouseMotionListener {
	private String param = "";

//MouseMotionListener の実装//////
	public void mouseDragged(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
	public void mouseMoved(MouseEvent e) {
		param = e.paramString();
		repaint();
	}
////////////////////////////////

	public void init() { addMouseMotionListener(this); }
	public void paint(Graphics g) {
		g.drawString(param , 0 , g.getFontMetrics().getAscent());
	}
}
実行結果
コード3 実行結果

コード3は、マウスの移動イベントが発生すると、そのイベント情報を表示するアプレットです。このアプレットは MouseMotionListener インタフェースを実装し、マウスカーソルがアプレット上を移動してメソッドが呼び出されると MouseEvent オブジェクトの paramString() メソッドを保存して表示しています。処理対象のイベントが変わっただけで、仕組みや考え方は MouseListener のそれと同じです。