Java AWT: delegation イベントモデル


最終更新: 1997年2月3日

目的

このドキュメントでは、新しいイベントモデルを AWT に導入する原理について、特に新しいモデルの AWT API への割当て方法について説明します。この新しいモデルは、 JavaBeans アーキテクチャによって一般的なイベント処理にも適用され、ドキュメントJavaBeans 仕様の中で高いレベルで説明されています。

1.0イベントモデル

AWT バージョン 1.0 のイベント処理モデルは、継承に基づいています。プログラムが GUI イベントをキャッチしプロセスするためには、GUI コンポーネントをサブクラス化し、 action() または handleEvent() のどちらかのメソッドをオーバーライドする必要があります。これらメソッドの 1 つから trueを返すとイベントを消費することになり、それ以上プロセスされません; そうでなければ、イベントは消費されるかまたは階層のルートに到達するまで、順番に GUI 階層にまで伝搬されます。このモデルの結果は、プログラムがイベント処理コードを構築するために基本的に次の 2 つの選択が持てるということです:
  1. 個別のコンポーネントは各々サブクラス化し、そのターゲットイベントを固有に処理します。この結果、クラスが過剰になります。
  2. 全階層 (または以降サブセット) のすべてのイベントを、特定のコンテナで処理します; 結果として、コンテナのオーバーライドされる action() または handleEvent() メソッドは、イベントをプロセスするために複雑な条件命令を含む必要があります。

1.0 イベントモデルに関する問題

上記モデルは単純なインタフェースの小さなアプレットに関しては見事に機能しましたが、大きな Java プログラムについては次の理由でうまく対応していません:


delegation モデル

JDK1.1 では、次のことを行うために新しい delegation ベースイベントモデルを導入します:

設計目標

AWT の新モデルの主要な設計目標は、次のとおりです:

注: これらの目標は AWT に対する特定の観点から説明しています。このモデルはまた JavaBeans アーキテクチャを取り込むように設計しているため、JavaBeans の観点からの設計目標については、「JavaBeans仕様」の「イベント」節にて説明し、それは今説明する目標とはやや違っている可能性があります。

delegation モデルの概要

イベント型は、 java.util.EventObject にルートを持つクラス階層にカプセル化されます 。イベントは、リスナのメソッドを起動し、生成するイベント型を定義するイベントサブクラスのインスタンスに渡すことによって、「ソース」オブジェクトから「リスナ」オブジェクトへ伝搬されます。

「リスナ」は、一般的な java.util.EventListener を拡張した固有の EventListener インタフェースを実装するオブジェクトです。 EventListener インタフェースは、インタフェースが処理する固有のイベント型各々に応じて、イベントソースが起動する 1 つ以上のメソッドを定義します。

「イベントソース」はイベントを発生または「発火」させるオブジェクトです。このソー スは、イベントに固有のリスナを登録するために使用する、 set<EventType>Listener (単一キャスト用) や add<EventType>Listener (マルチキャスト用) メソッドのセットを提供することによって、それが放出するイベントのセットを定義します。

AWT プログラムにおいて、イベントソースは通常 GUI コンポーネントであり、リスナは一般的には、アプリケーションがイベントのフロー/処理を制御するための適当なリスナ (またはリスナのセット)を実装する「アダプタ」オブジェクトです。リスナオブジェクトは、また GUI オブジェクトをお互いにフックするための 1 つ以上のリスナインタフェースを実装する別の AWT コンポーネントでもありえます。

イベント階層

イベントはもはや、( java.awt.Event のような、)数字 ID を持つ単一の「イベント」クラスで表せません。それは代わりにイベントクラスの階層で表されます。各イベントクラスは、そのイベント型またはイベント型の関連グループを表すデータによって定義します。

単一のイベントタイプは、1 つ以上のイベント型 (すなわち、MouseEvent はマウスのアップ、ダウン、ドラッグ、移動を表すなど) を表すために使用されるので、いくつかのイベントクラスには、また固有のイベント型に割り当てる "id" (そのクラスで一意)がある可能性があります。

イベントクラスには public フィールドはありません。イベント内のデータは、適当な get<Attr>()/set<Attr>() メソッド (ここでは、 set<Attr>() はリスナが修正できるイベントの属性のためにだけ存在する)によって完全にカプセル化されます。

これらは AWT が定義する具体的なセットですが、プログラムは java.util.EventObject または AWT イベントクラスの1つのどちらかをサブクラス化することによって、自由に自分自身のイベント型を定義できます。プログラムは次の定数より大きいイベント ID 値を選択する必要があります:

        java.awt.AWTEvent.RESERVED_ID_MAX

低レベルイベントとセマンティックイベントの対比

AWT は 2 つのイベントの概念的型を提供します: 低レベルとセマンティックです。

低レベルイベントは、画面上のビジュアルコンポーネントの低レベル入力、またはウィンドウシステムの発生を表すものです。 AWT が定義する低レベルイベントクラスは次のとおりです:

	java.util.EventObject
		java.awt.AWTEvent 
		    java.awt.event.ComponentEvent (コンポーネントのサイズ再設定、移動など)
			java.awt.event.FocusEvent (コンポーネントがフォーカスを得る、フォーカスを失う)
			java.awt.event.InputEvent
			    java.awt.event.KeyEvent (コンポーネントがキー押し下げ、キー開放をされるなど)
			    java.awt.event.MouseEvent (コンポーネントがマウスダウン、マウス移動をされるなど)
			java.awt.event.ContainerEvent
			java.awt.event.WindowEvent

セマンティックイベントは、ユーザインタフェースコンポーネントのモデルのセマン ティックスをカプセル化するために、上位レベルで定義されます。 AWT が定義するセマンティックイベントクラスは次のとおりです:

	java.util.EventObject
	    java.awt.AWTEvent
		java.awt.event.ActionEvent (「コマンドを実行する」)
		java.awt.event.AdjustmentEvent (「値が調整された」)
		java.awt.event.ItemEvent (「項目の状態が変わった」)
		java.awt.event.TextEvent (「テキストオブジェクトの値が変わった」)

これらのセマンティックイベントは、固有の画面ベースのコンポーネントクラスに結び付けられたものでなく、同様のセマンティックモデルを実装するコンポーネントのセットを介して適用される、ということに注意してください。例えば、「ボタン」オブジェクトは、押されたとき「アクション」イベントを発火し、「リスト」オブジェクトは、項目がダブルクリックされたとき「アクション」イベントを発火します。 MenuItem は、メニューから選択されたときに「アクション」イベントを発火し、ビジュアルでない「タイマ」オブジェクトは、タイマが切れたときに「アクション」イベントを発火します(後者は仮説)。

イベントリスナ

EventListener インタフェースは、通常イベントクラスが表す別個のイベント型各々に対して、それぞれのメソッドを持っています。そこで本質的に、特定のイベントセマンティックスが、「イベント」クラスおよび対になる EventListener の特定のメソッドの組み合わせによって定義されます。例えば、FocusEventListener インタフェースは focusGained() および focusLost() の2 つのメソッドを定義します。これらはそれぞれ、 FocusEvent クラスが表す各イベント型に対応します。

この API は、「リスナ」インタフェース型の合理的な細分性を提供すること、およびイベント型毎に個別のインタフェースを提供しないことの間のバランスを定義しようとします。

AWT が定義する低レベルリスナインタフェースは次のとおりです:

	java.util.EventListener
	    java.awt.event.ComponentListener 
	    java.awt.event.ContainerListener 
	    java.awt.event.FocusListener 
	    java.awt.event.KeyListener 
	    java.awt.event.MouseListener 
	    java.awt.event.MouseMotionListener 
	    java.awt.event.WindowListener

AWT が定義するセマンティックリスナインタフェースは次のとおりです:

	java.util.EventListener
	    java.awt.event.ActionListener 
	    java.awt.event.AdjustmentListener 
	    java.awt.event.ItemListener 
	    java.awt.event.TextListener 

イベントソース

イベントソースにより発火したイベントは、そのオブジェクトの特定のメソッドによって定義されるため、どのイベントをオブジェクトがサポートするかは API ドキュメンテーションから (実行時内部検査技術の使用によっても)完全に明確になります。

すべての AWT イベントソースは、リスナのマルチキャストモデルをサポートします。こ れは、複数のリスナを、単一ソースに対して追加および削除できることを意味します。 API は、与えられたソースの与えられたイベントに対する登録リスナのセットにイベントが配布される順番について、なんの保証もしません。 さらに、そのプロパティの変更 (setXXX() メソッド経由で) を許すイベントはすべて、明示的にコピーされ、各リスナは元のイベントの複製を受け取ります。イベントがリスナに配布される順番がプログラムの因数である場合、ソースに登録されている単一のリスナから配布されるリスナの連鎖を解く必要があります(イベントデータが単一のオブジェクトにカプセル化されるという事実によって、イベントの伝搬が極めて簡単になります)。

イベントの配布は同期的(1.0の handleEvent() のように)ですが、プログラムではリスナセットへのイベントの配布が同じスレッドで起こると仮定してはいけません。

再度、低レベルイベントとセマンティックイベントの違いを述べます。低レベルにつ いては、イベントは画面上の実際のコンポーネントに強く結び付けられているため、ソースはビジュアルコンポーネントクラス (Button、Scrollbarなど)の 1 つです。低レベルリスナは次のコンポーネント上で定義されます:

	addComponentListener(ComponentListener l)
	addFocusListener(FocusListener l)
	addKeyListener(KeyListener l)
	addMouseListener(MouseListener l)
	addMouseMotionListener(MouseMotionListener l)
	addContainerListener(ContainerListener l)
	addWindowListener(WindowListener l)
	addWindowListener(WindowListener l)

セマンティックイベントについては、ソースは通常セマンティックモデルを表す上位 レベルインタフェースです (そしてこの上位レベルインタフェースは、このモデルを使用するコンポーネントが共通に実装します)。 AWT コンポーネント用に定義されるセマンティックリスナは次のとおりです:

	addActionListener(ActionListener l)
	addItemListener(ItemListener l)
	addItemListener(ItemListener l)
	addItemListener(ItemListener l)
	addActionListener(ActionListener l)
	addItemListener(ItemListener l)
	addActionListener(ActionListener l)
	addAdjustmentListener(AdjustmentListener l)
	addTextListener(TextListener l)
	addActionListener(ActionListener l)
	addTextListener(TextListener l)

アダプタ

多くの EventListener インタフェースは、複数のイベントサブ型を受け付けるように設計されているため (すなわち、MouseListener マウスダウン、マウスアップ、マウス入力などを受け付ける)、AWT は各リスナインタフェースを実装する abstract な「アダプタ」クラスのセットを提供します。これによって、プログラムは「アダプタ」を簡単にサブクラス化し、関心のあるイベント型を表すメソッドだけをオーバーライドできるようになります。

AWT が提供する「アダプタ」クラスは次のとおりです:

	java.awt.event.ComponentAdapter
	java.awt.event.FocusAdapter
	java.awt.event.KeyAdapter
	java.awt.event.MouseAdapter
	java.awt.event.MouseMotionAdapter
	java.awt.event.WindowAdapter

注: セマンティックリスナに提供されるデフォルトの「アダプタ」はありません。これらリスナの各々に単一のメソッドしか入っておらず、アダプタは実際の値を提供しないからです。

パフォーマンスのためのフィルタリング

新しいモデルの大きな利点の 1 つは、リスナが固有のイベント型を処理するために登録 されているので、ツールキットがイベントにフィルタをかけ、コンポーネントが関心を持つイベントだけを配布することが、相対的に簡単なことです。古いモデルではこうはならないのです。フィルタリングは、特にマウス移動などの高周波数型のイベントに関してパフォーマンスを向上させる必要があります。

すべてのプラットフォームは、イベントトラフィックの削減によるある程度のパフォ ーマンスの向上があるはずですが、Solaris 実装はネットワークベースのウィンドウシステムなので向上も例外的なはずです。

コード例

次に新しいモデルを使用したサンプルコードのいくつかを示します:
	
import java.awt.*;
import java.awt.event.*;

public class App {
    public void search() { 
        /* do search operation ...*/ 
        System.out.println("Searching...");
    }
    public void sort() { 
        /* do sort operation ...*/ 
        System.out.println("Sorting....");
    }

    static public void main(String args[]) {
       App app = new App();
       GUI gui = new GUI(app);
    }
}

class Command implements ActionListener  {
    static final int SEARCH = 0;
    static final int SORT = 1;
    int id;
    App app;

    public Command(int id, App app) {
        this.id = id;
        this.app = app;
    }

    public void actionPerformed(ActionEvent e) {
        switch(id) {
          case SEARCH: 
            app.search();
            break;
          case SORT:
            app.sort();
            break;
        }
    }
}

class GUI {

    public GUI(App app) {
        Frame f = new Frame();
        f.setLayout(new FlowLayout());          

        Command searchCmd = new Command(Command.SEARCH, app);
        Command sortCmd = new Command(Command.SORT, app);

        Button b;
        f.add(b = new Button("Search"));
        b.addActionListener(searchCmd);
        f.add(b = new Button("Sort"));
        b.addActionListener(sortCmd);

        List l;
        f.add(l = new List());
        l.add("Alphabetical");
        l.add("Chronological");
        l.addActionListener(sortCmd);
        f.pack();

        f.show();
    }
}

この例および古いモデルでの実装の仕方の次のような違いに特に注意してください:


拡張コンポーネントにおけるイベント処理

サブクラス化を経由してコンポーネントクラスを拡張している Java プログラムにとっては、イベントに応答するために別個のリスナオブジェクトの登録を要求することは負荷が大きいと思われます。そのためこのケースでは、各コンポーネントが、イベントを実際にリスナ (存在すれば) に送る固有の protected メソッド (サブクラスがオーバーライドできる) を提供すると AWT が定義します。こうしてサブクラスは、イベントをプロセスするためにこれらメソッドの 1 つを簡単にオーバーライドできます。

これを可能な限り柔軟にするために、このイベント処理機能は 2 つのレベルで提供されます。第 1 はすべてのコンポーネント上の単一メソッドです:

     protected void processEvent(AWTEvent)
コンポーネントのイベントはすべて最初にこのメソッドを通して集められ、サブクラスは単一の場所ですべてのイベントを処理する選択ができます (1.0モデルの "handleEvent" と同様で、主な違いはイベントが新しいモデルの包含階層まで伝搬されないこと)。

イベント処理の第 2 のオプションは、イベントクラスレベルで提供されます; そのコン ポーネントが処理するイベントの各クラスには、次の別個のメソッドがあります:

     protected void processEventClass(EventClass)
例えば、java.awt.List コンポーネントは次のイベントクラス処理メソッドを持っていま す:
     protected void processActionEvent(ActionEvent e)

     protected void processItemEvent(ItemEvent e)
デフォルトでは、単一の processEvent メソッドが適当なイベントクラス処理メソッドを起動します。デフォルトのイベントクラス処理メソッドは、登録されているすべてのリスナを起動します。これらのメソッドは AWT コンポーネントのイベント処理において重大な関数を実行し、これをオーバーライドした場合、忘れずにスーパークラスメソッドを自分自身の内部で呼び出すようにする必要がある!ということを覚えておくことが重要です。

イベント型の選択

リスナモデルの目標の 1 つは、コンポーネントが関心のないイベントを配布しないことで、パフォーマンスを向上することです。デフォルトでは、リスナ型がコンポーネントに登録されていない場合、これらのイベントは配布されず、これらの処理メソッドも呼び出されません。イベント処理のこの拡張機構を使用している場合、コンポーネントが受け取りたいイベント固有型を選択する必要があります(リスナが登録されていない場合)。これは java.awt.Component の次のメソッドを使用して行うことができます:
     protected final void enableEvents(long eventsToEnable)
このメソッドのパラメータは、有効にしたいイベント型のビットマスクです。イベントマスクは java.awt.AWTEvent で定義されます 。 このマスクの変更は、リスナへのイベントの配布に影響しないことに注意してください。これはコンポーネントの処理メソッドへの配布を制御するだけです。要するに、processEvent() へ配布されるイベントのセットは、リスナが登録され、イベント型が enableEvents() 経由で明示的にオンになっているイベント型の連合によって定義されます。

拡張機構を使用する例

この拡張機構の使用方法の例を次に示します。例えば、java.awt.Canvas のサブクラスが、キーボードフォーカスを受け取るか失うときにあるビジュアルフィードバックをレンダしたい場合、次のようにすることができます。
     
    public class TextCanvas extends Canvas {
	  boolean haveFocus = false;

	  public TextCanvas() {
	      enableEvents(AWTEvent.FOCUS_EVENT_MASK); // ensure we get focus events
	      ...
	  }
	  protected void processFocusEvent(FocusEvent e) {
	      switch(e.getID()) {
	        case FocusEvent.FOCUS_GAINED:
	          haveFocus = true;
	          break;
	        case FocusEvent.FOCUS_LOST:
	          haveFocus = false;
	      }
	      repaint(); // need to repaint with focus feedback on or off...

	      super.processFocusEvent(e); // let superclass dispatch to listeners
	  }
	  public void paint(Graphics g) {
	      if (haveFocus) {
	          // render focus feedback...
	      }
	  }
	  ...rest of TextCanvas class...
    }

注意書

一般的に、ほとんどの基本的なイベント処理ニーズに対して delegation ベースのリスナモデルを使用し、コンポーネントの外観と動作を本当に拡張するときには上記の使用を留保することを薦めます。これは、上記機構が1.0 イベントモデルと同じような問題 (処理メソッドが複雑でエラーを生じ易い、"super.processEvent" の呼び出しを忘れるなど) を持っており、なにをしようとしているのか明確に理解していない場合、プログラムが期待どおりに動作しない可能性があります!

上記アプローチの代替としては、コンポーネントサブクラスが受け取りたいイベントの特定のリスナインタフェースを実装するようにし、自分自身をリスナとして登録することです。例えば、上記コード例を次のように書き直します:

     
    public class TextCanvas extends Canvas implements FocusListener {
	  boolean haveFocus = false;

	  public TextCanvas() {
	      addFocusListener(this); // ensure we get focus events
	  }
	  public void focusGained(FocusEvent e) {
	       haveFocus = true;
	       repaint();
          }								
	  public void focusLost(FocusEvent e) {
	       haveFocus = false;	      
	       repaint(); 
	  }	      
	  public void paint(Graphics g) {
	      if (haveFocus) {
	          // render focus feedback...
	      }
	  }
	  ...rest of TextCanvas class...
    }

イベントの消費

プログラムで、あるイベント型をコンポーネントが通常処理しないようにする必要のあるケースがあります(すなわち、ユーザがグラフィカルにボタンを移動できるようにビルダがマウスイベントの使用を希望し、マウスの押し下げがボタンを「押す」ことにならないようにする)。

入力イベントだけに明示的にこの機能を有効化するために、java.awt.event.InputEvent の次の 2 つのメソッドを提供します:

     public void consume()

     public boolean isConsumed()

あるオブジェクトが入力イベントを「消費する」場合、これは厳密にはイベントをデフォルトのやり方で処理してはいけないという、ソースコンポーネントへの指摘になることに注意してください (すなわち、「ボタン」の mousePressed イベントを消費すると、それをアクティブ化できなくなります)。連鎖のリスナがそのイベントを消費するかどうかに関わらず、イベントは登録リスナすべてに依然配布されます。

イベントキュー

1.1 イベントモデルの別の機能には、次のイベントキュークラスの追加があります:
     java.awt.EventQueue
このクラスはキューを操作するための多くの public インスタンスメソッドを提供します:
     public synchronized void postEvent(AWTEvent e)

     public synchronized AWTEvent getNextEvent()

     public synchronized AWTEvent peekEvent()

     public synchronized AWTEvent peekEvent(int eventID)
プログラムは実際このクラスを使用し、イベントを非同期的にポストするために自分のイベントキューインスタンスをインスタンスにします。 EventQueue クラスは、イベントを適切に配布するために自動的に内部スレッドをインスタンスにします。

デフォルトの JDK 実装において、コンポーネントに生成されるすべてのイベントは、ターゲットコンポーネントに配布される前に、最初に特別な「システム」 EventQueue インスタンスにポストされます。

「ツールキット」クラスは、システム EventQueueインスタンスの処理にアクセスする次のメソッドを提供します:

     public final EventQueue getSystemEventQueue()

認可されていないアプレットが自由にシステムイベントキューを操作することは、明らかにセキュリティの問題であり、そのため getSystemEventQueue() メソッドは、システムキューへのアプレットの直接アクセスを認めない SecurityManager のチェックによって保護されます。アプレットは、自分の包含階層にスコープされるイベントキューにもアクセスを望むと認識されており、次のバージョンでこれが可能になるようなアーキテクチャ作りに取り組んでいるところです。


古いモデルとの互換性

意図しているのは、1.1 バージョンに対して古いモデルで書かれたプログラムのバイナリ互換を維持することです。しかし、新しい Java プログラムは新モデルに移行するよう強く勧めます。単一のアプレット内で 2 つのモデルの明らかな混在は推奨しません。 しかし、新モデルに対してコード化するプログラムは、古いモデルを依然使用する既存の GUI クラスの使用を必要とし、または希望しているものと認識されています。そこで、最善を尽くしてこれが確実に機能するようにしています(例えば、hotjavaブラウザは、 1.0 および 1.1 スタイルアプレットをロードできる必要のある java アプリケーションです)。

これが機能する方法は、AWT がコンポーネントを 1.0 イベントモデル「ターゲット」か、 または 1.1 イベントモデル「ソース」のどちらかとして、ただし両方共にではなく認識することです。コンポーネントは次の条件に適合することで、 1.1 イベントモデル「ソース」であると認識されます:

  1. リスナ (種類は問わず)が登録されていること
  2. イベント型 (種類は問わず) が、 enableEvents() の呼び出しによって明示的に有効になっていること
さもなければ、コンポーネントは 1.0 イベントモデル「ターゲット」として扱われ、すべ てのイベントが以前と同様に 1.0 handleEvent メソッドに配布されます。

これは「すべてか、ゼロか」の違いであり、一度 AWT が、コンポーネントを特定のイベントモデル型であると決定すると、そのコンポーネントのすべてのイベントがそのコンテキストでプロセスされることに注意してください。例えば、TextField オブジェクトが FocusListener だけを登録している場合、フォーカスイベントだけが 1.1 機構のテキストフィールドに送られ、古い 1.0 handleEvent メソッドは決して呼び出されません (他のイベント型でさえも!)。そこで、異なるモデルを使用するコンポーネントを組み合わせることは可能ですが、両方のモデルを混在する単一のコンポーネントは得られません。

2 つのモデルの重要な違いは、古いモデルがイベントを包含階層にまで自動的に伝搬するのに対して、新しいモデルはイベントをこのように伝搬しないことです。これが互換性に対して機能するには、イベントが 1.0 イベントモデル「ターゲット」であるコンポーネント上で生成される場合、その祖先のコンテナのイベントモデル型に関わらず、1.0 の仕方でその階層まで伝搬されることです。イベントが 1.1 イベントモデル「ソース」で 生成される場合、そのイベントはその祖先のコンテナのイベントモデル型に関わらず、その階層まで伝搬されません。



フィードバックは java-awt@java.sun.com 宛てに送ってください。
Copyright (C) 1996, 1997 Sun Microsystems, Inc. All rights reserved.