[???] /
[Java FAQ] / [S172]
S172: イベント処理 - delegation event model
[S065 Q-01]
delegation event model とはなんですか?
[S065 A-01]
イベント発生源(イベントソース)そのものではなく、イベント処理の役割
を持ったオブジェクトに処理を行わせるモデルです。
以前の awt でのイベント処理方法として、コンポーネント(イベントソー
ス)自身にイベント発生時に呼び出されるメソッドがあり、これをオー
バーライドすることでイベントのハンドリングを行うというモデルがあり
ました。
これは Windows の MFC(Microsoft Foundation Class) 等でも使われて
いる手法です。
delegation event model は、それとは異なり、イベントソースであるコ
ンポーネントに対して、イベントを通知してほしいオブジェクト(イベン
トリスナ)を登録することで、イベントがそのオブジェクトに伝えられる
モデルです。
イベントの処理がイベントソースではなくイベントリスナによってなされ
る、つまり委譲(delegate)されるわけです。
S065-02 も参照してください。
参考記事 [JavaHouse-Brewers:8284]
[S065 Q-02]
Listener(イベントリスナ)とはなんですか?
[S065 A-02]
「イベントを受信できる役割」です。
S065-01 に記した通り、イベントソースとは別のオブジェクトがイベントを処
理するわけですが、そのような「イベントを処理する」即ち「イベントを
受信する」機能を持つオブジェクトは"イベントリスナ"と呼ばれます。
具体的には、XxxxListener を実装したクラスのオブジェクトがイベント
リスナとなります。
+-------------------------------------+
|FooListener implements ActionListener|>-+
| イベントリスナ | |
+-------------------------------------+ |
| |
| addActionListener |actionPerformed
V |
+-----------------+ |
|Button(Component)| ---------------------+
| イベントソース |
+-----------------+ >-- ボタンを押下
プログラマは FooListener を作成してボタンなどのイベントソースに登
録(addActionListener メソッド呼び出し)します。
そうすることで、ボタン側は ActionListener である FooListener オブ
ジェクトの actionPerformed を呼び出すことが可能になります。
ボタンが押されたタイミングでそれが呼び出されるので、
actionPerformed() メソッドにイベント処理を記述すればよいわけです。
[S065 Q-03]
なぜ delegation event model なのですか?
[S065 A-03]
イベントソースとイベント処理を分離出来るためです。
S065-01 に記したサブクラス化によるハンドリング方法では、ボタンが新たな
イベントを処理出来るようにするために新たなボタン(のサブクラス)を作
成する必要があります。
しかし、ボタンはあくまでボタンであり、ボタンを押された場合の処理は
ボタンでは無いわけですから、これが異なるオブジェクトとして扱われる
のは自然であり、使う側にとっても、イベントソースであるボタンをいち
いちサブクラス化する必要がなくなります。
[S065 Q-04]
ただイベントをハンドリングしたいだけなのになぜクラスを作らなければならないのですか?
[S065 A-04]
S065-03を参照して下さい。
[S065 Q-05]
ボタンをサブクラス化する必要がなくなっても、結局 Listener 実装クラ
スを新たに定義しなければならないのではないですか?
[S065 A-05]
そのとおりですが、オブジェクトとしてボタンから分離したことに意義が
あります。
イベント処理がイベントソースから独立するということは、そのイベント
処理オブジェクトであるイベントリスナを、異なるイベントソースに登
録出来るようになったということを意味します。
例えば、全てのコンポーネントにツールチップを表示する機能を付加する
場合を考えてみましょう。
イベントソースをサブクラス化する手法では、全てのコンポーネントを
サブクラス化した「ツールチップ対応コンポーネント」を作成しなけれ
ばなりません。
delegation event model では、ツールチップ表示を行う
MouseListener 実装クラスを一つ用意して、それをインスタンス化され
た各イベントソースに add してゆけばよいのです。異なる種類のイベン
トソースに対して一つイベントリスナを作成するだけで済む典型的なパ
ターンです。
ここで、一つのイベントリスナインスタンスを複数のイベントソースに
add するか、一つのイベントリスナクラスの複数のインスタンスをそれ
ぞれのイベントソースに add するかの二通りが考えられます。
「S065-12 一つのイベントリスナオブジェクトを複数のイベントソースに
接続してもよいのですか?」を参照してください。
[S065 Q-06]
Listener を implements するとハンドリングする必要のないメソッドま
で実装しなければならないのですか?
[S065 A-06]
そのとおりです。ただし、複数のハンドリングメソッドを持つ
XxxxListener には、通常それに対応する XxxxAdapter クラスが用意さ
れています。
「S065-07 java.awt.event.XxxxAdapter は何のためにあるのですか?」を
参照して下さい。
[S065 Q-07]
java.awt.event.XxxxAdapter は何のためにあるのですか?
[S065 A-07]
XxxxListener 中の複数のイベントハンドリングメソッドの内、特定のイ
ベントのみ処理する場合に実装を楽にします。
XxxxListener interface を implements してイベント処理クラスを作成
する場合は、宣言されている全てのメソッドを実装する必要があります
が、XxxxAdapter クラスを継承して作成する場合は、処理したいイベン
トに対応するメソッドのみをオーバーライドすればよいことになりま
す。
例:
Listener を implements する場合。
class HilightListener implements MouseListener {
public void mousePressed(MouseEvent ev) {}
public void mouseReleased(MouseEvent ev) {}
public void mouseClicked(MouseEvent ev) {}
public void mouseEntered(MouseEvent ev) {
((Component)ev.getSource()).setForeground(Color.blue);
}
public void mouseExited(MouseEvent ev) {
((Component)ev.getSource()).setForeground(Color.black);
}
}
Adapter を継承する場合。
class HilightListener extends MouseAdapter {
public void mouseEntered(MouseEvent ev) {
((Component)ev.getSource()).setForeground(Color.blue);
}
public void mouseExited(MouseEvent ev) {
((Component)ev.getSource()).setForeground(Color.black);
}
}
尚、XxxxAdapter は便利なクラスなのですが、このようなクラスの継承+
オーバーライドを多用すると気付きにくい問題が発生する場合があります
のでご注意ください。
「S011-03 オーバライドしたはずのメソッドが呼ばれないのですが?」
を参照してください。
余談になりますが、adapterと言えば、異なるインターフェイス(メソッ
ドのセット)を持つオブジェクト同士を仲介するオブジェクトのことで
す。
XxxxAdapter とはこのような目的のために命名されたと考えられます
が、実際のところは、
Listener (イベントソースに対して listen する役割)
Adapter (イベントソースと他のオブジェクトを adapt する役割)
という使い分けがなされているとは言いがたいです。
[S065 Q-08]
java.awt.event.XxxxAdapter は abstract メソッドが無いのになぜ
abstract なのですか?
[S065 A-08]
これらのクラスを直接インスタンス化して使用することが無いことを明
確にするためです。
XxxxAdapter クラス群は全てのメソッドが実装されていますが、内容は
空です。S065-07 に記した通り、 XxxxListener interface の全てのメソッ
ドを実装する手間を軽減するために用意されているわけですから、その
ままで用いられることはありません。もちろんそのまま用いることがで
きたとしても実装が空のためなんの意味も持ちません。
このように継承して使用することを前提としたクラスは、たとえ
abstract メソッドが存在しなくても abstract class として設計するの
です。
[S065 Q-09]
なぜ ActionListener には対応する ActionAdapter が無いのですか?
[S065 A-09]
メソッドが一つしかないため実質不要です。
「S065-07 java.awt.event.XxxxAdapter は何のためにあるのですか?」
を参照して下さい。
[S065 Q-10]
anonymous イベントリスナとはなんですか?
[S065 A-10]
イベントリスナクラスの名前を考える手間を軽減し、小さなイベント処理
定義をメソッド内(正確には new 出来る場所ならどこでも)に記述できる
ものです。
「S016-06 匿名クラス (anonymous class) とは何ですか?」を参照
してください。
以下に例を記します。
:
openButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
fileManager.fileOpen();
}
});
:
new Xxxx() というコンストラクタ呼び出し部に {} で定義を記述出来ま
す。これはXxxxクラスのサブクラスをその場で定義してインスタンス化す
るという意味になります。そのサブクラスには名前が無い(class 宣言が
無いので)ので anonymous inner class と呼びます。
基本的に、使い捨てのイベント処理オブジェクトや、adapter(*) として
の利用に徹する場合に使用します。
*:他のオブジェクトに実処理を任せ、メソッドシグネチャの違いを吸収す
るためのオブジェクト。XxxxAdapter とは意味が異なります。
[S065 Q-11]
イベント処理は独立のオブジェクトで処理すべきなのですか?
[S065 A-11]
はい。一つのオブジェクトにたくさんのイベント処理を書くこともできて
しまいますがそれは一般には避けるべきです。
特にセマンティックイベントの処理は独立させやすく、だからこそ一つの
リスナに一つのメソッドとなっているものが多いのですが、このようなイ
ベントリスナオブジェクトは適切に処理をカプセル化することが出来てい
れば、汎用化->再利用が容易になります。
もちろんイベント処理は他のクラスの機能を呼び出すように作ることが多
いため、呼び出されるクラスとセットで独立した処理にしたいところです
が、それもし辛い場面があります。
使い捨てプログラムにまでこのようなプログラミングスタイルを徹底する
必要はありませんが、必要であれば、イベント処理クラスから委譲される
オブジェクトも interface とすることで汎用性を保つことを容易にする
ことができます。
public interface Terminatable {
void terminate();
}
public class TerminateAdapter extends WindowAdapter {
private Terminatable terminatable;
public TerminateAdapter(Terminatable t) {
terminatable = t;
}
public void windowClosing(WindowEvent ev) {
terminatable.terminate()
}
}
adapter (XxxxAdapter とは異なる意味で用いています)としての利用方法
の典型的な例ですが、上記二つのクラス(interface)は十分に汎用的です。
上記例のように低レベルイベントといえどもその発生時に処理する内容を
うまくカプセル化していれば再利用の可能性が高まるのは同じです。
イベントリスナに関しては、interface を implements するにしても、な
るべく他のものを継承しないクラスとして設計するようにすべきでしょう。
「S065-17 イベントには低レベルイベントとセマンティックイベントがある
とのことですが?」も参照してください。
ただ、複数のイベントリスナを実装したクラスとした方がすっきりする
ケースもあります。
以下のように TerminateAdapter を拡張するのは自然であるといえるで
しょう。異なるイベントで同じ処理を行わせたい場合には、同一のオブジ
ェクトを異なるイベントソースの異なるイベントに対して登録することが
できるため有効です。
public class TerminateAdapter
extends WindowAdapter implements ActionListener {
private Terminatable terminatable;
public TerminateAdapter(Terminatable t) {
terminatable = t;
}
public void windowClosing(WindowEvent ev) {
terminatable.terminate()
}
public void actionPerformed(ActionEvent ev) {
terminatable.terminate()
}
}
※この場合、WindowListener と ActionListener は同列で扱われるべき
なので、implements WindowListener, ActionListener とした方がすっき
りするのですが、XxxxAdapter はあくまで(空の)実装を書く手間を軽減さ
せるだけのものと割り切ってください。もちろん XxxxAdapter を二つ継
承することはできませんので、そういう場合は空の実装を書かなければな
らなくなるでしょう。
[S065 Q-12]
一つのイベントリスナオブジェクトを複数のイベントソースに接
続してもよいのですか?
[S065 A-12]
はい。「S065-11 イベント処理は独立のオブジェクトで処理すべきなので
すか?」の例にもあるように、積極的に行ってよいでしょう。
但し、何も考えずにプログラミングしていると、イベントリスナオブジェ
クトがステートフルであった場合に、問題が発生することがあります。
つまり、同じオブジェクトを複数のイベントソースに登録した場合は、同
じインスタンスの同じメソッドが別のイベントソースから呼び出されるわ
けですから、イベントリスナが状態(フィールド)を持っていると、正しく
実装しないとその内容に不整合が出る可能性があるといえるでしょう。
[良い例はないでしょうか?]
イベントリスナは処理を行う対象のオブジェクトを保持するため以外の、
状態を表すフィールドは持たないように設計すべきです。
[S065 Q-13]
イベントはどうやってイベントリスナに伝えられるのですか?
[S065 A-13]
イベントソースが何らかの方法でイベントを発生させたとき、自身に登録
されたリスナ群のメソッドを呼び出すことで伝えられます。
public interface FooListener {
public void occurred(FooEvent ev);
}
というイベントリスナが存在したときの、最も単純なイベントの配送機構
(イベントソースの処理)を以下に記します。
private List listeners = new ArrayList();
public void addFooListener(FooListener l) {
listeners.add(l);
}
public void removeFooListener(FooListener l) {
listeners.remove(l);
}
protected void fire(FooEvent ev) {
Iterator i = ((List)listeners.clone()).iterator();
while (i.hasNext()) {
((FooListener)i.next()).occurred(ev);
}
}
listeners に対して clone() を呼び出しているのは、その後の while
ループを実行中に他の Thread から add/removeFooListener が呼び出さ
れる可能性があるためです。上記3つのメソッドを synchronized とする
方法でも構いません。
他にも java.beans.PropertyChangeSupport クラスや
java.awt.AWTEventMulticaster といったイベントをリスナに配送するた
めのツールクラスが存在します。
[S065 Q-14]
EventDispatchThread とはなんですか?
[S065 A-14]
AWT イベント処理を行うためにバックグラウンドで動作する Thread で
す。
AWT イベントは基本的に(*)この Thread でのみ実行されるため、一つの
イベント処理に時間がかかると他のイベントが処理待ち状態になってしま
います。
「S065-33 イベント発生時に時間のかかる処理を行いたいときはどうすれ
ばよいのでしょうか?」を参照してください。
モーダルダイアログボックスの表示時は少し特殊です。モーダルダイア
ログボックスを表示した場合、そのスレッドは show() や
setVisible(true) 呼び出し時にブロックされますが、ブロックされるス
レッドが EventDispatchThread だった場合には、一時的にもう一つの
EventDispatchThread が起動されるようになっています。
*:例外は「S065-18 AWT のイベントを自分で発生させるにはどうすればよい
のですか?」を参照して下さい。
[S065 Q-15]
AWT イベントはどうやってイベントソースまで伝えられるのですか?
[S065 A-15]
Toolkit#getSystemEventQueue() で取得される EventQueue オブジェク
トから取り出されて、各コンポーネントの dispatchEvent() が呼び出さ
れます。
OS 等からハンドリングした低レベルイベントを含む AWT イベントは、イ
ベントキューと呼ばれるイベントの一時貯蔵庫にためられていきます。
AWT コンポーネントが存在する場合(*)は、EventDispatchThread というイ
ベント処理専用のスレッドが動作して、イベントキューに溜まったイベン
トを順次各コンポーネントの dispatchEvent() メソッドに渡してゆきま
す。
イベントソースである各コンポーネントは、addXxxxListener で登録され
たイベントリスナが存在すれば、dispatchEvent() に渡されたイベントを
イベントリスナにも伝えるようになっています。
*:「S065-37 main メソッドを終了してもアプリケーションが終了しないの
ですが?」も参照してください。
[S065 Q-16]
なぜ、EventListener interface は java.util にあるのですか?
[S065 A-16]
event delegation model は汎用的な仕組みだからです。
イベントといえば GUI に対するイベントが思い浮かぶ方も多いかもしれ
ませんが、GUI とは無関係なイベントも多数存在します。例えば
JavaBeans の PropertyChangeEvent は Beans の属性の変化を通知するイ
ベントですし、Servlet でも HttpSessionBindingEvent といったイベン
トが使用されています。
すなわち、event delegation model とは、何かが何かに何かを通知する
仕組みでしかないため、その仕組みそのものを意味する interface は
java.util に存在するということです。
JavaBeans 仕様に則ったイベントソースを実装する場合は、
java.util.EventListener/java.util.EventObject をそれぞれ継承してイ
ベント配送の仕組みを実装するべきです。
[S065 Q-17]
イベントには低レベルイベントとセマンティックイベントがあるとのことですが?
[S065 A-17]
低レベルイベントや他の要因が複合した結果として「セマンティックイベ
ント」を発生させる場合があります。
低レベルイベントとは、MouseEvent や KeyEvent を始めとする「実際に
起こっていること」を表すイベントです。
セマンティックイベントは、例えば mouseDown/MouseUp や
keyDown/keyUp といった一連のイベントが発生した結果としての、「ボタ
ンが押された」を表す actionPerformed や、「選択肢が変更された」を
表す itemStateChanged が該当します。
ほとんどのイベントはセマンティックイベントです。
セマンティックイベントの複合によって新たなセマンティックイベントが
定義されることもよくあります。
アプリケーションの中でも、うまくカプセル化がなされたプログラムで
は、さまざまなイベントを受信した結果としてどのような意味の事象が発
生したかを表すセマンティックイベントを発生させるようになることが多
くなります。
また、セマンティックイベントはイベントの表す意味が抽象化されている
ので、低レベルイベントのように具体的な事象毎にハンドリングメソッド
(例えば MouseListener における mousePressed/mouseReleased ・・・の
ように)が定義されることは少なく、ほとんどのイベントリスナはメソッ
ドを一つしか持ちません。つまり XxxxAdapter クラスが用意されること
も少なくなります。
[S065 Q-18]
AWT のイベントを自分で発生させるにはどうすればよいのですか?
[S065 A-18]
イベントをイベントキューに post するか、Component#dispatchEvent()
に渡します。
「イベントキューに post する」とは、以下の二つの方式があります。
・システムイベントキューに post
・プログラマが生成したイベントキューに post
それぞれは以下のような処理を指します。
・システムイベントキューに post
KeyEvent ev = new KeyEvent(・・・);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(ev);
// Toolkit tk = Toolkit.getDefaultToolkit();
// EventQueue queue = tk.getSystemEventQueue();
// queue.postEvent(ev);
// と同じです
この方法では、イベントはキューに溜められるので、適切なタイミング
でEventDispatchThread によって実行されます。つまり、この処理を実
行している Thread が EventDispatchThread である場合(イベント処理
中に実行した場合)、そのイベント処理から復帰した後に post したイベ
ントの処理が行われることになります。
※EventDispatchThread 以外の Thread から post を行った場合は、
post したイベントが並行に処理され始める可能性があります。
・プログラマが生成したイベントキューに post
KeyEvent ev = new KeyEvent(・・・);
new EventQueue().postEvent(ev);
この方法では、新たな EventDispatchThread によってイベントの処理が
行われます。この方法を使用すると EventDispatchThread が複数存在す
るという状況になります。通常はそのような状況を想定したプログラミ
ングはされていないことが多いので、この方法を利用することはないで
しょう。
「Component#dispatchEvent() に渡す」とは以下のような処理を指しま
す。
KeyEvent ev = new KeyEvent(・・・);
button.dispatchEvent(ev);
このようにすると、dispatchEvent() を呼び出した瞬間に、その Thread
でイベント処理が実行されます。
処理させたいイベントの特性に応じて使い分けて下さい。
例えば、イベントAの処理中に他のイベントBを発生させて、イベントBの
処理が終わった後にイベントAのつづきの処理を行わせたい場合は
dispatchEvent() の方法を用いることになるでしょう。或いはイベントの
実処理メソッドを直接呼び出す方が直感的かもしれませんが、イベント配
送の流れに従う必要があるなら dispatchEvent() を使えばよいでしょう。
[S065 Q-19]
java.awt.AWTEventMulticaster を使ったイベントの配送方法がわからな
いのですが?
[S065 A-19]
AWTEventMulticaster は Chain Of Responsibility と言う方法でイベン
トを配送します。
まず、AWTEventMulticaster はその名の通り AWTEvent を配送する目的で
用意されています。このクラスは特定のイベントソースから利用します。
以下は任意のユーザ定義GUIコンポーネントに ActionEvent 配送機能を
付加する例です。
public MyComponent extends Component {
:
:
private ActionListener actionListener = null;
public void addActionListener(ActionListener l) {
actionListener = AWTEventMulticaster.add(actionListener, l);
}
public void removeActionListener(ActionListener l) {
actionListener = AWTEventMulticaster.remove(actionListener, l);
}
protected void fireActionEvent(ActionEvent ev) {
if (actionListener != null) {
actionListener.actionPerformed(ev);
}
}
}
既存の AWTEvent サブクラスを配送可能なイベントソースを作成する場
合は、上記のような固定的なコーディングで可能となります。
Chain Of Responsibility という仕組みについては GoF本 (JavaFAQにお
ける正式表記はどうでしたっけ?)に詳細に説明されています。
また、AWTEventMulticaster のソースを読まれた方は、いくつかのサブ
クラス向けのメソッドの存在から、これをサブクラス化する事で任意の
イベントを配送可能にする事も出来るのではないかと考えることと思い
ますが、これは AWTEventMulticaster の現在の実装では問題があって実
現できませんのでご注意下さい。
[S065 Q-20]
java.beans.PropertyChangeSupport を使ったイベントの配送方法がわか
らないのですが?
[S065 A-20]
PropertyChangeSupport は名前の通り、属性の変更通知イベントが基本の
使い方です。以下に例を示します。
public class FooBean {
:
:
private PropertyChangeSupport listeners =
new java.beans.PropertyChangeSupport(this);
public void addPropertyChangeListener(
java.beans.PropertyChangeListener l) {
listeners.addPropertyChangeListener(l);
}
public void removePropertyChangeListener(
java.beans.PropertyChangeListener l) {
listeners.removePropertyChangeListener(l);
}
protected void firePropertyChange(int oldValue, int newValue) {
if (oldValue == newValue) {
return;
}
listeners.firePropertyChange("value", oldValue, newValue);
}
}
上記は int 型のフィールドの変更を外部のオブジェクトに通知する
PropertyChangeEvent を発行可能なイベントソースの例です。
PropertyChangeSupport のコンストラクタにはイベントソースとなるオ
ブジェクトを与え、add/removePropertyChangeListener() ではそのまま
PropertyChangeSupport の同メソッドを呼び出します。
イベントの発行には変更前の属性値と変更後の属性値、及び「属性名」
を PropertyChangeSupport#firePropertyChange()に与えます。
「属性名」はどの属性に対するイベントかを識別可能であれば任意の文
字列で構いませんが、JavaBeans 規約に従うならば、プロパティ名と呼
ばれるgetXxxx()/setXxxx()メソッドのXxxx()に相当する文字列とすべき
ということになっています。
[S065 Q-21]
独自のイベントを作るにはどうすればよいのですか?
[S065 A-21]
以下のようにイベント/イベントリスナを用意し、イベントソースを実装します。
※なんか適当な具体例を教えてくださあい^^
public class FooEvent extends java.util.EventObject {
private int notifiedValue;
public FooEvent(Object source, int notifiedValue) {
super(source);
this.notifiedValue = notifiedValue;
}
public int getValue() {
return notifiedValue;
}
}
public interface FooEventListener
extends java.util.EventListener {
void notified(FooEvent ev);
}
この場合 int 値を何らかのタイミングで通知するイベントということに
なります。
このイベントを発行するイベントソースの実装方法については、
「S065-13 イベントはどうやってイベントリスナに伝えられるのですか?」
等を参照して下さい。
イベントリスナの使用方法は、コア API にあるようなイベントと同様に
なります。
[S065 Q-22]
イベントの受信者が限られている場合はどのようにすればよいのですか?
[S065 A-22]
「S065-23 TooManyListenersException とは何ですか?」を参照してください。
[S065 Q-23]
TooManyListenersException とは何ですか?
[S065 A-23]
addXxxxListener を呼び出したときに登録可能なリスナの個数を越えた場
合に TooManyListenersException が throw されます。
つまり、addXxxxListener() メソッドに
throws TooManyListenersException を記述して無効な呼び出しに対して
この例外を throw するように実装することで、イベントリスナの登録個
数を制限することができます。
さらに、リスナの個数を一つに制限する場合は、addXxxxListener ではな
く setXxxxListener というメソッド名にして、意味を明確にすることが
推奨されています。
特に Bean として実装されるイベントソースは setXxxxListener() が複
数回呼び出された場合は TooManyListenersException を throw するべき
です。
setXxxxListener() は他の setFoo() といった setter メソッドとは概念
が異なってくることに注意しましょう。setXxxxListener() で、後から呼
び出された時に渡されたリスナによって元々登録されていたリスナが上書
きされる実装はよくないでしょう。理由は、登録したイベントリスナを他
人(そのオブジェクトの参照を持たないオブジェクト)に削除できるように
すべきでは無いためです。
[S065 Q-24]
EventListenerList とはなんですか?
[S065 A-24]
イベントの配送を助けるツールクラスです。複数種類のイベントリスナを
保持し、特定の種類のイベントリスナだけを取り出したりできます。
# なんか作りがいまいちなんですけど・・・高速化のためには仕方がな
# いのか?でもほんとに高速なの?
swing で新たに定義されたイベントは AWTEventMulticaster を使用せず、
EventListenerList を利用してリスナの保持を行うようになっています。
使用方法はAPIドキュメントに例が示されており、そのとおりに実装すれ
ば利用できます。
# このQ&Aいいかげんなので改善案求む^^;
[S065 Q-25]
イベントが全く処理されないのですが?
[S065 A-25]
「S011-03 オーバライドしたはずのメソッドが呼ばれないのですが?」
を参照してください。
[S065 Q-26]
キーイベントが取得出来ないのですが?
[S065 A-26]
Component#requestFocus() を行っていない可能性があります。
キーイベントは現在キーボードフォーカスを持っているコンポーネントに
のみ配信されます。特に自作コンポーネントなどでもともとフォーカスを
持つことができないコンポーネントでキーイベントを受信出来るようにす
るためには、適切なタイミングで Component#requestFocus() を呼び出
す必要があります。
適切なタイミングとは自身または親コンポーネントの peer が生成された
後ということになります。マウスクリックが行われたときに行う場合が多
いようです。
また、そのようなコンポーネントに正しくキーボードフォーカスを持たせ
られるようにする(*)ためには、Component#isFocusTraversable() を
true を返すようにオーバーライドする必要があります。
*:Tab キー等でもそのコンポーネントにキーボードフォーカスが移動す
るもの
[S065 Q-27]
イベントリスナの呼び出される順番を制御したいのですが?
[S065 A-27]
基本的に出来ません。
イベントリスナの呼び出される順番に依存した処理を記述してはいけない
ことになっています。
一つのイベントが発生したときに、随時的に複数のイベントリスナの処理
を行わせたい場合は、それぞれのリスナのメソッドを随時的に呼び出す新
たなリスナオブジェクトを定義するのがよいでしょう。
以下のような二つのリスナオブジェクトをボタンに add した場合、ウィ
ンドウを閉じようとしたときに save メソッドが呼び出されることは保証
出来なくなります。
public class SystemExitListener extends WindowAdapter {
public void windowClosing(WindowEvent ev) {
System.exit(0);
}
}
public class ContentsSaver extends WindowAdapter {
public void windowClosing(WindowEvent ev) {
save();
}
private void save() {
:
}
}
このような小さな例の場合は、単に一つのリスナオブジェクトとして定義
すればよいのですが、イベント処理がある程度大きい場合は、以下のよう
にすることで処理される順番を保証出来るようになります。
public class SequencialEventInvoker implements ActionListener {
private Vector list;
public void add(ActionListener l) {
list.addElement(l);
}
public void actionPerformed(ActionEvent ev) {
for (int i = 0; i < list.size(); i++) {
((ActionListener)list.elementAt(i)).actionPerformed(ev);
}
}
}
複数のイベントリスナを SequencialEventInvoker オブジェクトに add
し、このオブジェクトをイベントソースに add することで、
SequencialEventInvoker オブジェクトに add した順にイベントリスナが
呼び出されます。
[S065 Q-28]
メニューがドロップダウンされた(開いた)イベントを取得出来ますか?
[S065 A-28]
AWT では出来ません。swing の場合はメニューが表示/消去される時に親
メニューの javax.swing.MenuElement#menuSelectionChanged() が呼び出
されますので、これをオーバーライドすることで認識出来ます。
ポップアップメニューの開閉については JMenu#addMenuListener() に
よって可能です。
[S065 Q-29]
TextField に数字しか入力出来ないようにしたいのですが?
[S065 A-29]
InputEvent#consume() により、イベントを処理済の状態にします。
低レベルイベントの中にはアプリケーションが何もしなくても OS のデフ
ォルトの処理が動作するものがあります。
TextField でのキーイベントなどは顕著な例です。アプリケーションが何
も処理しなくても文字入力が可能です。
このような挙動を拒否したい場合は、イベント処理中に
InputEvent#consume() を呼び出します。
例えば、以下のようにすることで TextField 中で数字以外の入力を制限
することが可能です。
textField.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent ev){
if(ev.getKeyChar() != ev.VK_BACK_SPACE
&& (ev.getKeyChar() < '0' || ev.getKeyChar() > '9')){
// 数字/BackSpace 以外の入力は無視
ev.consume();
}
}
});
[S065 Q-30]
TextField で BackSpace キーを無効化したいのですが?
[S065 A-30]
「S065-29 TextField に数字しか入力出来ないようにしたいのですが?」
を参照してください。
[S065 Q-31]
Windows では F10 キーを押下するとメニューにフォーカスがいってしま
うのですがこれを拒否出来ますか?
[S065 A-31]
「S065-29 TextField に数字しか入力出来ないようにしたいのですが?」
を参照してください。
[S065 Q-32]
コンポーネントにどのようなイベントが発生しているかを調べるには
どうすればいいですか?
[S065 A-32]
Component#processEvent() をオーバーライドします。
注目したいコンポーネントをサブクラス化して、コンストラクタ等で
Component#enableEvents() に-1(全てのイベント)、または注目するイベ
ントに対応した AWTEvent.XXXX_EVENT_MASK を |(or)したものを渡しま
す。そして、Component#processEvent() を以下のようにオーバーライド
すれば、そのコンポーネントに流れるほぼ全てのイベントを見ることが
できます。
public void processEvent(AWTEvent ev) {
System.out.println(ev);
super.processEvent(ev);
}
特にイベントの流れる順番はプラットフォームによって異なる場合もあり
ますので、気になった場合はこのようなコードで調査してみましょう。
参考記事 [JavaHouse-Brewers:19140]
[S065 Q-33]
イベント発生時に Thread#sleep() したり、時間のかかる処理を行うと
画面が更新されなくなってしまうのですが?
[S065 A-33]
Component#paint() メソッドも EventDispatchThread で呼び出される
ためです。
回避する方法は「S065-34 イベント発生時に時間のかかる処理を行いたいとき
はどうすればよいのでしょうか?」を参照してください。
参考記事 [JavaHouse-Brewers:19016],[JavaHouse-Brewers:19022]
[S065 Q-34]
イベント発生時に時間のかかる処理を行いたいときはどうすればよいので
しょうか?
[S065 A-34]
新たな Thread を生成して処理を実行させ、イベント処理メソッドから速
やかに復帰します。
イベントの発生頻度が高くないなら比較的単純に書き替えが可能でしょ
う。
public void actionPerformed(ActionEvent ev) {
new Thread() {
public void run() {
// 元々の actionPerformed() の処理
:
}
}.start();
}
イベントの発生頻度が高い場合や、何らかの同期が必要な場合は、バッ
クグラウンドスレッドを予め用意しておくことになるでしょう。
参考記事:[JavaHouse-Brewers:8284]
[S065 Q-35]
どのコンポーネントがどのイベントを発生するのかはどうやって知るのですか?
[S065 A-35]
API ドキュメントを探す必要があります。
delegation event model の場合、イベント発生源クラスの API ドキュメ
ントを参照して、addXxxxListener()/setXxxxListener() メソッドを探し
ます。スーパークラスに存在する場合もあります。
その名前からすぐに類推できる場合が多いですが、それらの中で自分のハ
ンドリングしたいイベントが実際にあるかどうかは
add/setXxxxListener() のパラメタである XxxxListener interface の
API ドキュメントを見ます。常にどこからイベントを渡してほしいかとい
う考え方で探しましょう。
尚、このようにイベントを探すときにどのリスナを使えばよいのかを調べ
たり把握したりするのは億劫だという意見がありますが、イベントが適切
にグループ化されているおかげで逆に把握しやすくなっているということ
に気付く必要があります。
特に、XxxxEventクラスがイベントのグループごとに定義されていること
で、イベント発生時に渡される情報に曖昧さが無くなっている点が重要で
す。
[S065 Q-36]
EventDispatchThread はなぜデーモンスレッドではないのですか?
[S065 A-36]
EventDispatchThread がデーモンスレッドだと、GUI コンポーネントが
画面に存在するのに JavaVM が終了し得るからです。
GUI コンポーネントはユーザからのイベントを待つだけで、他の自発的な
処理を全く行わない場合が多くなります。
このような場合は、JavaVM 上で非デーモンスレッドが
EventDispatchThread のみという状況になります。従って、
EventDispatchThread をデーモンスレッドとするわけにはいかないので
す。
[S065 Q-37]
main() メソッドを終了してもアプリケーションが終了しないのですが?
[S065 A-37]
System.exit() の呼び出しが必要になる場合があります。
非デーモンスレッド(*)である EventDispatchThread が動作中の場合な
どはアプリケーションの終了が行えなくなります。
GUI コンポーネントを生成すると自動的に EventDispatchThread が動
作します。
このような場合は、 System.exit() を呼び出すことでアプリケーション
の終了とせざるを得ません。
しかし、System.exit() を呼び出すようになっていると、他のアプリ
ケーションから main() メソッドを呼び出す形で起動されるアプリケー
ションの場合に問題になります。
http://developer.java.sun.com/developer/bugParade/bugs/4072987.html
も参照してください。
このような場合は独自の SecurityManager を作成し、
System#setSecurityManager()により登録することで、
System.exit() を拒否するといった対策が必要になります。
注:この対策を行うと、アプリケーションの能動的な終了が行えなくなり
ます。setSecurityManager() は一度しか呼び出してはいけないことに
なっているためです。登録する独自の SecurityManager で、以下のよう
に実装することで、System#exit() を再度呼び出せるようにすることが
できますが、これは議論を呼ぶでしょう。
System.setSecurityManager(new SecurityManager() {
public void checkExit(int status) {
// System#exit() 呼び出しを拒否する。
throw new SecurityException();
}
public void checkPermission(java.security.Permission p) {
// System#setSecurityManager() 呼び出しを許す。
}
});
:
try {
// 他のクラスの main() メソッドを表す
// java.lang.reflect.Method オブジェクトに対して呼び出し
mainOfAnyClass.invoke(null, new Object[] {args});
} catch (SecurityException e) {
}
:
// 終了したい時
System.setSecurityManager(null);
System.exit(0);
「S019-12 デーモンスレッド(Daemon thread)ってなんですか?」、
「S065-36 EventDispatchThread はなぜデーモンスレッドではないのですか?」
も参照してください。
※このQAはトラブル一般の項に移動すべき
contributor: Shin
コメントの送り先
Java FAQ BBS