Javaのヒント35:Javaで新しいイベントタイプを作成する

JDK 1.1は、委任イベントモデルの導入によりイベント処理を確実に合理化しましたが、開発者が独自のイベントタイプを作成することは容易ではありません。ここで説明する基本的な手順は、実際にはかなり簡単です。簡単にするために、イベントの有効化とイベントマスクの概念については説明しません。さらに、この手順を使用して作成されたイベントはイベントキューに投稿されず、登録済みのリスナーでのみ機能することを知っておく必要があります。

現在、Javaコアは次のように定義された12のイベントタイプで構成されていjava.awt.eventsます。

  • ActionEvent
  • AdjustEvent
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

新しいイベントタイプの作成は簡単な作業ではないため、コアJavaの一部であるイベントを調べる必要があります。可能であれば、新しいタイプを作成するのではなく、それらのタイプを使用するようにしてください。

ただし、新しいコンポーネント用に新しいイベントタイプを開発する必要がある場合があります。この説明では、新しいイベントタイプを作成する方法を示す手段として、単純なコンポーネントであるウィザードパネルの例を使用します。

ウィザードパネルは、単純なウィザードインターフェイスを実装します。コンポーネントは、[次へ]ボタンを使用して進めることができるカードパネルで構成されています。戻るボタンを使用すると、前のパネルに戻ることができます。FINISHボタンとCANCELボタンも用意されています。

コンポーネントを柔軟にするために、すべてのボタンによって実行されるアクションを完全に制御して、それを使用する開発者に提供したいと思いました。たとえば、[次へ]ボタンを押すと、開発者は、次のコンポーネントに進む前に、現在表示されているコンポーネントに必要なデータが入力されているかどうかを最初に確認できるはずです。

独自のイベントタイプを作成するには、主に5つのタスクがあります。

  • イベントリスナーを作成する

  • リスナーアダプタを作成する

  • イベントクラスを作成する

  • コンポーネントを変更する

  • 複数のリスナーの管理

これらの各タスクを順番に調べてから、すべてをまとめます。

イベントリスナーを作成する

特定のアクションが発生したことをオブジェクトに通知する1つの方法(および多くの方法)は、登録済みのリスナーに配信できる新しいイベントタイプを作成することです。ウィザードパネルの場合、リスナーはボタンごとに1つずつ、4つの異なるイベントケースをサポートする必要があります。

まず、リスナーインターフェイスを作成します。ボタンごとに、次の方法でリスナーメソッドを定義します。

インポートjava.util.EventListener; パブリックインターフェイスWizardListenerはEventListenerを拡張します{publicabstract void nextSelected(WizardEvent e); public abstract void backSelected(WizardEvent e); public abstract void cancelSelected(WizardEvent e); public abstract void finishSelected(WizardEvent e); }

各メソッドは1つの引数を取ります:WizardEvent次に定義されます。インターフェイスは拡張EventListenerされ、このインターフェイスをAWTリスナーとして識別するために使用されることに注意してください。

リスナーアダプタを作成する

リスナーアダプタの作成はオプションの手順です。AWTでは、リスナーアダプターは、特定のリスナータイプのすべてのメソッドにデフォルトの実装を提供するクラスです。java.awt.eventパッケージ内のすべてのアダプタクラスは、何もしない空のメソッドを提供します。以下のアダプタクラスはWizardListener次のとおりです。

パブリッククラスWizardAdapterはWizardListenerを実装します{publicvoid nextSelected(WizardEvent e){} public void backSelected(WizardEvent e){} public void cancelSelected(WizardEvent e){} public void finishSelected(WizardEvent e){}} 

ウィザードリスナーとなるクラスを作成する場合、WizardAdapter対象のリスナーメソッドのみを拡張して実装を提供(またはオーバーライド)することができます。これは厳密には便利なクラスです。

イベントクラスを作成する

次のステップは、Eventここで実際のクラスを作成することですWizardEvent

import java.awt.AWTEvent; public class WizardEvent extends AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; public static final int NEXT_SELECTED = WIZARD_FIRST; public static final int BACK_SELECTED = WIZARD_FIRST + 1; public static final int CANCEL_SELECTED = WIZARD_FIRST + 2; public static final int FINISH_SELECTED = WIZARD_FIRST + 3; public static final int WIZARD_LAST = WIZARD_FIRST + 3; public WizardEvent(Wizard source、int id){super(source、id); }}

2つの定数、WIZARD_FIRSTとはWIZARD_LAST、このイベントのクラスで使用されるマスクの包括的な範囲をマーク。イベントIDは、RESERVED_ID_MAXクラスの定数を使用AWTEventして、AWTによって定義されたイベントID値と競合しないIDの範囲を決定することに注意してください。より多くのAWTコンポーネントが追加さRESERVED_ID_MAXれると、将来的に増加する可能性があります。

残りの4つの定数は、ウィザードの機能で定義されているように、それぞれが異なるアクションタイプに対応する4つのイベントIDを表します。

イベントIDとイベントソースは、ウィザードイベントコンストラクターの2つの引数です。イベントソースはタイプである必要がありますWizard-つまり、イベントが定義されているコンポーネントタイプです。その理由は、ウィザードパネルのみがウィザードイベントのソースになることができるということです。WizardEventクラスが拡張することに注意してくださいAWTEvent

コンポーネントを変更する

次のステップは、コンポーネントにメソッドを装備して、新しいイベントのリスナーを登録および削除できるようにすることです。

イベントをリスナーに配信するには、通常、適切なイベントリスナーメソッドを呼び出します(イベントマスクによって異なります)。アクションリスナーを登録して、[次へ]ボタンからアクションイベントを受信し、それらを登録済みWizardListenerオブジェクトに中継できます。actionPerformedNEXT(または他のアクション)ボタンのアクションリスナーのメソッドは、次のように実装できます。

public void actionPerformed(ActionEvent e){//リスナーが登録されていない場合は何もしませんif(wizardListener == null)return; WizardEvent w; ウィザードソース= this; if(e.getSource()== nextButton){w = new WizardEvent(source、WizardEvent.NEXT_SELECTED); WizardListener.nextSelected(w); } //残りのウィザードボタンも同様の方法で処理します}

注:上記の例では、Wizardパネル自体が[次へ]ボタンのリスナーです

NEXTボタンが押されると、押されWizardEventているNEXTボタンに対応する適切なソースとマスクを使用して新しいものが作成されます。

例では、行

 WizardListener.nextSelected(w); 

wizardListenerプライベートメンバー変数でWizardあり、タイプがであるオブジェクトを参照しますWizardListener。このタイプは、新しいコンポーネントイベントを作成する最初のステップとして定義されています。

一見すると、上記のコードはリスナーの数を1つに制限しているように見えます。プライベート変数wizardListenerは配列ではなく、1回のnextSelected呼び出しのみが行われます。上記のコードが実際にその制限を課さない理由を説明するために、リスナーがどのように追加されるかを調べてみましょう。

イベント(事前定義または新規)を生成する新しい各コンポーネントは、2つのメソッドを提供する必要があります。1つはリスナーの追加をサポートし、もう1つはリスナーの削除をサポートします。Wizardクラスの場合、これらのメソッドは次のとおりです。

public synchronized void addWizardListener(WizardListener l){wizardListener = WizardEventMulticaster.add(wizardListener、l); } public synchronized void removeWizardListener(WizardListener l){wizardListener = WizardEventMulticaster.remove(wizardListener、l); }

どちらのメソッドも、クラスの静的メソッドメンバーを呼び出しますWizardEventMulticaster

複数のリスナーの管理

While it is possible to use a Vector to manage multiple listeners, JDK 1.1 defines a special class for maintaining a listener list: AWTEventMulticaster. A single multicaster instance maintains references to two listener objects. Because the multicaster is also a listener itself (it implements all listener interfaces), each of the two listeners it keeps track of can also be multicasters, thus creating a chain of event listeners or multicasters:

If a listener is also a multicaster, then it represents a link in the chain. Otherwise, it is merely a listener and is thus the last element in the chain.

Unfortunately, it is not possible simply to reuse the AWTEventMulticaster to handle event multicasting for new event types. The best that can be done is to extend the AWT multicaster, although this operation is rather questionable. AWTEventMulticaster contains 56 methods. Of these, 51 methods provide support for the 12 event types and their corresponding listeners that are part of AWT. If you subclass AWTEventMulticaster, you will never use them anyway. Out of the remaining five methods, addInternal(EventListener, EventListener), and remove(EventListener) need to be recoded. (I say recoded because in AWTEventMulticaster, addInternal is a static method and therefore cannot be overloaded. For reasons unknown to me at this time, remove makes a call to addInternal and it needs to be overloaded.)

Two methods, save and saveInternal, provide support for object streaming and can be reused in the new multicaster class. The last method that supports listener remove routines, removeInternal, can also be reused, provided that new versions of remove and addInternal have been implemented.

For the sake of simplicity, I am going to subclass AWTEventMulticaster, but with very little effort, it is possible to code remove, save, and saveInternal and have a fully functional, standalone event multicaster.

Here is the event multicaster as implemented to handle WizardEvent:

import java.awt.AWTEventMulticaster; import java.util.EventListener; public class WizardEventMulticaster extends AWTEventMulticaster implements WizardListener { protected WizardEventMulticaster(EventListener a, EventListener b) { super(a, b); } public static WizardListener add(WizardListener a, WizardListener b) { return (WizardListener) addInternal(a, b); } public static WizardListener remove(WizardListener l, WizardListener oldl) { return (WizardListener) removeInternal(l,oldl); } public void nextSelected(WizardEvent e) { //casting exception will never occur in this case //casting _is_ needed because this multicaster may //handle more than just one listener if (a != null) ((WizardListener) a).nextSelected(e); if (b != null) ((WizardListener) b).nextSelected(e); } public void backSelected(WizardEvent e) { if (a != null) ((WizardListener) a).backSelected(e); if (b != null) ((WizardListener) b).backSelected(e); } public void cancelSelected(WizardEvent e) { if (a != null) ((WizardListener) a).cancelSelected(e); if (b != null) ((WizardListener) b).cancelSelected(e); } public void finishSelected(WizardEvent e) { if (a != null) ((WizardListener) a).finishSelected(e); if (b != null) ((WizardListener) b).finishSelected(e); } protected static EventListener addInternal(EventListener a, EventListener b) { if (a == null) return b; if (b == null) return a; return new WizardEventMulticaster(a, b); } protected EventListener remove(EventListener oldl) { if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal(a, oldl); EventListener b2 = removeInternal(b, oldl); if (a2 == a && b2 == b) return this; return addInternal(a2, b2); } } 

Methods in the multicaster class: A review

Let's review the methods that are part of the multicaster class above. The constructor is protected, and in order to obtain a new WizardEventMulticaster, a static add(WizardListener, WizardListener) method must be called. It takes two listeners as arguments that represent two pieces of a listener chain to be linked:

  • To start a new chain, use null as the first argument.

  • To add a new listener, use the existing listener as the first argument and a new listener as the second argument.

This, in fact, is what has been done in the code for class Wizard that we have already examined.

Another static routine is remove(WizardListener, WizardListener). The first argument is a listener (or listener multicaster), and the second is a listener to be removed.

Four public, non-static methods were added to support event propagation through the event chain. For each WizardEvent case (that is, next, back, cancel, and finish selected) there is one method. These methods must be implemented since the WizardEventMulticaster implements WizardListener, which in turn requires the four methods to be present.

How it all works together

Let's now examine how the multicaster actually is used by the Wizard. Let's suppose a wizard object is constructed and three listeners are added, creating a listener chain.

最初は、wizardListenerクラスのプライベート変数Wizardはnullです。したがって、が呼び出されるWizardEventMulticaster.add(WizardListener, WizardListener)と、最初の引数、wizardListenerはnullになり、2番目の引数はnullになりません(nullリスナーを追加しても意味がありません)。次に、addメソッドはを呼び出しますaddInternal。引数の1つがnullであるため、の戻り値はnulladdInternal以外のリスナーです。add戻り値は、null以外のリスナーをメソッドに返すメソッドに伝播しますaddWizardListener。そこで、wizardListener変数は追加される新しいリスナーに設定されます。