イベントとイベントハンドラ

ユーザによるキー入力、タッチ操作、表示内容の変化、状態の変化など、さまざまな状況でイベントが発生します。
イベントが発生した場合、後述する過程で対応するイベントハンドラに渡されます。
イベントには同期イベントと非同期イベントがあり、同期イベントは即時に、非同期イベントはイベントキューを経由して処理されます。
イベントハンドラを用いることで、オブジェクトの状態の変化に応じた処理をユーザアプリケーションで定義できます。
../_images/event_image001.png
イベントハンドラは、"on"+イベント名という名前のFunctionとして定義します。
CRSの実行エンジンは、イベントの伝播先にこのような名前で定義されているFunctionを探し、イベントを伝播していきます。

例)Touchイベントを受け取るイベントハンドラ

function onTouch(e) {
    title = "Hello!";
}
イベントハンドラは呼び出される際に引数としてEventオブジェクトを受け取ります。
Eventオブジェクトには下記のプロパティが定義されており、これらを用いて処理を分岐することも可能です。

e.from

イベントの発信元オブジェクト

e.eventName

イベント名

例として、イベントの発信元オブジェクトを用いて処理を分岐するコードを示します。
このコードではテキスト入力エリアからキーボードフォーカスが失われた際に入力内容を検査し、結果に合わせて背景色を変化させています。
function onLostFocus(e) {
    if (e.from instanceof TextBox) {
        if (e.from.value == "") {
            e.from.bgColor = Color.RED; # 未入力ならば赤色
        } else {
            e.from.bgColor = Color.STD; # 入力済みであればデフォルト色(白色)
        }
    }
}
イベントは、イベントハンドラにより捕捉されるまで"//"(Rootオブジェクト)を頂点とするオブジェクトツリーを上位に向かって伝播していきます。
伝播の過程でイベントハンドラにより捕捉されるか、最終的にRootオブジェクトにまで到達したときに消滅します。
../_images/event_image002.png
イベントハンドラ内でpostEventメソッドを引数なしで呼び出すことで、イベントハンドラが捕らえたイベントを再度イベントの伝播の流れに戻すことができます。
../_images/event_image003.png

汎用イベントハンドラ

通常のイベントハンドラは、on にイベント名を続けた名前で定義します。
これに対し、onEvent という名前のFunctionを定義すると、イベント名によらずイベントを受け取る汎用イベントハンドラになります。
onEvent は、通常のイベントハンドラより先に呼び出されます。
汎用イベントハンドラでは、Eventオブジェクトの eventName プロパティを参照して処理するイベントを選択できます。
汎用イベントハンドラが true を返した場合、そのイベントの伝播を停止します。
false または値を返さなかった場合は、通常のイベントハンドラの検索とイベントの伝播が続行されます。
次の例では、KeyDown イベントの伝播を汎用イベントハンドラで停止しています。
この場合、同じオブジェクトに定義した onKeyDown は呼び出されません。
KeyDown 以外のイベントでは onEvent が false を返すため、通常のイベントハンドラが呼び出されます。
Form form1 {
  function onEvent(e) {
    if (e.eventName == "KeyDown") {
      return true;  # KeyDownイベントの伝播を停止
    } else {
      return false; # その他のイベントは通常どおり処理
    }
  }

  function onKeyDown(e) {
    # onEventがtrueを返すため、このイベントハンドラは呼び出されません。
  }
}
onEvent でイベントを停止せず、イベントに応じた処理だけを行いたい場合は、false を返すか、戻り値を指定しません。
イベントの発信元は e.from、イベント名は e.eventName で参照できます。

同期イベント処理のタイミング

同期イベントは、イベントが発生した時点でイベントキューを経由せずに直接イベントハンドラを実行します。

そのため、同期イベントのハンドリングは、関数の呼び出しと同じような流れで実行されます。


var numA = new Number {
    # デフォルトプロパティの変更に対しchangeイベントを発行します
    useChange = true;

    # デフォルトプロパティが変化した際に呼ばれるイベントハンドラです
    function onChange(e) {
        print("2");
    }
};

# 下記の処理を実行すると、1 → 2 → 3 の順で出力されます
print("1");
numA.value = 100; # 代入処理を行った瞬間、onChangeイベントが発生します
print("3");

非同期イベント処理のタイミング

非同期イベントは、常にイベントキューを経由します。イベントキューへのイベント格納と、イベントハンドラの実行は非同期で行われます。
つまり、イベントが発生したタイミングとイベントハンドラが実行されるタイミングは厳密には一致せず、実行時のコンピュータの状態により変動します。
例えば、3個の連続したイベントが発生したとき、コンピュータの状況によりイベントハンドラの実行タイミングは、以下のように変動します。

ケース1

1個目のイベントがキューに格納された後、イベントハンドラが起動される。

ケース2

2個目のイベントがキューに格納された後、イベントハンドラが起動される。

ケース3

すべてのイベントがイベントキューに格納された後、イベントハンドラが起動される。

通常、イベントの発生は何か状態が変化したときに発生します。

連続したイベントは、状態が連続して変化していることになります。

上記のケースのように連続した非同期イベントで、イベントの要因となった状態をスクリプト中で参照すると、 イベントハンドラが実行されるタイミングにより、参照した結果は異なることがあります。

例えば、非同期イベントでオブジェクトの状態変化を通知するような場合、 ケース1の場合ではイベントハンドラの1回目の呼び出しでは1個目のイベントが発生した際の値が取得できます。 ケース3の場合、イベントハンドラの1回目の呼び出しでは3個目のイベントが発生した際の値が取得できます。


最終的にはどのケースでも、イベントと同じ数だけイベントハンドラは起動されますが、 オブジェクトプロパティの現在値とイベントを起こしたときの状態は必ずしも一致しないことがある点は、特に注意してください。

イベントが発生したときのより詳細な状況を特定する必要がある場合は、イベントハンドラに渡されるEventオブジェクトを参照してください。 イベントの種類によっては、イベントオブジェクトに追加情報が格納されている事があります。


非同期イベントに対するイベントハンドラの実行で、ここで説明したような実行タイミングによる影響を抑止するためには、 必ずイベントオブジェクトの付加情報を参照して処理するようにしてください。


イベントの順序性

同期イベントでは、イベントは、イベント発生の瞬間にイベントハンドラに渡されます。

非同期イベントであれば、イベントは、イベントを発生させた動作と同じ順序でイベントキューに格納されます。

また、イベントキューに格納されたイベントは、同じ順序で取り出されてイベントハンドラに渡されます。

これらの順序は、常に一定です。


しかし、イベントを発生させる動作がいつも同じ順序で行われる保証はありません。

例えば、LostFocusイベントの次に、別のオブジェクトのGetFocusイベントが必ず発生するという前提は誤りです。 また、画面の操作により発生するイベントと、CRSスクリプトで発行したイベントのどちらが先になるのかも状況により変わるため、特定の発生順序を前提とした処理は誤りです。

例えば、Buttonオブジェクトを押下したとき発生する可能性のあるイベントは、GetFocusとTouchイベントです。

しかし、はじめからButtonにフォーカスがあるときGetFocusは発生しません。 また、onGetFocusイベントハンドラ内で、ユーザ定義イベントを発行したとき、Touchイベントとユーザ定義イベントのどちらが先にイベントキューに格納されるかは不定です。 これは、Touchイベントの発生要因となる、マウスボタンを離す操作と、CRSスクリプトの実行でpostEventメソッドを実行する処理で、 どちらが先に行われるかを決定する事ができないためです。


イベントハンドラは、どのような順序でイベントが発生しても、正しい動作となるように考慮する必要があります。

イベントの発生順序に依存する必要がある場合、そのイベントを発行する要因が、常に同じ順序で発生することを検証してください。

ただし、Biz/Browserに内蔵されるすべてのクラスは、複数のイベントの発生順序を保障していないため、 CRSスクリプトで発行するユーザ定義イベントのみ、発生順序が保障される可能性があります。

同期イベントと非同期イベントの分類

組み込みクラスのイベントには、同期(send)または非同期(post)のイベントモデルが定義されています。 現行の Biz/Browser DT で、非同期イベントとして定義されているイベントは次のとおりです。

イベント名

発生元クラス

イベントモデル

ItemClicked

ChartBarSeries、ChartBoxSeries、ChartLineSeries、ChartPercentBarSeries、ChartPieSeries、ChartScatterSeries、ChartSplineSeries、ChartStackedBarSeries、ChartStandardBarSeries、ChartXYSeries

非同期(post)

TaskProgress

Task、AsyncTask

非同期(post)

TaskResult

Task、AsyncTask

非同期(post)

InterContextMessage

Root

非同期(post)

WindowStateChanged

Root

非同期(post)

上表以外の組み込みクラスのイベントの多くは、イベント仕様上は同期(send)イベントです。 例えば、ネイティブモードでの Touch、Pressed、Released、Clicked、MouseEnter、 MouseLeave、Timer、SelectionChanged、EditChanged などが該当します。 イベント名が似ている場合でも、発生元クラスのイベント一覧で分類を確認してください。

この分類は、組み込みクラスのイベント仕様に定義されたモデルを示します。 実際の処理モデルは、イベントを発行する処理や互換モードによって変わる場合があります。 例えば、次のイベントはイベント仕様上のモデルと発行経路が異なります。

イベント

非同期になる条件

Socket.Receive

ソケット受信処理がイベントキューへ登録して発行します。

HTTPAsyncSession.SessionEnd

セッション終了処理がイベントキューへ登録して発行します。

Change、Touch など

互換モードでは、発行側の実装によりイベントキューへ登録される場合があります。

このため、イベント名だけで実行時の処理モデルを常に決められるわけではありません。 CRSからイベントを発行する場合は、postEvent の第2引数で明示的に指定できます。

  • postEvent(ev, true) は同期イベントとして直ちに処理します。

  • postEvent(ev, false) は非同期イベントとしてイベントキューに登録します。

  • 第2引数を省略した場合、通常モードでは同期、互換モードでは非同期として処理します。

  • イベントハンドラ内で postEvent() を引数なしで呼び出した場合は、処理中のイベントモデルを引き継いで親オブジェクトへ転送します。

したがって、ユーザ定義イベントを必ず非同期で処理する場合は、 postEvent(new Event("イベント名"), false) のように第2引数へ false を指定してください。