Go to Contents Go to Java Page
Project Looking Glass
 
 

はじめての LG3D プログラミング

- LG3D で Hello, World! その 2 イベント編 -

 
 

前回、単に Hello, World を作って、サムネイルを表示させるところまでやりました。

今回は、そこにイベント処理を加えていきたいと思います。付け加えようと思っているのは下の 3 つの機能です。

  • マウスポインタがアプリケーション領域に入ったら、サイズを変える
  • マウスの右クリックを押したら、終了する

どれもマウスからのイベントですね。

今回使用した LG3D のバージョンは Release-0.7.0 です。

  1. LG3D のイベント
  2. サイズの変更
  3. 意味もなくまわす
  4. おわりに
 
 

LG3D のイベント

 
 

LG3D のイベントモデルは、AWT や Swing のイベントモデルとちょっと異なります。

まず AWT (Swing) のイベントモデルについて。AWT のイベントモデルはイベントを発生するオブジェクトに対してリスナを登録することでイベントを受けられるようにします。

AWT Event

実際にはリスナが自分で登録しないで、他のオブジェクトが登録することもありますが、まぁ大目に見てください。

そして、イベントが発生した時は、イベントは一度イベントキューに入れられます。そして、イベントキューから 1 つづつ取りだされて、イベントが発生したコンポーネント (下図ではとりあえず EventPublisher としています) に渡されます。この EventPublisher がリスナにイベントを配信します。

このとき、イベントをキューに入れるスレッドと、イベントを配信するスレッドは別になります。

AWT Event

イベント処理はリスナで行います。よく使われるのが無名クラスを使ったリスナですね。

このモデルだとイベントの種類とリスナはかなり強く結びついています。

次に LG3D のイベントモデルです。

LG3D のイベントモデルは、AWT のリスナに相当する部分が 2 つのインタフェースで表されます。イベントを受ける部分と、イベントの処理をおこなう部分です。

クラス図で書くと次のようになります。

AWT Event

org.jdesktop.lg3d.utils.eventadapter.EventAdapter インタフェースはイベントを受け取って、イベントから必要な情報を抽出します。そして、イベントの処理をおこなう org.jdesktop.lg3d.utils.action.Action インタフェースを実装したクラスを呼び出します。

このような構成にすることで Action インタフェースはイベントの種類に依存せずに、処理だけをおこなうことが可能になります。

実際には EventAdapter インタフェースが Action インタフェースへの関連を持つわけはないので、実装クラスが関連を持つようになります。

また、AWT/Swing と違うのが EventAdapter を実装したクラスが提供されていることです。

AWT/Swing では MouseListener など、EventListener の派生インタフェースは提供されています。しかし、これはあくまでもインタフェースでしかありません。MouseAdapter クラスなどの実装クラスも提供されていますが、単に定義してあるメソッドに対して空の実装をしただけのクラスでしかありません。

それに比べて LG3D では EventAdapter インタフェースを実装したクラスとして、org.jdesktop.lg3d.utils.eventadapter.MousePressedEventAdapter クラスなどが提供されています。

これらのコンクリートクラスは AWT のリスナに比べると、粒度が小さいのが特徴です。

たとえば、AWT では MouseListener インタフェースに相当するのが、MousePressedEventAdapter, MouseClickedEventAdapter, MouseEnteredEventAdapter の 3 つのクラスで表されます。

粒度を小さくすることでイベントから取り出す情報を決めうちすることができます。

さて、コードではどう書くのでしょう。

コンポーネントにリスナ登録をおこなうのはおなじなのですが、コンストラクタに Action インタフェースを実装したクラスを指定します。

    comp3d.addListener(new MousePressedEventAdapter(
                                      new ScaleActionBoolean(comp3d, 1.1f)));

comp3d が Component3D オブジェクトと考えてください。

MousePressedEventAdapter クラスが EventAdapter インタフェースを実装したクラス。そして、ScaleActionBoolean クラスが ActionBoolean インタフェースを実装したクラスです。

イベント発生時はどのような動きになるかというと、次の図のようになります。

LG3D Event

このシーケンス図に出てくる EventProcessor オブジェクトは org.jdesktop.lg3d.displayserver パッケージで定義されています。しかし、AWT と同じようにあまり意識する必要はないです。

配信するときは EventProcessor オブジェクトから直接 EventAdapter オブジェクト (この場合は MousePressedEventAdapter オブジェクト) に配信されます。AWT がコンポーネントからイベントが配信されていたのとは異なりますね。

processEvent メソッドの引数は LgEvent オブジェクトです。

おぉ、ここではじめてイベントクラスが出てきました。

LgEventListener オブジェクトは Action オブジェクト (この場合は ScaleAction オブジェクト) の performAction メソッドをコールします。

AWT ではイベントにくるまれている情報が、LG3D では performAction の引数になります。つまり、EventAdapter がイベントから情報を抽出してくれるのです。

そして、扱う情報に応じて Action の派生クラスが定義されています。

主なところだと

  • org.jdesktop.lg3d.utils.action.ActionNoArg インタフェース
  • org.jdesktop.lg3d.utils.action.ActionBoolean インタフェース
  • org.jdesktop.lg3d.utils.action.ActionFloat インタフェース
  • org.jdesktop.lg3d.utils.action.ActionFloat3 インタフェース

などがあります。

これらのインタフェースの performAction メソッドの第 1 引数はすべて LgEventSource オブジェクトです。LgEventSource インタフェースはイベントが発生したコンポーネントなどを表すためのインタフェースです。

ActionNoArg インタフェースは引数が LgEventSource オブジェクトだけ。ActionBoolean インタフェースは boolean の引数、ActionFloat は float の引数、ActionFloat3 は float の引数が 3 つあります。

たとえば、ScaleActionBoolean クラスは ActionBoolean インタフェースを実装しています。

他の Action インタフェースを実装したクラスには

  • org.jdesktop.lg3d.utils.action.ScaleActionFloat
  • org.jdesktop.lg3d.utils.TranslateActionBoolean
  • org.jdesktop.lg3d.utils.TranslateActionFloat
  • org.jdesktop.lg3d.utils.TransparencyActionBoolean
  • org.jdesktop.lg3d.utils.TransparencyActionFloat

などがあります。

クラス名の Boolean とか Float がインタフェース名に対応しています。

しかし、こうしているとちょっと困ったことがあります。

イベントアダプタは扱う情報が決まっているので、コールできるアクションのインタフェースが決まっているということです。

MousePressedEventAdapter であれば、マウスが押されているかどうかという情報とマウスの位置情報を扱うので、ActionBoolean, ActionBooleanFloat2, ActionBooleanFloat3 の 3 つのインタフェースを扱うことができます。

困るのは、たとえば、ScaleActionBoolean を使いたいのだけれども、MouseClickedEventAdapter クラスのようにイベントアダプタが ActionBoolean インタフェースを扱えない場合です。

そんなときにはイベントアダプタとアクションの間にアクションアダプタを挟むようにします。

たとえば、トグル処理にするには org.jdesktop.lg3d.utils.actionadapter.ToggleAdapter クラスを使用します。org.jdesktop.lg3d.utils.actionadapter パッケージには他にもこのようなクラスが提供されています。

例として MouseClickedEventAdapter クラスで ActionBoolean インタフェースを使う場合を示しました。

    comp3d.addListener(new MouseClickedEventAdapter(
                                  new ToggleAdapter(
                                      new ScaleActionBoolean(comp3d, 1.1f))));

このようにデリゲートするクラスをコンストラクタで示すようにします。

 

 
 

サイズの変更

 
 

前置きが長くなってしまいました。

プログラムを見たかった方はうずうずしていたのではないでしょうか。さっそく、Hello, World を改良していきましょう。

まずは、マウスポインタが領域に入ったらサイズを変化させてみましょう。

本来なら、フォーカスの移動に関するイベントがあればいいのですが、まだ実装されていないようなので、マウスポインタが領域内に入ったらサイズ変更をしてみます。

サンプルのソース HelloWorld4.java

今までの説明がわからないとしても、コードは簡単なので、すぐ書けます ^^;;

    private void doEnableToChangeSize(Component3D comp3d) {
        // なめらかに動かすようにする
        comp3d.setAnimation(new NaturalMotionAnimation(1000));
 
        // Mouse が panel に入ったらサイズを拡大する
        comp3d.addListener(new MouseEnteredEventAdapter(
                               new ScaleActionBoolean(comp3d, 1.1f, 500)));
 
        // 親にイベントを伝播する
        comp3d.setMouseEventPropagatable(true);
    }

はじめの Component3D#setAnimation メソッドはコンポーネントをアニメーションさせるときに、どのような動きにするかということを指定します。NaturalMotionAnimation クラスは org.jdesktop.lg3d.utils.c3animation パッケージで定義されています。ここでは、お約束だと思っておいてかまいません。

サイズを変更するアクションは ScaleActionBoolea クラスです。第 2 引数がサイズの拡大率になります。ここでは 1.1 倍しています。第 3 引数は変化にかかる時間で、ここでは 500ms かけて徐々に大きくなるようにしています。

最後の Component3D#setMouseEventPropagate メソッドは親コンポーネントにイベントを伝えるかどうかを指定します。デフォルトだと false になっており、伝えないようになっています。

このため、フレームの移動などができなくなってしまいます。 引数を true にしてコールすることでフレームの移動もできるようになります。

マウスポインタが入っていない場合
マウスポインタが入っていない場合
マウスポインタが入っている場合
マウスポインタが入っている場合

このイメージだと違いが微妙なので分かりづらいかもしれませんが、実際に動かしてみればすぐ分かりますよ。ぜひ、やってみてください。

 

 
 

意味もなくまわす

 
 

アクションアダプタも使ってみましょう。

特に意味もなく、コンポーネントをぐるぐるまわしてみましょうか ^^;;

サンプルのソース HelloWorld5.java

このコードも簡単ですね。

    private void doEnableToRotate(Component3D comp3d, Component3D target) {
        // なめらかに動かすようにする
        target.setAnimation(new NaturalMotionAnimation(1000));
 
        // クリックしたら回す
        comp3d.addListener(new MouseClickedEventAdapter(
                               new ToggleAdapter(
                                   new RotateActionBoolean(target, (float)Math.PI, 500))));
 
        // 親にイベントを伝播する
        comp3d.setMouseEventPropagatable(true);
    }

回転をおこなうのが RotateActionBoolean クラスです。第 2 引数に回転角度を指定します。第 3 引数が回転にかける時間です。

doEnableToRotate メソッドは引数を 2 つにしてみました。

イベントが発生するコンポーネントと、回転をさせるコンポーネントを別々にしたかったからです。

こうすることで、コンポーネントがクリックされたときに、サムネイルもいっしょに回転させることなどができます。

ぜひコンパイルして、実行してみてください。くるくる回って楽しいですよ ;-)

回転前
回転前
回転中
回転中
回転後
回転後

 

 
 

おわりに

 
 

イベントアダプタとアクションを分離したことで、アクションが書きやすくなったのは確かです。

でも、逆にイベントの種類が増えるたびにイベントアダプタを作らなければならないのはちょっと面倒です。

まぁ、とりあえず Hello, World は作ることができました。でも、不満なのはイメージをロードしてそれを貼り付けているだけというところです。次は動的にテクスチャを作ることを考えてみます。

 

今回使用したサンプルはここからダウンロードできます。

 

(Oct. 2004)
(改訂 Feb. 2005 Release-0.61 に対応)
(改訂 Aug. 2005 Release-0.7.0 に対応)

 
 
Go to Contents Go to Java Page