Deep Side of Java〜Java 言語再入門 第4回 〜 アプレット、スレッド、AWT

AWTの使い方

イベントモデル






AWTの使い方

イベントモデル

AWTでは、ウィンドウプログラムの通例に従って、ユーザが行うアクションが「イベント」のかたちでWidgetに対して通知される。しかし、一般のツールキットとは違い、物理的なマウスクリック/キーボード入力に直接結びつくのではなく、AWTで定義する Look&Feel に基づいて、抽象的な「選択」をベースとして定義されている。つまり、ボタンをクリックすることと、そのWidgetがフォーカスを取得している状態でスペースキーを押すことが、同一の「選択」アクションとして扱われるかたちでイベントが生成する。実際にはどんなイベントが生成するかはWidgetによって異なるが、それらは基底クラスである Event クラスから派生したクラスのインスタンスである。

このイベント処理に際して使われる技法が Observer デザインパターンである。つまり、Widgetはイベントを生成するが、それを処理するハンドラは別なクラスに属するメソッドであっても良いことになる。特にAWTでは、そのイベントを処理するクラスを「リスナ」と呼び、このイベントを処理するクラスをマーキングするための interface が定義されている。つまり、次の通りになる。

class MyClass implements SomeEventListener {
    ...............
    Widget w = new Widget();   /* 何かのWidgetを生成 */
    w.addSomeEventListener( this );  /* 何か生成するイベントのためのリスナを登録 */
  /* この例では、Widgetを生成したクラス自身で処理するから this を渡す */
    ...............
void SomeEventHandler( SomeEvent e ) {  /* イベントが起きたときに何かする */ }

だから、SomeEventListener.java の中には、void SomeEventOccure( SomeEvent e ); が定義されていることは言うまでもない。つまり、Widget クラスの中で、そのイベントを処理するリスナクラスの登録をする addSomeEventListener メソッドが定義されており、これに登録されたリスナクラスのそれぞれの SomeEventHandler がコールバックとして、マルチキャストで呼び出されることになる。勿論、カスタムウィジットクラスを定義してれば、そのWidgetから派生したクラスで処理してもよいし、多くのコールバックを集中して処理するクラスを定義して、そこで処理をすればデザインパターンで言う Mediator パターンになる(後述)。

また、イベントによっては具体的なサブ種別がいくつかあり、interface の中にいくつものメソッドが定義されている場合がある。とすると、その interface を implements するリスナクラスでは、特に興味のないコールバックも形式的に定義しなければならないことになる。これは面倒なだけである。そこでこういうケースには、「アダプタクラス」と呼ばれる、interface を実装しただけの何もしないクラスがAWTには用意されていて、これを使った内部無名クラスをリスナとして与える手法が良く使われる。たとえば、WindowEvent では windowClosing メソッドだけを実装すれば良いだけのことが多いので、次のようにするのが一般的である。

public class Awt extends Frame {
    /* 形式的には Awt クラスの内部で、更に addWindowListener メソッドの引数の中で、
       無名のクラスが定義されている。WindowAdapter クラスでは、何もしない 
       WindowListener interface で定義された抽象メソッドが実装されている。*/
    addWindowListener( new WindowAdapter() {
        /* その中で windowClosing メソッドだけを上書きする。*/
        public void windowClosing( WindowEvent we ) {
            System.exit( 0 );
        }
    } );  /* ← addWindowListener メソッド引数の終りとその式文の終了 */

以下は各Widgetが受理するイベントである。この他にもいくつかあるが、アプリケーションプログラマがいじるようなものではない。

ActionEvent
抽象化された「選択」を示す。Button, List, MenuItem, TextField などのWidgetで生成し、選択されたことを示す。リスナ定義の interface は ActionListener、コールバックとなるメソッドは public void actionPerformed( ActionEvent e ); である。

Button
マウスによるクリック、フォーカスを取得した状態でのスペースキーの入力で生成。
List
マウスによるダブルクリック、項目が選択された状態での改行キーの入力で生成。
TextField
フォーカスを取得した状態での改行キー入力で生成。
MenuItem
メニューが表示された状態でその項目をマウスでクリックするか、その項目が選択された状態でスペースキーか改行キーの入力で生成。


WindowEvent
ウィンドウの状態が変化に応じて生成し、7つのイベント種別が定義されている。リスナ定義の interface は WindowListener だが、種別が多いのでアダプタクラスが定義されており、通常このアダプタクラス WindowAdaptor を上書きして使う。

特に重要なメソッドとして、ウィンドウ右上隅のクローズボタンを押した時に生成し、ウィンドウを閉じるのに使う public void windowClosing( WindowEvent we ); がある。逆に言えば、スタンドアロンGUIプログラムでは、これを上書きして System.exit( 0 ); を実行してやらないと、クローズボタンを押してもウィンドウは終了しない。

AdjustmentEvent
Scrollbar と、Scrollbar 付きContainer Widgetである ScrollPane で生成する。つまり、スクロールバーを操作した時に生成し、スクロールバーの値に応じた移動処理をすることが期待される。リスナ定義 interface は AdjustmentListener であり、コールバックは public void adjustmentValueChanged(AdjustmentEvent e); である。

ItemEvent
Checkbox, CheckboxMenuItem, Choice, List Widgetで生成する。つまり、それぞれの項目がとりあえず選択された(具体的な表示で反転した)時に生成する。リスナ定義 interface は ItemListener であり、コールバックは void itemStateChanged(ItemEvent e); である。

TextEvent
TextField, TextArea Widgetで生成する。つまり、それらの編集可能なテキストが変更された時に生成する。リスナ定義 interface は TextListener であり、コールバックは public void textValueChanged(TextEvent e); である。

KeyEvent
さて、意味ベースのイベントの他に、物理的なキーボード入力やマウス移動に関するイベントも存在する。まずキーボード入力をトラップするイベントが KeyEvent であるが、KeyListener リスナ interface が3つのコールバックを定義するために、KeyAdaptor アダプタクラスが定義されている。これはその本質上、どんなWidgetでも生成する。

public void keyTyped(KeyEvent e);
キーが押されてから離された時に発生する。
public void keyPressed(KeyEvent e);
キーが押された時に発生する。
public void keyReleased(KeyEvent e);
キーが離された時に発生する。


MouseEvent
同じく物理的なマウス操作に関して生成するイベントである。しかし、これは利用価値が大きいために、マウスの移動の操作に関するリスナと、マウスのボタンなどのアクションに関するリスナの2つに分けて定義されている。

マウスのアクションを担当する MouseListener リスナに5つのコールバックが定義されているために、MouseAdaptor アダプタクラスが定義されている。これもその本質上、どんなWidgetでも生成する。これは特に Canvas クラスを拡張してカスタムウィジットを作る時によく利用する。

public void mouseClicked(MouseEvent e);
マウスのボタンがクリックされた(実質上、押されて離された)時に発生する。
public void mousePressed(MouseEvent e);
マウスのボタンが押された時に発生する。
public void mouseReleased(MouseEvent e);
マウスのボタンが離された時に発生する。
public void mouseEntered(MouseEvent e);
マウスが移動して、そのWidgetの中に入った時に生成する。
public void mouseExited(MouseEvent e);
マウスが移動して、そのWidgetの中から出た時に生成する。


マウスの移動を担当する MouseMotionListener リスナには2つのコールバックが定義されており、MouseMotionAdaptor アダプタクラスが定義されている。これも本質上、どんなWidgetでも生成するが、Canvas Widgetに対して絵を描いたりする処理をするのに使うことが多い。

public void mouseDragged(MouseEvent e);
マウスボタンが押された状態で、マウスが移動したことを通知する。正確にはそのWidgetの内部でマウスボタンが押された後から、たとえマウスが移動してWidgetから出てもフォーカスが継続しているために、移動を報告し続け、マウスボタンが離されるまで続く。
public void mouseMoved(MouseEvent e);
マウスボタンが押されていない状態で、そのWidget内でマウスが移動したことを通知する。

具体的なプログラムでは次の通り。

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

public class Awt extends Frame implements ActionListener {
     Button quit;
     public static void main( String [] args ) {
          new Awt();
     }

     private Awt( ) {
          super( "Awt Widgets sample" );  /* タイトル表示はこうする */
          addWindowListener( new WindowAdapter() {
                    public void windowClosing( WindowEvent we ) {
                         System.exit( 0 );
                    }
               } );

          setSize( new Dimension( 150, 80 ) );

          quit = new Button( "Quit" );
          quit.addActionListener( this );
          add( quit );
          show();
     }

     public void actionPerformed( ActionEvent ae ) {
          /* getSource() は、そのイベントが起きたWidgetを返す */
          Object o = ae.getSource();
          if( o instanceof Button ) {
               Button b = (Button)o;
               /* 適切にキャストすれば、そのインスタンス変数なども取得できる */
               System.out.println( "Button Label is " + b.getLabel() );
               if( b == quit ) System.exit( 0 );
          }
     }
}



copyright by K.Sugiura, 1996-2006