最終更新日: 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 仕様」の「イベント」節で説明し、上記の目標とはやや異なっている可能性があります。
委譲モデルの概要
イベント型は、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 または 1 つの AWT イベントクラスのどちらかをサブクラス化することによって、自由に自分自身のイベント型を定義できます。プログラムは次の定数より大きいイベント 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.ContainerAdapter
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();
}
}
この例と古いモデルでの実装の仕方に、次のような違いがあることに注意してください。
これを可能な限り柔軟にするために、このイベント処理機能は 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 コンポーネントのイベント処理において重大な関数を実行しているので、これをオーバーライドした場合は、必ず、スーパークラスメソッドを自分自身の内部で呼び出すようにする必要があるということを覚えておくことが重要です。
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 イベントを消費すると、それをアクティブ化できなくなります。連鎖しているリスナーの 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 のチェックによって保護されます。自分の包含階層にスコープされるイベントキューにアプレットがアクセスすることが希望されると考えられるので、次のバージョンでこれが可能になるようなアーキテクチャ作りに取り組んでいます。
これが機能するには、AWT がコンポーネントを 1.0 イベントモデル「ターゲット」か、または 1.1 イベントモデル「ソース」のどちらかとして (ただし両方ではなく) 認識することです。コンポーネントは次の条件に合うと、1.1 イベントモデル「ソース」であると認識されます。
これは「すべてか、ゼロか」の違いであり、一度 AWT が、コンポーネントを特定のイベントモデル型であると決定すると、そのコンポーネントのすべてのイベントがそのコンテキストで処理されることに注意してください。たとえば、TextField オブジェクトが FocusListener だけを登録している場合、フォーカスイベントだけが 1.1 機構のテキストフィールドに送られ、古い 1.0 handleEvent メソッドは決して呼び出されません (他のイベント型でさえも)。つまり、異なるモデルを使用しているコンポーネントを組み合わせることは可能ですが、両方のモデルが混在する単一のコンポーネントは得られません。
2 つのモデルの重要な違いは、古いモデルがイベントを包含階層にまで自動的に伝搬するのに対して、新しいモデルはイベントをこのように伝搬しないことです。これが互換性に対して機能するには、イベントが 1.0 イベントモデル「ターゲット」であるコンポーネント上で「生成される」場合、その祖先のコンテナのイベントモデル型にかかわらず、1.0 のやり方でその階層まで伝搬されることです。イベントが 1.1 イベントモデル「ソース」で「生成される」場合、そのイベントはその祖先のコンテナのイベントモデル型にかかわらず、その階層まで伝搬されません。