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.0 イベントモデルに関する問題
上記モデルは単純なインタフェースの小さなアプレットに関しては見事に機能しましたが、大きな Java プログラムについては次の理由でうまく対応していません:
注: これらの目標は 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 コンポーネントでもありえます。
単一のイベントタイプは、1 つ以上のイベント型 (すなわち、MouseEvent はマウスのアップ、ダウン、ドラッグ、移動を表すなど) を表すために使用されるので、いくつかのイベントクラスには、また固有のイベント型に割り当てる "id" (そのクラスで一意)がある可能性があります。
イベントクラスには public フィールドはありません。イベント内のデータは、適当な get<Attr>()/set<Attr>() メソッド (ここでは、 set<Attr>() はリスナが修正できるイベントの属性のためにだけ存在する)によって完全にカプセル化されます。
これらは AWT が定義する具体的なセットですが、プログラムは java.util.EventObject または AWT イベントクラスの1つのどちらかをサブクラス化することによって、自由に自分自身のイベント型を定義できます。プログラムは次の定数より大きいイベント ID 値を選択する必要があります:
java.awt.AWTEvent.RESERVED_ID_MAX
低レベルイベントは、画面上のビジュアルコンポーネントの低レベル入力、またはウィンドウシステムの発生を表すものです。 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 は、メニューから選択されたときに「アクション」イベントを発火し、ビジュアルでない「タイマ」オブジェクトは、タイマが切れたときに「アクション」イベントを発火します(後者は仮説)。
この 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
すべての 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)
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
注: セマンティックリスナに提供されるデフォルトの「アダプタ」はありません。これらリスナの各々に単一のメソッドしか入っておらず、アダプタは実際の値を提供しないからです。
すべてのプラットフォームは、イベントトラフィックの削減によるある程度のパフォ
ーマンスの向上があるはずですが、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();
}
}
この例および古いモデルでの実装の仕方の次のような違いに特に注意してください:
これを可能な限り柔軟にするために、このイベント処理機能は 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 コンポーネントのイベント処理において重大な関数を実行し、これをオーバーライドした場合、忘れずにスーパークラスメソッドを自分自身の内部で呼び出すようにする必要がある!ということを覚えておくことが重要です。
protected final void enableEvents(long eventsToEnable)
このメソッドのパラメータは、有効にしたいイベント型のビットマスクです。イベントマスクは java.awt.AWTEvent で定義されます
。 このマスクの変更は、リスナへのイベントの配布に影響しないことに注意してください。これはコンポーネントの処理メソッドへの配布を制御するだけです。要するに、processEvent() へ配布されるイベントのセットは、リスナが登録され、イベント型が enableEvents() 経由で明示的にオンになっているイベント型の連合によって定義されます。
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...
}
上記アプローチの代替としては、コンポーネントサブクラスが受け取りたいイベントの特定のリスナインタフェースを実装するようにし、自分自身をリスナとして登録することです。例えば、上記コード例を次のように書き直します:
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 イベントを消費すると、それをアクティブ化できなくなります)。連鎖のリスナがそのイベントを消費するかどうかに関わらず、イベントは登録リスナすべてに依然配布されます。
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 のチェックによって保護されます。アプレットは、自分の包含階層にスコープされるイベントキューにもアクセスを望むと認識されており、次のバージョンでこれが可能になるようなアーキテクチャ作りに取り組んでいるところです。
これが機能する方法は、AWT がコンポーネントを 1.0 イベントモデル「ターゲット」か、 または 1.1 イベントモデル「ソース」のどちらかとして、ただし両方共にではなく認識することです。コンポーネントは次の条件に適合することで、 1.1 イベントモデル「ソース」であると認識されます:
これは「すべてか、ゼロか」の違いであり、一度 AWT が、コンポーネントを特定のイベントモデル型であると決定すると、そのコンポーネントのすべてのイベントがそのコンテキストでプロセスされることに注意してください。例えば、TextField オブジェクトが FocusListener だけを登録している場合、フォーカスイベントだけが 1.1 機構のテキストフィールドに送られ、古い 1.0 handleEvent メソッドは決して呼び出されません (他のイベント型でさえも!)。そこで、異なるモデルを使用するコンポーネントを組み合わせることは可能ですが、両方のモデルを混在する単一のコンポーネントは得られません。
2 つのモデルの重要な違いは、古いモデルがイベントを包含階層にまで自動的に伝搬するのに対して、新しいモデルはイベントをこのように伝搬しないことです。これが互換性に対して機能するには、イベントが 1.0 イベントモデル「ターゲット」であるコンポーネント上で生成される場合、その祖先のコンテナのイベントモデル型に関わらず、1.0 の仕方でその階層まで伝搬されることです。イベントが 1.1 イベントモデル「ソース」で 生成される場合、そのイベントはその祖先のコンテナのイベントモデル型に関わらず、その階層まで伝搬されません。