イベント

要約:
  • Curl® 言語で記述されたアプレットは、目標で起動するイベント を使用してメッセージをプログラムに送ります。
  • イベントは Curl アプレットをインタラクティブにするための重要なキーになります。
  • イベント ハンドラEventHandler クラスのオブジェクトを意味します。
  • ダイナミック イベント ハンドラは、特定の EventTarget オブジェクトにアタッチする EventHandler オブジェクトです。
Curl アプレットは、目標で起動するイベントを使用して、プログラムのコンポーネント間でメッセージを送信したり、テキスト フィールドへの入力やオブジェクト上でのマウスの移動など、画面の特定の目標オブジェクトに影響する、ユーザーのマウスの操作についてプログラムに通知します。イベントが発生したことが目標ターゲットに通知されると、目標ターゲットはそれに応えて 1 つまたは複数のイベント ハンドラを呼び出します。
この章では、イベント、イベント目標、イベントの作成や起動の方法、およびイベントの起動に対して実行されるイベント ハンドラの種類について説明します。ここでは、キーボードやマウスを使用してアプレット ウィンドウと交信するときに生成されるイベントについて詳しく説明します。
イベントの選択、ドラッグ、およびドロップは、「選択」と「ドラッグ アンド ドロップ」の章で個別に説明します。ダイアログ イベントは「コントロールで発生するイベント」の章で説明します。TimerEvent およびプロデューサ / コンスーマ関係で使用されるイベントは、「アニメーション」の章で説明します。これらはアニメーションに必須です。
イベントは Curl アプレットおよびアプリケーションをインタラクティブにするための鍵です。イベントは Event サブクラスで表されます。各 Event サブクラスは、起動可能な特定タイプのイベントを表します。たとえば、画面に表示されるオブジェクトをクリックすると、PointerPressPointerRelease イベントが起動され、テキスト セグメント上のカーソルをドラッグすると DragPointerEvent を起動することが出来ます。
イベント目標として動作するオブジェクトからイベントが起動されます。イベント目標の例としては、クリックするオブジェクトやドラッグして選択するテキストがあります。
オブジェクトは、EventTarget のサブクラスになっているときに限り Event の目標になります。すべてのグラフィック オブジェクトは EventTarget クラスから継承しているので、目標になる可能性があります。EventTarget オブジェクトには、関連付けられているダイナミック イベント ハンドラのリストがあります。一致するイベントがオブジェクトで起動されると呼び出されます。
ポインタがグラフィック オブジェクト上にあるときにマウス ボタンをクリックするような特定のイベント活動が発生すると、その種の活動を表すイベント オブジェクトが生成され、そのグラフィック オブジェクトが目標となり、イベントが起動されます。次の例では、イベント目標は CommandButton で、その上をクリックすると Action イベントが CommandButton で起動されます。

例: 単純なアクション イベント ハンドラ
{CommandButton
    label="Click me!",
    {on Action at btn:CommandButton do
        set btn.label = "Thank you."
    }
}
単語 on で始まるソース コードは、この例では CommandButton にアタッチされるイベント ハンドラを作成します。イベント ハンドラにより、CommandButton は、Action イベントの一種である PointerRelease イベントに対応してボタンのラベルおよびコントロールの色を変更します。
通常は、任意のイベントを使用して、他のイベントの起動のような、一連の操作を知らせたり、アプレットの状態を変更したりすることができます。それらの操作のソース コードはイベント ハンドラに含まれています。目標オブジェクトには複数のイベント ハンドラを含めることができます。各ハンドラは、一定のタイプのイベントが目標で起動されるとそのソース コードを実行します。イベント ハンドラ内のソース コードは他の目標でイベントを再起動することができます。
イベント 体系を簡単に要約すると次のようになります。
イベント 体系
  • ユーザーがマウスまたはキーボードを使用して、画面上のグラフィック オブジェクトに対していくつかの動作を行います。
  • その動作を表すイベントがインスタンス化され、該当する目標オブジェクトで起動されます。
  • 目標オブジェクトにアタッチされ、このイベントに対応する 1 つまたは複数のイベント ハンドラが実行されます。
  • 任意のイベント ハンドラが同じイベント (または新たなイベント) を他の目標オブジェクトで起動し、他のイベント ハンドラを始動させることがあります。
同種のイベントを再び起動するアクションをイベント ハンドラが実行するといった、 再帰的なイベント ハンドラの呼び出しは回避する必要があります。 例えば、ViewResizeEvent のハンドラが表示サイズを変更する場合や、 FocusIn イベント ハンドラがキーボード フォーカスを別のオブジェクトに移動する場合がそうです。 この状況を防ぐ、最も簡単な方法は、{after 0s do ...} を使用して、 新しいイベントを引き起こすコードを実行します。次のコード フラグメントを検討してみましょう。
{on FocusIn do
    {after 0s do
        || code-that-changes-keyboard-focus
    }
} 
キーボード フォーカスを変更するコードは、現在のイベントが完全に処理された後でのみ実行され、 起こり得る再帰が回避されます。式を {after 0s do body} 内に挿入すると、 タイマー イベントは他にイベントが無い場合のみに処理されるため、 body をイベント キューの終わりに追加するのと同様の効果が得られます。

イベント キュー

各 Curl アプレットは イベント キュー を保持しています。イベント キュー上の各ノードはイベントおよびそのイベントが起動されるイベント目標で構成されます。
ユーザー インターフェイスがアクティブなとき、Curl® ではイベント ループと呼ばれるイベントに関連する操作の標準シーケンスを繰り返し実行します。イベント ループは、イベント キューが空になるたびに、ユーザー インターアクションによって、イベント キューに新しいイベント ノードが生成されるのを待ちます。イベント キューが空でないときは、キューの前面にあるイベント ノードが移動され、その指定された目標でイベントが起動され、イベントが要求どおりに処理されます。

イベントの起動

頻繁に必要になることはありませんが、EventTarget.enqueue-event を呼び出してイベントをイベント キューに追加することができます。これは、キューに入れられたイベントのハンドラが現在のイベント (およびキュー内で新しいイベントの前にあるその他のイベント) が処理されるまで実行されないので、非同期イベント処理と呼ばれます。イベント キューを介さずにイベントをすぐに目標で起動したい場合、EventTarget.handle-event を呼び出すことができます。これは、現在のイベント ハンドラ (ある場合) から戻る前に新しいイベントが直ちに処理されるので、同期イベント処理と呼ばれます。
例えば、CommandButton は、同一ボタン内で PointerRelease が続く PointerPress を検出し、Action イベントをキューに入れます。同様の方法で、ボタンにキーボード フォーカスがある場合、KeyPress イベント ハンドラは、スペース バーが押され開放されると Action イベントをキューに入れます。いずれの場合も Action イベント ハンドラは、ボタンに関連付けられた同様の動作を実行します。

イベント ハンドラ

イベント ハンドラ は、正確には、EventHandler クラスのオブジェクトを意味します。このオブジェクトには、オブジェクトが応答するイベント タイプ (イベント サブクラスの 1 つ) およびそのイベントのタイプに応答して実行されるプロシージャの両方が含まれます。
イベント ハンドラのプロシージャはイベントに対応して実行されるオブジェクトのアクティブな部分なので、イベント ハンドラ オブジェクトとその中に含まれるプロシージャとの違いを見過ごしがちです。イベント ハンドラのコードは指定されたクラスのイベントに対してのみ実行されることを覚えておくことが重要です。
イベント ハンドラにはいくつかの種類があります。オブジェクトの 1 つのインスタンスにアタッチされているイベント ハンドラをダイナミック イベント ハンドラといいます。最も広く使用されるイベント ハンドラで、任意の EventTarget オブジェクトにアタッチすることができます。
EventTarget オブジェクトはダイナミック EventHandler のリストを保持しています。EventTarget クラスには、EventHandler のリストにイベント ハンドラを追加および削除するメソッドがあります。イベントが目標で起動されると、目標はイベントのデータ型をリスト上にあるすべてのイベント ハンドラのイベント クラスと比較します。最後に追加されたハンドラから始まってリストの上方へ向かい、イベント クラスの一致する各ハンドラを起動します。複数のイベント ハンドラが所定のイベントに適用される可能性もあります。
次のセクションでは、ダイナミック イベント ハンドラの書き方について説明しますが、もっともよく使用されるタイプは特殊な on 式を使用して作成されます。(次のセクション 「on 式の使い方」を参照してください。)
Events はアプレットの実行時に再利用されることがよくあります。参照を作成した Event が再利用される場合、格納する情報は不正確になります。イベント ハンドラはハンドラが返されると、その後は、Event をグローバル変数に割り当てたりフィールドに格納することによって、イベントへの参照を保持するべきではありません。

ダイナミック イベント ハンドラ

ダイナミック イベント ハンドラは、特定の EventTarget オブジェクトにアタッチされる EventHandler オブジェクトです。
特定のオブジェクトに対してダイナミック イベント ハンドラを作成するための主要なステップは次の通りです。
  1. イベント ハンドラ プロシージャのコードを記述する
  2. 適用するイベント クラスを指定する
  3. コードを特定のオブジェクト (イベント目標) にアタッチする
on 式を使用して、ステップ 1 と 2 を同時に実行することができます。この式にはいくつかの変化形があります。さらに、ステップ 3 でイベント ハンドラを目標オブジェクトにアタッチする方法が 2 つあります。
1 つ目は、イベント目標オブジェクトを作成するときに、次のようにしてイベント ハンドラにその他の引数をインクルードする方法です。

例: イベント ハンドラを目標にインクルードする
{CommandButton
    label = "Do not press!",
    || create an event handler for Action events on this button
    {on Action do
        {popup-message "Why did you do that?"}
    }
}
もう 1 つは、目標オブジェクトを作成した後、その目標オブジェクト上でadd-event-handlerを開始する方法です。

例: EventTarget.add-event-handler メソッドの使用
{value
    let target:CommandButton =
        {CommandButton
            label = "Do not press!"
        }
    || create an event handler and add it to the button already created
    {target.add-event-handler
        {on Action do
            {popup-message "Why did you do that?"}
        }
    }
    target
}

on 式の使用

特別な on 式にはいくつかのスタイルがあります。単純なフォームの構文を次に示します。 {on event-class do body}
次に示すようにして、このイベント ハンドラをグラフィック オブジェクトにアタッチすることができます。

例: on 式の最も単純なフォーム
{CommandButton
    label={center {bold Invokes an event handler when clicked}},
    || Attach following event handler to this CommandButton
    {on Action do
        {popup-message
            title="Your Message",
            "This is a user message dialog."
        }
    }
} ||CommandButton
on 式の他のスタイルを使用すると、イベント ハンドラの対応する特定のイベントに変数名をバインドし、メソッドを呼び出したりそのイベントのプロパティを調べることができます。同様に、変数名をイベント目標にバインドし、そのメソッドやプロパティにアクセスすることができます。
スタイル説明
{on event-type do body} イベント タイプのみ指定
{on event-var:event-type do body} 変数をイベントにバインド
{on event-type at target-var[:target-type]} do body} 変数を目標にバインド。target-type が指定されていない場合、既定では any になります。
{on event-var:event-type at target-var[:target-type] do body} 変数をイベントと目標の両方にバインド
すべてのスタイルで次のようになります。
注意: ダイナミック イベント ハンドラを記述するときは、on 式が含まれているコンテキストで定義された任意の変数を参照することができます。
次の例は、ポインタが中に入って離れるとその色が変わる長方形を示します。Frame にアタッチされたダイナミック イベント ハンドラが 2 つあり、目標 Frame の 背景を設定することにより色を変更します。このオプションを参照するには、イベント ハンドラが、その目標を表す変数 (rect) を宣言する必要があります。

例: on 式で目標変数を宣言する
{Frame 
    width=100pt, 
    height=50pt,
    background = "orange",
    || attach the first dynamic event handler
    {on PointerEnter at rect:Frame do
        set rect.background = "cyan"
    },
    || attach the second dynamic event handler
    {on PointerLeave at rect:Frame do
        set rect.background = "magenta"
    }
}

明示的にダイナミック イベント ハンドラを追加

イベント ハンドラを目標の中かっこ内に入れて暗黙的に GuiEventTarget にアタッチする代わりに、目標上で GuiEventTarget.add-event-handler メソッド、または、on 式で作成されたイベント ハンドラを明示的にアタッチすることができます。
以下のサンプルでは、HBox には 2 つの Frame が含まれています。変数leftright は、左右のFrameを識別します。左のフレームには、明示的にイベントハンドラがアタッチされており、PointerEnterとPointerLeaveのイベントに反応して、右のフレームの色を変えます。ターゲットは左フレームですが、イベント ハンドラは右のフレームの背景色を変化させます。

例: 明示的にダイナミック イベント ハンドラを追加する
{value
     let left = 
         {Frame
             background = "orange",
             width=100pt, 
             height=50pt
         }
     let right = 
         {Frame 
             background = "orange",
             width=100pt, 
             height=50pt
         }
     let my-hbox = 
         {spaced-hbox 
             left, 
             right
         }

     || attach first event handler to left frame
     {left.add-event-handler
         {on PointerEnter do
             set right.background = "aqua"
         }
     }
     || attach second event handler to left frame
     {left.add-event-handler
         {on PointerLeave do
             set right.background = "magenta"
         }
     }
     my-hbox
 }

ダイナミック イベント ハンドラの削除

ダイナミック イベント ハンドラを削除するには、削除するイベント ハンドラを引数として指定してイベント ハンドラがアタッチされている目標上で EventTarget.remove-event-handler を起動します。on 式で返されるイベント ハンドラを EventHandler クラスの変数に割り当て、削除するハンドラを指定できるようにします。
以下のサンプルは、チェックボックスをtrueに設定した時にFrameにイベント ハンドラを追加し、チェックボックスをfalseに設定した時にイベント ハンドラを削除します。

例: ダイナミック イベント ハンドラの削除
{value
    let rectangle = 
        {Frame 
            width={make-elastic}, 
            height=50pt,
            background = {FillPattern.get-orange}
        }
    || create a handler for PointerEnter event
    let turn-me-aqua:EventHandler =
        {on PointerEnter do
            set rectangle.background = {FillPattern.get-aqua}
        }
    || create a handler for PointerLeave event
    let turn-me-magenta:EventHandler =
        {on PointerLeave do
            set rectangle.background = {FillPattern.get-magenta}
        }
    || create a checkbutton that will alternately add or remove
    || the color changing event handlers
    let checkbox:CheckButton =
        {CheckButton
            label = "Add color changers",
            {on ValueChanged do
                {if checkbox.value == true then
                    || add color changers when the box is checked
                    {rectangle.add-event-handler turn-me-magenta}
                    {rectangle.add-event-handler turn-me-aqua}
                 else
                    || remove color changers when it is unchecked
                    {rectangle.remove-event-handler turn-me-magenta}
                    {rectangle.remove-event-handler turn-me-aqua}
                }
            }
        }
    {VBox
        checkbox,
        rectangle
    }
}

イベントと目標クラス階層

イベントの処理とその動作の変更方法についてより深く理解するには、Event および EventTarget クラスについて理解すると共に、これらのクラスおよびその主要なサブクラスで利用できるメソッドの種類について知っておく必要があります。
イベント クラス階層の一部の詳細を次に示します。最もよく使用されるイベント クラスは、グラフィカル ユーザー インターフェイス関連のイベントを含む GuiEvent、そのサブクラス GuiWindowEvent、およびそのサブクラス GuiInputEvent (マウスやキーボード イベントのすべてを含む)です。これらは、残りの章の主要点となります。
Event
GuiEvent
CurrentNodeChanged
CurrentRecordChanged
StopEvent
DetachEvent
AttachEvent
CommandChanged
RenderFailed
StartEvent
TabPaneShown
SelectionEvent
LoadingFinished
RepaintNotify
DialogEvent
GuiWindowEvent
GrabRelease
Action
TabPaneHidden
CurrentRecordChangeRequest
AsyncWorkEvent


AsyncStreamWriteEvent
AsyncStreamReadEvent
AsyncSocketEvent
AsyncFileOpenEvent


RecordSetEvent
RecordSetLoadStateChanged
RecordsChanged


ChartAxesChanged
ChartLayoutChanged


SceneEvent
IntersectionSceneEvent


EmbeddedBrowserEvent


DocumentFinishEvent
StatusTextChangeEvent
BrowseStartEvent
TitleTextChangeEvent
BrowseFinishEvent
DownloadStartEvent
DownloadFinishEvent
BrowseErrorEvent


WindowEvent


UpdateWindowEvent
RepaintWindowEvent
DestroyWindowEvent
TimedWindowEvent
PointerCaptureEndWindowEvent
HostSettingChangedWindowEvent
DestroyRequestedWindowEvent


IOEvent
StreamEvent
IOTimeoutEvent
SocketEvent
TimerEvent
注意: その他のイベント クラスは、主に「選択」、「ドラッグ アンド ドロップ」、「ダイアログとコントロール」、および「アニメーション」の章で説明しています。
同様に、主要なイベント目標クラスは EventTarget で、その最も重要なサブタイプは GuiEventTarget です。イベント目標の部分クラス階層を次に示します。
EventTarget
AsyncStreamReaderEventTarget
RecordSet


EmptyRecordSet
EventManagingRecordSet
RecordView
ConnectedRecordSet


SceneObject


TextSceneObject
LightableSceneObject
SceneGroup
Camera
SceneLight


Window
IOEventTarget


DefaultIOEventTarget
DataSocketStream


AsyncFileOpenerEventTarget
GuiEventTarget
SkinnableControlUI
ControlFeel
SelectionContext
ContainerDragScroller
GuiManager
Selectable
Visual
CommandBinding
KeyAccel
ConsoleInput
ControlContainer
ControlUI
Timer
イベントはグラフィカル インターフェイスと通常関連していますが、必ずしもそうではありません。I/O イベントやソケット イベントだけでなく、タイマー イベントも含まれます。GUI イベントの詳細については、「GUI およびウィンドウ イベント」のセクションを参照してください。