プログラミング応用b 第13回 『GUIその2 (AWT/Swingによるイベント処理)』


【イベント処理】

 さて,ここまでサンプルプログラム上で様々なコンポーネントを生成し,ウィンドウに
配置してきました。しかし,ボタンを押したり,メニューを選んでも何もおこりませんで
した。なぜなら,ボタンが押されたことや,メニューが選ばれたことを察知して,何かし
らの動作をさせるようなコードを書いてなかったからです。


●イベント (event)

 では,ソフトウェア使用者の

  ・ボタンへのクリック
  ・メニューの選択
  ・キーボードからの文字入力

などの操作が行われたことを察知し,対応した動作をさせるには,どうすればいいのでし
ょう。その鍵となるのが,イベントという考え方です。イベントは英語でevent,日本語
で言えば“出来事”という意味です。日本でも,コンサートや運動会やお祭りなど,行事
のことをイベントといいますね。

 GUIを持つソフトウェアのプログラミングでは,ボタンやメニューなどのGUI部品への操
作が行われたことを,“イベント”としてプログラムに通知するのが一般的
です。具体的
には,何かしらGUI部品に対して操作が行われると,オペレーティングシステムやGUIライ
ブラリが,その内容を表すイベントオブジェクトを生成します。

 何かしらの操作が行われ,対応するイベントオブジェクトが生成されることを,“イベント
が発生した”
とか,“イベントが発行された”“イベントが通知された”などと言います。

 Javaでは,イベントを表すスーパークラスとしてjava.util.EventObjectが用意されてお
り,様々な操作に対応するイベントクラスがこのEventObjectのサブクラスとして用意さ
れています。

 たとえば,Javaではボタンをクリックしたり(Fig.6-①),メニュー項目を選択した場合,
java.awt.event.ActionEvent型イベントオブジェクトが生成されます(Fig.6-②)。この
ActionEvent型イベントは,コンポーネントになにかしらの“標準的な操作”が起こった
ことを表します


  この“標準的な操作”が,
  ・ボタンの場合は「ボタンを押した」という操作
であり,
  ・ メニュー項目の場合は「メニュー項目を選んだ」という操作
というわけです。

 

Table 1の左側に,Javaで使われる主なイベントクラスをあげておきます。

所属欄がAWTのイベントクラスは,java.awt.eventパッケージで定義されており,
所属欄がSwingのイベントクラスは,javax.swing.eventパッケージで定義されています。

 GUIコンポーネントによって,発生するイベントが異なることに注意してください。たとえば,ActionEvent型
イベントは多くのコンポーネントで発生しますが,TreeModelEvent型イベントは,JTree型
コンポーネントでしか発生しません。

 なお,操作が行われたGUI部品,つまりイベントの発生原因となったGUIコンポーネント
を,イベントソースと呼びます。


(Table 1のPDF版はこちら)



●イベントリスナ

 こうして生成されたイベントを待ち受けるのが,各GUIコンポーネントに登録されてい
イベントリスナ(event listener)と呼ばれるオブジェクトです。ご存じのように英単語
のlistenは,“意識して聴く”,“聞き耳を立てる”というような意味で,イベントリスナ
とは,“イベントがくるのを待ち受ける”というような意味です。

 イベントリスナは,対応するイベントごとに用意されているインタフェイス(リスナイン
タフェイス
)をプログラマが実装して作成します。Table 1の右側に,主なイベントに対応
するイベントリスナインタフェイスをあげておきます。イベントと同様に,所属欄がAWT
のイベントリスナインタフェイスは,java.awt.eventパッケージで定義されており,所属
欄がSwingのイベントリスナインタフェイスは,javax.swing.eventパッケージで定義され
ています。

 たとえば,Table 1を見れば,ActionEventに対応するイベントリスナインタフェイス
として,インタフェイスActionListenerがあることがわかりますね。このように,XXXXEvent型
イベントには,対応するリスナインタフェイスXXXXListenerが用意されている
のです。
Fig.6の例では,ActionListener型イベントリスナがボタンオブジェクトb1に登録されてい
ます(Fig.6-⓪)。

 リスナインタフェイスの実装の仕方を,ActionListenerの例で説明しておきましょう。
ActionListenerインタフェイスの定義は,だいたい次のようになっています。

  public interface ActionListener extends EventListener {
   public void actionPerformed(ActionEvent e);
  }

このactionPerformed( )のように,リスナインタフェイスにはいくつか抽象メソッドが用意
されています。これらの抽象メソッドの引数は,対応するイベントオブジェクトになってい
ます。上のactionPerformed( )メソッドの例では,引数はActionEvent型オブジェクトです。

 JavaのGUIシステムは,イベントオブジェクトが生成されると,イベントソースに登録さ
れているイベントリスナからイベントの種類に対応するイベントリスナの適切なメソッドを
呼び出します
。Fig.6の例では,b1に登録されているActionListener型オブジェクトの
actionPerformed( )を呼び出すことになります。

 ですから,ActionListenerを実装するクラスを定義し,そこでactionPaformed( )メソッド
をオーバライドして,ボタンが押されたときの処理を記述しておけば,ボタンが押されたと
きに望みの処理が実行されることになります。



●リスナアダプタ

 ところで,イベントリスナインタフェイスには,抽象メソッドをたくさん持っているものもあります。
たとえば,ComponentListenerは,

  void componentHidden ( ComponentEvent e );
  void componentMoved ( ComponentEvent e );
  void componentResized ( ComponentEvent e );
  void componentShown ( ComponentEvent e );

という4個の抽象メソッドを持っていて,それぞれ

  ・ GUIコンポーネントが不可視になる
  ・ GUIコンポーネントの位置が変わる
  ・ GUIコンポーネントのサイズが変わる
  ・ GUIコンポーネントが可視になる

ときに呼び出されます。もし,コンポーネントのサイズが変わったときだけに特定の処理を
行いたい場合でも,これら4つのメソッドをオーバライドしてやらなくてなりません。なぜなら,
抽象メソッドをすべてオーバライドしないと,そのサブクラスは抽象クラスになって,オブジェ
クトを作成することができなくなるからです。これではめんどうですね。

 そこで,多くの抽象メソッドを持つリスナインタフェイスには,アダプタ(adapter)と呼ば
れる実装クラスが用意されています。リスナインタフェイスXXXXListenerに対応するアダプ
タクラスの名称は,XXXXAdapterです


 このアダプタクラスでは,スーパーインタフェイスのメソッドすべてを空の定義でオーバ
ライドしています。たとえば,ComponentListenerのアダプタクラスcomponentAdapterの
定義は,次のようになっています。

    public abstract class ComponentAdapter implements ComponentListener{
      public void componentResized (  ComponentEvent e ) {  }
      public void componentMoved   (  ComponentEvent e ) {  }
      public void componentShown   (  ComponentEvent e ) {  }
      public void componentHidden  (  ComponentEvent e ) {  }
    }

全メソッドが空の定義 { } でオーバライドされているのがわかりますね。ですから,コンポーネ
ントのサイズが変更された時だけに処理を行うイベントリスナクラスを作りたければ,
    class クラス名 extends ComponentAdapter  {
      public void componentResized ( ComponentEvent e ) {
        /*  処理をここに記述  */
      }
    }

というように,必要なメソッドだけをオーバライドすればいいことになります。
 各リスナに用意されているアダプタもTable 1の右側にあげておきましたので参 考に
してください。

 



●イベント処理記述の手順

 ここで,XXXXEvent型イベントを処理するための記述手順をまとめておきましょう。まず,
 (1) XXXXEventに対応するイベントリスナインタフェイスXXXXListenerを(直接または間接的に)実装するリスナクラスを作成し,  
 (2) そのリスナクラスでは,継承した抽象メソッドをオーバライドしてイベント処理時に実行したい処理を記述します。
 (3) そして,イベントソースとなるGUIコンポーネントに,そのリスナクラスのオブジェクト(イベントリスナ)を登録します。
   GUIコンポーネントに,イベントリスナを登録するには,GUIコンポーネントのaddXXXXListener( )メソッドを使用することができます

 



●イベント処理の流れ

 次に,プログラム実行時のイベント処理の流れをまとめると,次のようになります。まず,
初期化の時点であらかじめイベントリスナを対象となるGUIコンポーネントオブジェクト(イベ
ントソース)に登録します(Fig.6-⓪)。イベントソースのGUI部品に何かしらの操作が行われ
る(Fig.6-①)と,対応するイベントオブジェクトが生成されます(Fig.6-②)。

 そして,イベントソースに登録されているイベントリスナのうち,発生したイベントに対応
するものが選ばれ,状況に応じて適切なメソッドが選ばれて呼ばれます(Fig.6-④)。




●プログラム終了時の処理

 ところで,いままでのGUIサンプルプログラムでは,終了時の処理を省略していたので,
コンソールからControl+Cというキーコンビネーションで強制終了させていました。しかし,
イベント処理でプログラムを正常に終了させることができます。

 メインウィンドウが閉じられるときに,プログラムを正常終了させることにしましょう。
ウィンドウに関するイベントクラスはWindowEventで,そのリスナはWindowListenerです。

 ウィンドウが閉じるときには,WindowListenerのwindowClosing( )メソッドが呼ばれます
ので,このメソッドを,プログラムを終了させるメソッドSystem.exit( )を呼ぶようにオーバライド
すればいいわけです。

 なお,System.exit( )メソッドは,int型の引数を1個持ち,その値が0の場合はプログラム
が正常に終了したことを表し,0以外の場合は異常終了したことを表します。

 WindowListenerには7個もメソッドがあるので,アダプタクラスWindowAdapterのサブ
クラスとして,イベントリスナクラスを定義すればいいでしょう。もちろん,作成したイベ
ントリスナオブジェクトを,メインウィンドウオブジェクトに登録しておかなくてはなりません。

 


●イベント処理の実例

 ここで,イベント処理の簡単な実例を見てみましょう。List 4は,AWTコンポーネント
を使ったイベント処理の例です。このアプリケーションは,ボタンを持ったメインウィン
ドウを最初に1個表示し,そのボタンを押すと,1個ずつサブウィンドウが増えていくとい
うものです。

List 4 AWTTestEvent1.java


 では,List 4を見ていきましょう。List 4-①では,AWTパッケージをインポートしてい
ます。そして,List 4-②では,イベントを扱うためにjava.awt.eventパッケージをインポ
ートしています。

 List 4-③は,サブウィンドウの初期位置x0,y0と,サブウィンドウの数nです。List 4-④
ではメインウィンドウfを生成し,List 4-⑤では,ボタンb1を生成してメインウィンドウf
に追加しています。

 さて,List 4-⑥からがイベント処理の具体的な例になります。まず,ボタンに登録する
アクションリスナを作成します。List 4-⑥〜⑧がその部分になります。List 4-⑥に
あるように,ActionListenerを実装するクラスとして,Button1Actionクラスを定義してい
ます。このように,クラスの内部や,メソッドの内部で定義されるクラスを,内部クラス
呼びます。

 この内部クラスButton1Actionの定義の中で,actionPerformed( )メソッドをオーバライ
ドしているのが,List 4-⑦の部分です。ここでは,サブウィンドウを生成し,座標をずら
して表示しています。そして,このリスナクラスButton1Actionのオブジェクトをボタンb1
へ登録しているのが,List 4-⑨です。

 次に,アプリケーションの正常終了を行わせるイベント処理を記述します。List 4-⑩
〜⑫で,WindowAdapterのサブクラスとしてイベントリスナクラスMainWindowListener
を定義しています。ここでは,List 4-⑪にあるようにSystem.exit( )メソッドを呼び出す
ように,windowClosing( )メソッドをオーバライドしています。こうして作成したイベント
リスナMainWindowListener型のオブジェクトをメインウィンドウfに登録指定しているのが,
List 4-⑬です。最後のList 4-⑭でメインウィンドウfを可視化しています。

 List 4を実行すると,最初にボタンのついたメインウィンドウが表示されます。そして,
ボタンを押すと,次々にサブウィンドウが表示されます。メインウィンドウを閉じると,正常
にアプリケーションが終了します。実際に実行して,イベント処理がうまくはタラしている
ことを確認してください。



●匿名クラスとしてのリスナクラス

 実は,List 4はイベント処理そのものとしては正常に動作しますが,リスナクラスの定義
の書き方にいささか古い記述があります。リスナクラスは,通常,匿名クラス(無名クラス
も言う)という,名前のないクラスを使うのが主流です。



 匿名クラスは,Fig.7のように定義します。特徴的なのは,匿名クラスは定義と生成が同じ
場所で行われる
ことです。通常のクラスは,名前がありますから,

  new クラス名( )

とすれば,定義とは別のところでクラス名を指定することでオブジェクトの生成ができました。
しかし,匿名クラスは名前がないので,定義と同じ場所でオブジェクトを生成するしかありません。

 一見,この点は不便に感じますが,逆に利点もあるのです。一般的に,クラス定義と使用
場所は近いにこしたことはありません。この点で,匿名クラスは非常に優れているのです。
通常のクラス定義は,クラスに名前をつけて,その名前を使ってプログラムのいろいろな
場所から利用できるようにしています。しかし,リスナクラスは,コンポーネントオブジェクト
1つ1つに定義してやる必要があり,しかも他の目的で再利用することは滅多にありません。
ですから,リスナクラスに名前を付ける本質的な必要性はあまり無いのです。


 そこで,匿名クラスとしてリスナクラスを定義し,同時にオブジェクトを生成してGUIコンポーネント
オブジェクトに登録してしまうのがスマートな方法なのです。

 実際に,List 4をSwing版になおした上で,リスナを匿名クラスで定義するようにした例を
List 4_2に示します。

List 4_2 SwingTestEvent1.java



List 4_2-①でSwingパッケージを,List 4_2-②でjava.awt.eventパッケージをインポート
しています。List 4_2-③は,サブウィンドウの初期位置と数です。List 4_2-④,⑤は,
メインウィンドウの生成とボタンの生成です。

 List 4_2-⑥からが,イベント処理部分です。List 4_2-⑥〜⑧で,addActionListener( )
メソッドを呼び出して,匿名クラスオブジェクトをリスナとしてボタンb1に登録しています。
List 4_2-⑦が,ActionListenerを実装する匿名クラスを定義し,オブジェクトを生成して
いる部分です。

 同様に,List 4_2-⑨〜⑫では,addWindowListener( )メソッドを呼び出して,アプ
リケーション終了に関するイベントリスナをメインウィンドウfに登録しています。List 4_2-⑩
が匿名のWindowAdapterのサブクラスを定義し,そのオブジェクトを生成している部分です。
このList 4_2-⑨〜⑪の部分は,多くのGUIアプリケーションで共通する部分ですから,よくおぼ
えておきましょう。

 



次回はもう少し詳しくGUIのイベント処理例を紹介します。