イベント処理


入力と表示

前々回やった,端末からの入力と AWT を合わせて,キーボードからの入力に 合わせて,絵を表示させることができる.サンプルプログラムを見てみよう.
 // キーボード入力関連のクラスの使用のため
import java.io.*;
 // AWT関連のクラスの使用のため
import java.awt.*;

 // 線分に対応する内部で用いるデータ構造
class Line{
   // 始点,終点の2点のX座標,Y座標
  public int start_x,start_y,end_x,end_y;
  public Line(int x1,int x2,int x3,int x4){
   start_x=x1;
   start_y=x2;
   end_x=x3;
   end_y=x4;
  }
}

 // Frameクラスの子クラスとしてKeyDrawを定義
class KeyDraw extends Frame{
    // 線分の配列を管理する.
  public Line[] lines;
    // 配列linesのどこまで中身が入っているかを管理.
  int lineCount;
    // コンストラクタの宣言
  public KeyDraw(String title){
     // Frameクラスのコンストラクタを呼び出し
    super(title);
      // Lineを保持するための配列linesを大きさ10で定義
    lines=new Line[10];
      // linesの中身は使われていない中身は
    lineCount=0;
  }
    // 入出力例外が生ずる可能性があるので,throws IOExceptionが必要
  public static void main(String args[]) throws IOException{
      // KeyDrawクラスのインスタンスを作成.
    KeyDraw frame=new KeyDraw("KeyDraw");
      // ウィンドウの大きさを横600ドット縦400ドットとする
    frame.setSize(600,400);
      // ウィンドウを表示する.
    frame.setVisible(true);
      // キーボードから読み込むためのBufferedReaderクラスのインスタンスを生成
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
    int i,start_x,start_y,end_x,end_y;
      // 10回繰り返す
    for(i=0;i< 10;i++){
      System.out.println("Input Line "+i);
      System.out.print("Input start_x : "); System.out.flush();
        // キーボードから1行入力して整数に直しstart_xに入れる
      start_x=Integer.parseInt(br.readLine());
      System.out.print("Input start_y : "); System.out.flush();
        // キーボードから1行入力して整数に直しstart_yに入れる
      start_y=Integer.parseInt(br.readLine());
      System.out.print("Input end_x   : "); System.out.flush();
        // キーボードから1行入力して整数に直しend_xに入れる
      end_x=Integer.parseInt(br.readLine());
      System.out.print("Input end_y   : "); System.out.flush();
        // キーボードから1行入力して整数に直しend_yに入れる
      end_y=Integer.parseInt(br.readLine());
        // frameのインスタンス変数である入れる型の lines に Lineの要素を足す
      frame.lines[i]=new Line(start_x,start_y,end_x,end_y);
      frame.lineCount++;
        // 再表示を支持する
      frame.repaint();
    }
    System.out.print("Exit OK?");System.out.flush();
     // 確認のために,1行入力
    br.readLine();
     // 終了する
    System.exit(0);
  }
   // MyFrameの中身を表示するためのメソッド paint
  public void paint(Graphics g){
    int i;
     // 使う色を白に指定
    g.setColor(Color.white);
     // (0,0)-(600,400)を塗り潰し
    g.fillRect(0,0,600,400);
     // 使う色を黒に指定
    g.setColor(Color.black);
     // lineCount分だけ繰り返し
    for(i=0;i< lineCount;i++){
       // lines[i]を描画する
      g.drawLine(lines[i].start_x,lines[i].start_y,
                 lines[i].end_x,lines[i].end_y);
    }
  }
}
上のプログラムのSystem.exit(0) というのは,プログラムを終了することを 表している.これを入れないと,C-c を押すまでプログラムが止まらない.

イベント駆動プログラミング

上のプログラムを実行させてみるとわかるように,標準入力からの入力は, などの問題点がある.

AWTでは,マウス,キーボードなど多種類の デバイスを扱い,レスポンス(応答時間)の早い入力をおこなうために,イベン ト駆動のモデルを採用している.

イベント駆動の考え方は,br.readLine() のように入力を要求する文を実行 するのではなく,入力の発生時にイベントを待っているオブジェクト中に定義 された対応するメソッドを呼び出すというものである.JDK1.1以降ではイベン トを待つオブジェクトはGUI部品のクラス本体で, GUI部品に関連づけられた Event Listener である.

Event Listnerはインタフェースという形で実現される.インタフェースは, 継承(inheritance)と似ているが,継承と違って,メソッド名を受け継ぐだけ で,メソッド定義の中身は受け継がないというものである.継承での親クラス に対応するものをインタフェース,子クラスに対応するものを実装 (implementation)と言う.一つのクラスは一つの親クラスしか持つことができ ないが,複数のインタフェースの実装と成ることができる.
このあたりがなぜこうなっているかを認識するには,オブジェクト指向に関 するかなりの知識が必要となる.ここでは,細かい説明は避ける.
イベントは以下のように分類されていて,それぞれ別の Event Listnerが対 応する.

たとえば,KeyListenerでは以下のようなメソッドを定義する必要がある. また,MouseListenerでは以下のようなメソッドを定義する必要がある. Event Listenerをどのように書くかはいくつかの流儀がある. 最後の方法が推奨されているがここでは,最初の方法でやってみる.
  // AWTを使うため,
import java.awt.*;
  // イベント駆動関係のクラスを用いるため
import java.awt.event.*;

  // 線分のクラスを定義する.
class Line{
  // 始点,終点のX座標,Y座標を int で保持する.
  public int start_x,start_y,end_x,end_y;
  // Lineのコンストラクタ
  public Line(int x1,int x2,int x3,int x4){
   start_x=x1;
   start_y=x2;
   end_x=x3;
   end_y=x4;
  }
}

 // インタフェースの実装では 「implements インタフェース名」と書く
 // 複数書く場合は,カンマで区切る
class MouseDraw extends Frame implements KeyListener, MouseListener, MouseMotionListener{
   // Lineの配列を保持する変数 linesの宣言
  public Line[] lines;
   // linesのどこまで使われているかを示す変数 lineCountの宣言
  int lineCount;
   // マウスをドラッグ中かどうかを示す boolean型(真偽値)の変数draggingの宣言
  boolean dragging;
   // 表示する色を保持する変数
  Color lineColor;
   // コンストラクタの宣言
  public MouseDraw(String title){
     // 親クラス Frameのコンストラクタの宣言
    super(title);
     // 大きさ10のLineの配列を作成してlinesに代入
    lines=new Line[10];
     // linesは何も使われていない
    lineCount=0;
     // ドラッグ中ではない
    dragging=false;
     // 線の色は黒に
    lineColor=Color.black;
      // GUI部品と,Event Listenerを関連づける
    addKeyListener(this);
    addMouseListener(this);
    addMouseMotionListener(this);
  }
  public static void main(String args[]){
     // MouseDrawのインスタンスを作成 frameに代入
    MouseDraw frame=new MouseDraw("MouseDraw");
     // サイズを 600x400に設定
    frame.setSize(600,400);
     // 表示する.
    frame.setVisible(true);
  }
   // Frameの書き直しをする際に呼ばれる
  public void paint(Graphics g){
    int i;
      // 白で(0,0)-(600,400)を塗り潰す
    g.setColor(Color.white);
    g.fillRect(0,0,600,400);
      // 色を設定
    g.setColor(lineColor);
      // lineCount個,linesに登録されているLineを描画する
    for(i=0;i< lineCount;i++){
      g.drawLine(lines[i].start_x,lines[i].start_y,
                 lines[i].end_x,lines[i].end_y);
    }
     // マウスをドラッグ中の時は
    if(dragging){
       // 赤い色で
      g.setColor(Color.red);
       // lines[lineCount] を描画する.
      g.drawLine(lines[i].start_x,lines[i].start_y,
                 lines[i].end_x,lines[i].end_y);
    }
  }
   // KeyListenerを実装するためのメソッド
  public void keyPressed(KeyEvent e){
     // イベントからキーのコードを取り出す
    int key=e.getKeyChar();
     // デバッグ用の表示
    System.out.println("keyPressed("+e+","+key+")");
     // 入力が 'q'の時は終了する
    if(key=='q') System.exit(0);
  }
   // 要らないイベントに対応するメソッドも中身は空で書いておく必要がある.
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
   // MouseListenerを実装するためのメソッド
  public void mousePressed(MouseEvent e){
     // 押された時のマウスカーソルの位置を得る
    int mx=e.getX(),my=e.getY();
     // デバッグ用の表示
    System.out.println("mousePressed("+e+","+mx+","+my+")");
     // 配列linesのlineCount番目に線分を登録
    lines[lineCount]=new Line(mx,my,mx,my);
     // ドラッグ中であることを示す
    dragging=true;
     // 再表示をおこなう
    repaint();
  }
   // マウスのボタンが離された時のイベント
  public void mouseReleased(MouseEvent e){
     // マウスカーソルの位置を得る
    int mx=e.getX(),my=e.getY();
     // デバッグ用の表示
    System.out.println("mouseUp("+e+","+mx+","+my+")");
     // 配列linesのlineCount番目の始点を変更
    lines[lineCount].end_x=mx;
    lines[lineCount].end_y=my;
     // lineCountを増やす
    lineCount++;
    dragging=false;
     // 再表示をおこなう
    repaint();
  }
  public void mouseClicked(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
   // MouseMotionListenerを実装するためのメソッド
  public void mouseDragged(MouseEvent e){
     // マウスカーソルの位置を得る
    int mx=e.getX(),my=e.getY();
     // デバッグ用の表示
    System.out.println("mouseDrag("+e+","+mx+","+my+")");
     // 配列linesのlineCount番目の始点を変更
    lines[lineCount].end_x=mx;
    lines[lineCount].end_y=my;
     // 再表示をおこなう
    repaint();
  }
  public void mouseMoved(MouseEvent e){}
}
興味を持っている人のために,無名クラスを用いたプログラムも書いておく. ただし,説明は省く.
import java.awt.*;
  // イベント駆動関係のクラスを用いるため
import java.awt.event.*;

class Line{
  public int start_x,start_y,end_x,end_y;
  public Line(int x1,int x2,int x3,int x4){
   start_x=x1;
   start_y=x2;
   end_x=x3;
   end_y=x4;
  }
}

 // インタフェースの実装では 「implements インタフェース名」と書く
 // 複数書く場合は,カンマで区切る
class MouseDraw extends Frame{
  public Line[] lines;
  int lineCount;
  boolean dragging;
  Color lineColor;
  public MouseDraw(String title){
    super(title);
    lines=new Line[10];
    lineCount=0;
    dragging=false;
    lineColor=Color.black;
      // GUI部品と,Event Listenerを関連づける
      // KeyAdapterは,KeyListenerを実装して中身は何もないクラス
      // new クラス名(){} で,「クラス名」の名前のない子クラスを定義すると同時に
      // インスタンスを作る
    addKeyListener(new KeyAdapter(){
      public void keyPressed(KeyEvent e){
	int key=e.getKeyChar();
	System.out.println("keyPressed("+e+","+key+")");
	if(key=='q') System.exit(0);
      }
    });
      // MouseAdapterは,MouseListenerを実装して中身は何もないクラス
    addMouseListener(new MouseAdapter(){
      public void mousePressed(MouseEvent e){
	int mx=e.getX(),my=e.getY();
	System.out.println("mousePressed("+e+","+mx+","+my+")");
	lines[lineCount]=new Line(mx,my,mx,my);
	dragging=true;
	repaint();
      }
      public void mouseReleased(MouseEvent e){
	int mx=e.getX(),my=e.getY();
	System.out.println("mouseUp("+e+","+mx+","+my+")");
	lines[lineCount].end_x=mx;
	lines[lineCount].end_y=my;
	lineCount++;
	dragging=false;
	repaint();
      }
    });
      // MouseMotionAdapterは,MouseMotionListenerを実装して中身は何もないクラス
    addMouseMotionListener(new MouseMotionAdapter(){
      public void mouseDragged(MouseEvent e){
	int mx=e.getX(),my=e.getY();
	System.out.println("mouseDrag("+e+","+mx+","+my+")");
	lines[lineCount].end_x=mx;
	lines[lineCount].end_y=my;
	repaint();
      }
    });
  }
  public static void main(String args[]){
    MouseDraw frame=new MouseDraw("MouseDraw");
    frame.setSize(600,400);
    frame.setVisible(true);
  }
  public void paint(Graphics g){
    int i;
    g.setColor(Color.white);
    g.fillRect(0,0,600,400);
    g.setColor(lineColor);
    for(i=0;i< lineCount;i++){
      g.drawLine(lines[i].start_x,lines[i].start_y,
                 lines[i].end_x,lines[i].end_y);
    }
    if(dragging){
      g.setColor(Color.red);
      g.drawLine(lines[i].start_x,lines[i].start_y,
                 lines[i].end_x,lines[i].end_y);
    }
  }
}

課題1
上のプログラム MouseDrawに以下のような変更を加えたプログラムを作りなさい.
  • drawLine の第3, 第4引数は終点のx,y座標ですが, fillRect の第3,第4引数は幅と高さです.間違えがち なので注意して下さい.
  • ドラッグ中に描く長方形の色は適当でいいです.
  • 長方形を描く際に,終点が始点の右下になくても正しく動くようにプロ グラムを作成してください.
  • 長方形ごとに色を変えるというより困難な課題に挑む人の場合は,Line クラスを拡張して(名前を変えた方がいいのはもちろんですが),色の情報も含 むようにすると良いでしょう.
    課題をこなしたら, lectures.g99.cp1-ktanaka-W-Wed-5 のニュースグループに投稿すること.
    次に進む