イベントシステム
参考
http://www.dev-articles.com/article/Gwt-EventBus-%28HandlerManager%29-the-easy-way-396001
http://stackoverflow.com/questions/998621/gwt-custom-event-handler
概要
GWTのイベントシステムは、Swingのものをアレンジしたものと言ってよい。基本的な用語は以下の通り
- Source:Swingでのイベントソースと同じ意味。イベントの発行元オブジェクトで、オブザーバパターンでの「オブザーバブル」。
- Event:Swingでのイベントとほぼ同じ意味。Swingと同様に、イベントオブジェクトは少なくともそのソースへの参照を持つが、加えて必要な任意の情報を保持してもよい。
EventHandler:Swingでいうところのリスナ。オブザーバパターンでの「オブザーバ」
HandlerManager(もしくはEventBus):Swingでいうところのリスナリスト。オブザーバブルオブジェクト上でオブザーバを管理するオブジェクト。
最も単純な形態
以下にできる限り単純なサンプルを示すが、一般的にはこのようなコードは記述しないものと思われる。 あくまでも例を単純にするための便宜的なコードである。
イベントの定義
イベントの定義自体に、そのハンドラ(Swingでいうリスナ)を指定しなければならない。 Swingでは、リスナはイベントを参照するが、その逆は無いので、一つのイベントクラスは複数のリスナで利用することができる。
ところが、GWTでは、(後述のハンドラの定義をあわせてみると)イベントとハンドラは互いに参照しあっている。
また、(イベントオブジェクトは初めからハンドラが確定しているため)イベントの配布はイベント自身に行わせることができる。 これがdispatchというメソッドで、イベントオブジェクトはハンドラが渡された場合、その何というメソッドを呼び出せばよいかがわかっている。
TYPEとgetAssociatedType()については後述。
import com.google.web.bindery.event.shared.*; public class SomeEvent extends Event<SomeEventHandler> { public static Type<SomeEventHandler>TYPE = new Type<SomeEventHandler>(); @Override public Event.Type<SomeEventHandler>getAssociatedType() { return TYPE; } @Override protected void dispatch(SomeEventHandler handler) { handler.onSomething(this); } }
イベントハンドラ
イベントハンドラは単純で、これはSwingのリスナと似たようなもの。 ただし、Swingと異なり、メソッドはただ一つにするのがお約束である。 というより、イベントの配布はイベントオブジェクト自身が行うのだが、前項に示したように、 一つのメソッドしか呼び出さないため複数メソッドを定義する理由がない。
※イベントがGwtEventの場合は、そのハンドラはEventHandlerをextendsするのが一般的だが、ここでは必要ない。
public interface SomeEventHandler { public void onSomething(SomeEvent e); }
イベントソースの例
一般には、EventBusはその名のとおり、アプリの各パーツを連絡する「バス」として用いられるので、 通常は以下のような使い方はしない。単純化するために「可能な例」をあげた。
また、GwtEventの場合には、HasHandlerインターフェースを作成してそれを実装するのが通例だが、ここでは省略している。
この単純化された例はSwingでの構造とほぼ同じである。つまり、ハンドラ(リスナ)を登録するメソッドを用意し、 リスナリスト(EventBus)に追加する。 何らかのイベントを発生させたい場合、イベントオブジェクトを作成し、リスナリスト中のすべてのハンドラにそれを通知する。
import com.google.web.bindery.event.shared.*; public class Something { private EventBus eventBus = new SimpleEventBus(); public void fireEvent() { eventBus.fireEvent(new SomeEvent()); } public HandlerRegistration addSomeEventHandler(SomeEventHandler handler) { return eventBus.addHandler(SomeEvent.TYPE, handler); } }
getAssociatedType()の意味
さて、イベントオブジェクトに実装しなければならないgetAssociatedType()メソッドだが、これが必要な理由はどこにもまともに説明されていない。 単に、「どのイベントであるか」を知るためであれば、このようなものは使わずにSomeEvent.classでもよいはずである。
この方式の場合、SomeEventクラスからはstaticのTYPEとgetAssociatedType()メソッドは除去され、SomethingのaddSomeEventHandlerは
return eventBus.addHandler(SomeEvent.class, handler);
と記述することになるだろう。わざわざこうなっている理由は今のところ不明である。
HandlerRegistration
Swingでよくあるリスナ登録メソッドとは異なり、 addSomeEventHandler()登録メソッドは、HandlerRegistrationインターフェースを実装したオブジェクトを返すことになっている。このインターフェースには、removeHandler()というメソッドがただひとつ登録されている。
つまり、Swingのやり方とは異なり、Somethingクラスには、ハンドラの登録解除メソッドはなく、登録メソッドから返されたHandlerRegistrationオブジェクトのremoveHandler()メソッドを呼び出すことにより登録解除を行うことになっている。
つまり、Somethingオブジェクトを使う側は、
Something something = ... HandlerRegistration hr = something.addSomeEventHandler(new SomeEventHandler() { public void onSomething(SomeEvent e) { System.out.println("SomeEvent!"); } }); ... hr.removeHandler();
などとすることができる。Swingの方式(各オブジェクトで提供されたremoveHandlerメソッドを、登録時のリスナを指定して呼び出す)に比較すると、優れていることは明らかである。
パッケージの違い
注意しなければならないのが、
- com.google.web.bindery.event.shared
- com.google.gwt.event.shared
の二つのパッケージの違いである。上の例では、前者のパッケージのみを使用していたが、もともとは後者のパッケージしか存在しなかった模様である。 後者の一部は徐々に前者に置き換えられていくものと思われるが、しかし現在(GWT2.4)では前者と後者に同じ名称のクラスが存在している。
上の例のようにGWTのウィジェットと無関係な場合は前者パッケージのクラスを使うことになるが、GWTウィジェット関連のコードを作成する場合(イベントクラスの継承元をGwtEventとする場合)には、後者パッケージのクラスを使うことになる。
GwtEventの例
GWTウィジェットで新規にイベントを発生する場合、イベントクラスはEventではなくGwtEventを継承させる。 この場合、関連するクラスは、上述したようにcom.google.gwt.event.sharedパッケージのものを使用する。
なお、Somethingオブジェクトは今回はButtonを一つ保持するCompositeウィジェットであり、 Buttonが押されたら、SomeEventを発生するようにしている。
Compositeを継承しているSomethingにはHandlerManager(EventBus)が組み込まれているので、独自にこれを作成する必要はないことに注意。
※以下では簡単のために、通常必要とされるHasHandlersは省略している。
SomeEvent.java
import com.google.gwt.event.shared.*; public class SomeEvent extends GwtEvent<SomeEventHandler> { public static Type<SomeEventHandler>TYPE = new Type<SomeEventHandler>(); @Override public GwtEvent.Type<SomeEventHandler>getAssociatedType() { return TYPE; } @Override protected void dispatch(SomeEventHandler handler) { handler.onSomething(this); } }
SomeEventHandler.java
import com.google.gwt.event.shared.*; public interface SomeEventHandler extends EventHandler { public void onSomething(SomeEvent e); }
Something.java
import com.google.gwt.event.dom.client.*; import com.google.gwt.event.shared.*; import com.google.gwt.user.client.ui.*; public class Something extends Composite { public Something() { LayoutPanel layoutPanel = new LayoutPanel(); Button button = new Button("push!"); layoutPanel.add(button); button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent e) { // CompositeにはfireEventというメソッドが提供されている。 Something.this.fireEvent(new SomeEvent()); } }); layoutPanel.setSize("100px", "100px"); initWidget(layoutPanel); } public HandlerRegistration addSomeEventHandler(SomeEventHandler handler) { // HandlerManager(EventBus)はCompositeに組み込まれており、そこに登録するための // メソッドaddHandlerが提供されている。ただし、引数は逆になっている。 return addHandler(handler, SomeEvent.TYPE); } }
イベントオブジェクトの階層
- Event
GwtEvent
JavaDocの訳: Handlermanagerが発生するすべてのGWTウィジェットとDOMのイベントのルートとなる。 イベントを発生させたHandlerManagerが、それを完了させた後はGWTイベントは死んだ状態となるので、それ以降はアクセスしてはいけない。 アプリケーションのカスタムイベントをGwtEventのサブクラスとする必要はない。Eventのサブクラスとすることを推奨する。
DomEvent
JavaDocの訳: GwtEventのサブクラスであり、下層のネイティブなブラウザイベントオブジェクトとsinkEvents()で用いられるGWTイベントビットを理解するTypeのサブクラスを提供する (意味不明)。
HumanInputEvent
JavaDocの訳:マウス等の位置イベントやタッチイベント等を表現する抽象クラス。