{page12(update:2018/01/09)}
プログラムの実行中に外からいろいろと操作するための手法として、javaのライブラリはイベントモデルを採用しています。 今回はイベント処理をプログラムに組み込む部分を説明します。
- 事前にイベントの発生源(イベントソース)にイベントを受けとるオブジェクト(イベントリスナ)を登録。
- イベントが発生するとイベントソースはイベントオブジェクトを生成しイベントリスナに 配信。
- イベントリスナはイベントオブジェクトからイベントの詳細情報を読み出して対応する処理を実行。
目次
[目次]
イベントは多くの情報を持っています。これをイベント処理関数に構造体などのデータで渡すことにすると、イベントの種類ごとに構造体を定義しなければなりません。イベントの受け手が複数の場合は途中でデータが変更されたりするかもしれません。そこで構造体では無くオブジェクトにイベント情報を持たせて回覧する仕組みが採用されました。
下記のように、イベント情報の共通部分を抽出したEventObjectクラスと、これを継承してた個別のイベントクラスが用意されています。
また、オブジェクト内のデータはカプセル化により変更を制限できるので、勝手に中身が変更される恐れは有りません。
イベントを汎化したクラス。
イベント発生源をメソッドgetSource()
で参照できる
ので、覚えておいてください。
Object
getSource()
//Event が最初に発生したオブジェクトを戻す
ここで紹介する赤い文字で示したイベントは以下のようなEventObjectからの継承階層の中に在ります。
[目次]java.util.EventObject (implements java.io.Serializable) を継承するクラスのうちでAWTEventの部分
- java.awt.AWTEvent (※AWT:Abstract Window Toolkit)
- java.awt.event.ActionEvent
- java.awt.event.AdjustmentEvent
- java.awt.event.ComponentEvent
- java.awt.event.ContainerEvent
- java.awt.event.FocusEvent
- java.awt.event.InputEvent
- java.awt.event.KeyEvent
- java.awt.event.MouseEvent
- java.awt.event.MouseWheelEvent
- java.awt.event.PaintEvent
- java.awt.event.WindowEvent
- java.awt.event.HierarchyEvent
- java.awt.event.InputMethodEvent
- java.awt.event.InvocationEvent (implements java.awt.ActiveEvent)
- java.awt.event.ItemEvent
- java.awt.event.TextEvent
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) //アクションが発生した }
TextActionEvent.java (TextActionEvent.jar)
次のプログラム例は、素直に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++; } } }
異なる設計としてテキストフィールドやボタンなどのイベントソース自体をイベントリスナーにして 作ってみました。内部クラスの仕組みを使いますが、actionPerformed( )メソッドでのif文による処理の分岐はなくなります。
イベントソースになっているボタンやテキスト自体に自分のイベントのみを受けとるactionPerformed( )メソッドを作り、このメソッドから親の実行したいメソッドを呼ぶようにします。ここで親側のメソッドやフィールドを自由に使えるようにするために、ボタンやテキストは親クラスの内部クラスにします。
若干プログラムは長くなっていますが将来機能を拡張したときにif文を使うより楽になることを期待しました。 ソースコードへのリンクを載せておきます。
TestActionEvent2.java (TestActionEvent2.jar)
[目次 ]
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);//円 } }
[目次 ]
キーボードのキーボタン操作のイベントです。
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 値を設定します。
確認事項
[目次 ]
用意したソースコード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文を書くことになりかねません。お勧めできません。