Java AWT:委譲イベントモデル


最終更新日: 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 プログラムでは、次の理由によりうまく対応しません。


委譲モデル

JavaTM プラットフォーム Version 1.1 からは、次のことを行うために、AWT に新しい委譲ベースイベントモデルが導入されました。

設計目標

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

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

委譲モデルの概要

イベント型は、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 または 1 つの AWT イベントクラスのどちらかをサブクラス化することによって、自由に自分自身のイベント型を定義できます。プログラムは次の定数より大きいイベント 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 の特定のメソッドの組み合わせによって定義されます。たとえば、FocusListener インタフェースは 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 は各リスナーインタフェースを実装する抽象的な「アダプタ」クラスのセットを提供します。これによって、プログラムは「アダプタ」を簡単にサブクラス化し、目的のイベント型を表すメソッドだけをオーバーライドできるようになります。

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

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

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

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

新しいモデルの大きな利点の 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 process>EventClass<(>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...
    }

注意

通常は、ほとんどの基本的なイベント処理ニーズに対しては委譲ベースのリスナーモデルを使用し、コンポーネントの外観と動作を実際に拡張するときのために、上記の使用を留保することをお勧めします。これは、上記の機構が、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.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 © 1996, 1997 Sun Microsystems, Inc. All rights reserved.