GUI部品のイベント処理


GUIでマウス入力,キー入力などを処理する方法は説明したが,GUI部品に対 するイベントはどう扱うかは説明していなかった.たとえば,Frameの中にボ タンがある時に,ボタンの上でマウスボタンを押された時の処理などが問題に なる.

前回やったように,ボタンのような GUI部品に対するユーザ入力は,GUI部品 に関連づけられた Event Listenerによっておこなわれるが,ボタンなどの部 品に関しては,MouseEventのような低レベルのイベントではなく, ActionEventというイベントで処理する.

ボタンのような GUI部品では,mousePressを受け取ってActionEventに変換す るだけだが,テキストフィールドのようなものでは,キー入力やマウスを使っ た編集作業は内部で処理して,リターンキーが押された時に始めて, ActionEventを発生するようになっている.

また,チェックボックスのように,イベントを発生させるよりは,状態を内 部で保持しておいて,他のGUI部品を利用したイベント発生時に内部をチェッ クする使い方が主だというGUI部品もある.ただし,チェックボックスの内部 状態が変化した時に,ItemEventイベントを発生するので,それを受け取る Event Listenerを作っておけば,イベントを捕まえることができる.

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

public class GUITest2 extends Frame implements ActionListener,MouseListener{
     // 画面に表示するメッセージの変数.空文字列 "" で初期化する必要がある.
  String message="";
  Button button1, button2, button3;
  Checkbox check1, check2;
  TextField textField;
  GUITest2(){
    setLayout(new FlowLayout());
       // 「Check1」というラベルを持つチェックボックスを作り,window中に配置
    add(check1=new Checkbox("Check1"));
       // 「Button1」というラベルを持つボタンを作り,window中に配置
    add(button1=new Button("Button1"));
    button1.addActionListener(this);
       // ラベルを持たないチェックボックスを作り,window中に配置
    add(check2=new Checkbox());
       // ラベルを持たないボタンを作り,window中に配置
    add(button2=new Button("Button2"));
    button2.addActionListener(this);
       // 幅10文字分のTextField(テキスト入力用領域)を作り,window中に配置
    add(textField=new TextField(10));
    textField.addActionListener(this);
    add(button3=new Button("Quit"));
    button3.addActionListener(this);
       // windowのサイズを 500 x 500 に変更
    setSize(500,500);
       // windowを表示する.
    setVisible(true);
    addMouseListener(this);
  }
  public static void main(String args[]){
       // GUITest2クラスのインスタンスを生成
    GUITest2 window=new GUITest2();
       // window内部に GUI部品を置くときのレイアウトをFlowLayoutに指定
  }
  public void paint(Graphics g){
       // 以後の描画,塗り潰しの色を黄色に指定
    g.setColor(Color.yellow);
       // (0,0)から幅500,高さ500で描画
    g.fillRect(0,0,500,500);
       // 以後の描画,塗り潰しの色を黒に指定
    g.setColor(Color.black);
       // 文字列 message の内容を(50,100)の座標から描画
    g.drawString(message,50,100);
  }
     // 内部の GUI 部品以外のところでマウスが押された時に呼ばれる.
  public void mousePressed(MouseEvent e){
    int mx=e.getX(), my=e.getY();
     // マウスが押された時に渡される Event と押された時のX座標,Y座標を表示
    System.out.println("mouseDown("+e+","+mx+","+my+")");
     // イベントの処理をおこなったことを表すため true を返す
  }
  public void mouseReleased(MouseEvent e){}
  public void mouseClicked(MouseEvent e){}
  public void mouseEntered(MouseEvent e){}
  public void mouseExited(MouseEvent e){}
     // GUI部品がイベントを処理して,その部品を含むクラスに処理結果を渡す
     // 際に呼ばれる.
  public void actionPerformed(ActionEvent e){
     // イベントのターゲットは GUI 部品のオブジェクトだが,
     // これが Button クラスのインスタンスかどうかのチェック
    System.out.println(e);
    Object source=e.getSource();
    if(source instanceof Button){
      String label=e.getActionCommand();
        // 押されたボタンのラベルが"Button1"かどうかのチェック
      if(label.equals("Quit")){
	System.exit(0);
      }
      else if(label.equals("Button1")){
          // その時は表示する messageは "Button1"
        message="Button1," + "Check1 is "+check1.getState();
      }
      else{
          // それ以外の時は表示する messageは "Unknown Button"とする.
        message="Button2," + "Check2 is "+check2.getState();
      }
    }
     // イベントのターゲットが Checkbox クラスのインスタンスかどうかのチェック
    else if(source instanceof Checkbox){
        // その時は チェックボックスの内容を表示
      message="Checkbox"+(Checkbox)source;
    }
     // イベントのターゲットが TextField クラスのインスタンスかどうかのチェック
    else if(source instanceof TextField){
      // その時は 内容を表示
      message="TextField"+(TextField)source;
    }
      // 明示的に,書き直しをする
    repaint();
  }
}
実行してみると分かるが,内部のGUI部品の内側でマウスのボタンを押した時 は,外側のFrameのmouseDownは呼ばれない.

課題1

下のプログラムは,テレビゲームの初期の傑作である「ブロック崩し(Break Out)」からブロックを除いたものである.このプログラムに以下の変更を加え なさい. 時間が余ったら,オプション課題として,「ゲームとして楽しめるようにプ ログラムを変更する」,「GUI部品を使って機能を追加する」という課題もやっ てみてください.

サンプルプログラムではゲームを遊んでいるわけではないことを示すために,

    g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
という文字列を表示するようにしてあります.この表示は取り除かないで下さ い.
import java.awt.*;
import java.awt.event.*;

class BallGame extends Frame implements KeyListener{
    // ボールの座標,速度のx, y成分の宣言
  int ball_x=100,ball_y=100,ball_vx=16,ball_vy=12;
    // ボールの大きさ
  int ball_size=20;
    // 画面の幅,高さ
  int width=300, height=600;
    // バーの座標
  int bar_x=0, bar_y=550;
    // バーの速度
  int bar_vx=0;
    // バーの幅,高さ
  int bar_width=60,bar_height=10;
    // BallGameクラスのコンストラクタ
  public BallGame(){
      // 親クラスである Frameのコンストラクタを呼ぶ
    super();
      // ウィンドウのサイズを変更
    setSize(width,height);
      // ウィンドウを表示
    setVisible(true);
    addKeyListener(this);
  }
  public static void main(String[] args){
      // BallGameクラスのインスタンスを作成
    BallGame frame=new BallGame();
      // スレッドを獲得
    Thread t=Thread.currentThread();
    for(;;){
        // 0.1秒(100ミリ秒)待つ
      try { t.sleep(100); }
      catch(Exception e){}
        // BallGameクラスのインスタンス frame のtimeTickメソッドの呼び出し
      frame.timeTick();
    }
  }
  void timeTick(){
      // バーの移動
    bar_x=bar_x+bar_vx;
      // 左端から飛び出そうになったら左端に合わせる
    if(bar_x<0) bar_x=0;
      // 右端から飛び出そうになったら右端に合わせる
    else if(bar_x+bar_width >width) bar_x=width-bar_width;
      // ボールの移動.古い座標を保存
    int old_x=ball_x;
    int old_y=ball_y;
      // 速度に従って,次の座標を決定
    ball_x=ball_x+ball_vx;
    ball_y=ball_y+ball_vy;
      // 左端から飛び出そうになったら,反射させる
    if(ball_x<0){
      ball_x= -ball_x;
      ball_vx= -ball_vx;
    }
      // 右端から飛び出そうになったら,反射させる
    else if(ball_x >width-ball_size){
      ball_x=(width-ball_size)-(ball_x-(width-ball_size));
      ball_vx= -ball_vx;
    }
      // 上端から飛び出そうになったら,反射させる
    if(ball_y<0.0){
      ball_y= -ball_y;
      ball_vy= -ball_vy;
    }
      // バーのある線を通過しそうになったら,
    else if(ball_y >bar_y && old_y<=bar_y){
        // バーのある線を横切るX座標を計算
      int x=old_x+(ball_x-old_x)*(bar_y-old_y)/(ball_y-old_y);
        // バーと接触している場合は反射させる.
      if(bar_x <= x && x<=bar_x+bar_width){
        ball_y=bar_y-(ball_y-bar_y);
        ball_vy= -ball_vy;
      }
    }
      // 下端から飛び出そうになったら,反射させる
    else if(ball_y > height){
      ball_vy=-ball_vy;
    }
      // 再表示
    repaint();
  }
  public void paint(Graphics g){
      // 描画色を黒にする.
    g.setColor(Color.black);
      // 全体を塗り潰す
    g.fillRect(0,0,width,height);
      // 描画色を白にする.
    g.setColor(Color.white);
      // 警告文字列を(0,200)の点を左下にして描く
    g.drawString("Not a game, but a excercise in CP1(Wed/tanaka)",0,200);
      // 描画色を赤にする.
    g.setColor(Color.red);
      // ボールを描く
    g.fillOval(ball_x,ball_y,ball_size,ball_size);
      // オフスクリーンイメージへの描画色を青にする.
    g.setColor(Color.blue);
      // バーを描く
    g.fillRect(bar_x,bar_y,bar_width,bar_height);
  }
    // キーが押された時にこのメソッドが呼ばれる.
  public void keyReleased(KeyEvent e){}
  public void keyTyped(KeyEvent e){}
  public void keyPressed(KeyEvent e){
    int key=e.getKeyChar();
      // 'h' のキーが押された時は,バーの移動速度を -10に
    if(key=='h'){
      bar_vx= -10;
    }
      // 'l' のキーが押された時は,バーの移動速度を 10に
    else if(key=='l'){
      bar_vx=10;
    }
      // 'j' のキーが押された時は,バーの移動速度を 0に
    else if(key=='j'){
      bar_vx=0;
    }
    else if(key=='q'){
      System.exit(0);
    }
  }
}


課題をこなしたら, lectures.g99.cp1-ktanaka-W-Wed-5 のニュースグループに投稿すること.