Event処理

 今回は,MacMETHにおけるイベント処理を紹介します。

ソース

 まずは,今回解説に利用するプログラム「ClickDraw」全体を引用します。
ClickDraw.mod
MODULE ClickDraw;

FROM
  GraphicWindows
IMPORT
  Window,
  OpenGraphicWindow,
  CloseGraphicWindow,
  SetPen,
  TurnTo,
  WriteString,
  Move,
  IdentifyPos;

FROM
  EventBase
IMPORT
  mouseDown,
  mouseUp,
  keyDown,
  Point,
  EventRecord,
  PushTask,
  PopTask,
  PollEventTasks;

VAR
  wndClickDraw	:Window;
  blnDone			:BOOLEAN;
  tskMouse		:INTEGER;
  tskKey			:INTEGER;

PROCEDURE TrackMouse(VAR recEvent :EventRecord) :BOOLEAN;
VAR
  intX,intY	:INTEGER;
BEGIN
  IF (recEvent.what # mouseDown) & (recEvent.what # mouseUp) THEN
    RETURN FALSE
  END;
  intX := recEvent.where.h;
  intY := recEvent.where.v;
  IdentifyPos(wndClickDraw,intX,intY);
  SetPen(wndClickDraw,intX,intY);
  WriteString(wndClickDraw,"Clicked!");
  RETURN FALSE
END TrackMouse;

PROCEDURE KeyPressed(VAR recEvent :EventRecord) :BOOLEAN;
BEGIN
  IF recEvent.what # keyDown THEN
    RETURN FALSE
  END;
  blnDone := TRUE;
  RETURN TRUE
END KeyPressed;

PROCEDURE WriteMessage(wndArg :Window);
BEGIN
  SetPen(wndArg,125,250);
  WriteString(wndArg,"ClickDraw;b")
END WriteMessage;

BEGIN
  OpenGraphicWindow(wndClickDraw,50,50,300,300,"ClickDraw",WriteMessage);
  tskMouse := PushTask(TrackMouse);
  tskKey := PushTask(KeyPressed);
  blnDone := FALSE;
  REPEAT
    PollEventTasks
  UNTIL blnDone;
  PopTask(tskKey);
  PopTask(tskMouse);
  CloseGraphicWindow(wndClickDraw)
END ClickDraw.

解説

 MacMETHでのイベント処理は,EventBaseモジュールを用いて実現します。EventBaseモジュールには,
CONST
  nullEvent
  mouseDown
  mouseUp
  keyDown
  keyUp
  autoKey
  updateEvt
  diskEvt
  activateEvt
  networkEvt
  driverEvt
  app1Evt
  app2Evt
  app3Evt
  app4Evt

TYPE
  Point
  EventRecord
  EventHandler

PROCEDURE PushTask(EventProc :EventHandler)	:Integer;
PROCEDURE PopTask(taskNum :INTEGER);

PROCEDURE PollEventTasks;

PROCEDURE GetBusyReadEvent(VAR event :EventRecord) :BOOLEAN;
上記のような定数・型・関数が定義されています。
 詳細に関しては,Documentに譲るとして,実際にClickDrawプログラムのソースを追いながら簡単に説明していきます。
VAR
  wndClickDraw	:Window;
  blnDone			:BOOLEAN;
  tskMouse		:INTEGER;
  tskKey			:INTEGER;
 キー押下時の処理が追加されたため,キー押下イベント処理のタスク番号を追加。
 MacMETHでは,処理するイベント毎に処理を行う関数を定義し,それを登録するという方法でイベント処理を行います。そのイベント登録時に,各イベント処理関数に番号が一意に振られます。それを納めるのが,tskMouse及びtskKeyです。
PROCEDURE TrackMouse(VAR recEvent :EventRecord) :BOOLEAN;
VAR
  intX,intY	:INTEGER;
BEGIN
  IF (recEvent.what # mouseDown) & (recEvent.what # mouseUp) THEN
    RETURN FALSE
  END;
  intX := recEvent.where.h;
  intY := recEvent.where.v;
  IdentifyPos(wndClickDraw,intX,intY);
  SetPen(wndClickDraw,intX,intY);
  WriteString(wndClickDraw,"Clicked!");
  RETURN FALSE
END TrackMouse;
 TrackMouse関数はマウスイベントを処理する関数です。
 イベント処理用の関数は,EventHandler型の関数である必要があります。EventHandler型の関数は,PROCEDURE(VAR EventRecord) :BOOLEAN;で定義されています。つまり,引数としてEventRecord型の変数を取り,返り値としてBOOLEAN型を返す関数である必要があります。また,引数の型として指定されているEventRecord型は,
EventRecord = RECORD
  what :INTEGER;
  message :LONGINT;
  when :LONGINT;
  where :POINT;
  modifiers :INTEGER;
END;
what
イベントの種別
message
イベントメッセージ
when
開始時からの時間
where
マウスの位置
modifiers
モディファイアーフラグ
の様に定義されています。
 最初のwhatで,起こったイベントの種類が判ります。正確なところは解りませんが,GetNextEventを呼ぶ度に,登録されているEventHandler全てを呼び出している様で,処理しないイベントに関しては,イベントキューからイベントを削除せずに終了する必要があります。キューにイベント情報を残したままで処理を終えるには,返り血にFALSEを設定します。また,マウス押下イベントに関しては,mouseDownでなくかつmouseUpでない場合にはマウス押下イベントでない,と判断しないと,mouseDown又はmouseUpでないことのみを見て処理を行った場合,仮にmouseDownもしくはmouseUpだった場合にも,違うイベントであると判断されてしまうようです。(なぜだろう?(@@))どちらかのイベントに関してのみ処理を行う必要がある場合には,加えてmouseDownもしくはmouseUpであるかどうかのチェックを入れる等しなければなりません。
 さて,以下の部分の処理ですが,
  intX := recEvent.where.h;
  intY := recEvent.where.v;
  IdentifyPos(wndClickDraw,intX,intY);
  SetPen(wndClickDraw,intX,intY);
  WriteString(wndClickDraw,"Clicked!");
日本語で表現すれば,「マウスイベントが起きた画面上の位置をWindowの局所座標系での座標に変換し,後そのWindow上のマウスイベントが起こった位置に"Clicked!"とメッセージを表示する。」処理であることになります。詳しい説明は省略しますが,そのうちの「画面上の位置をWindowの局所座標系での座標に変換する」関数が,GraphicWindowsモジュールのIdentifyPos関数で,この関数は,与えられた座標(今の例では,水平方向intX,垂直方向intY)を,第一引数で指示されたWindow(今の例ではwndClickDraw)の局所座標系に変換する(VARとして与えられるので,与えた引数の値が変更されて返される。),ものです。
 更に,この処理後,返り値としてFALSEを返していますが,これは,イベントキューからマウスイベントを削除すると,Windowのドラッグ処理等もかっさらって処理してしまうためです。(つまり,Windowの移動ができなくなったりの不都合が生じる。)この辺はちょっと使いにくいかもしれませんね。
PROCEDURE KeyPressed(VAR recEvent :EventRecord) :BOOLEAN;
BEGIN
  IF recEvent.what # keyDown THEN
    RETURN FALSE
  END;
  blnDone := TRUE;
  RETURN TRUE
END KeyPressed;
 次に,KeyPressed関数ですが,これは,キーイベントを処理する関数です。今回は,前回までの,マウスが押されたらアプリケーションを終了するという処理の流れを変更して,マウスが押された場合には,マウスが押された位置にメッセージを表示する形になったため,代わりに,何かキーが押された場合にアプリケーションを終了する流れに変更しまして,その処理を行っているのがこの関数なのです。極簡単な関数なので,説明する必要もないかと思いますが,イベントがキーイベントでない場合には,イベントキューをそのままにして,キーダウンイベントの場合は終了用のフラグを立ててその後キューから情報を削除する様にTRUEを返しています。
PROCEDURE WriteMessage(wndArg :Window);
BEGIN
  SetPen(wndArg,125,250);
  WriteString(wndArg,"ClickDraw;b")
END WriteMessage;
 このWriteMessage関数は,Windowの描画関数で,Windowの初期メッセージを描画します。(…と言いつつ,家の環境では,うまく処理が行われないことも多々あるので,何ともいえないところも…。間違ってはないと思うんだけど。(^^;))
  tskMouse := PushTask(TrackMouse);
  tskKey := PushTask(KeyPressed);
  blnDone := FALSE;
  REPEAT
    PollEventTasks
  UNTIL blnDone;
  PopTask(tskKey);
  PopTask(tskMouse);
 メインルーチンの部分での上の処理は,Macintosh上でのアプリケーションプログラミングの肝であるところの,イベント処理を実際に組み込んでいる部分です。cやPascalで,Macintoshようアプリケーションを書く場合,イベントの処理は,イベントレコードを拾ってきて,それを解析して適当なルーチンを呼び出す,という流れになるのですが,MacMETHの場合,特定のイベントを扱うルーチンを用意して,それを先ずは登録して,後がいとうするイベントがあった場合にはその処理をする,という形を取っています。前に若干触れた様に,どんなイベントであっても,イベント処理用のルーチンは呼ばれますから,これらの間に差異はない様に思えるかもしれませんが,イベント処理の柔軟性と,イベント処理の組み込み易さ,を考えると,この辺りは妥当なレベルにあるのではないかと思います。(マウスイベントの処理を見てください。)
 具体的な組み込みは,イベント処理用に定義された関数(イベントハンドラー型関数)を定義し,PushTask関数にその関数を与えることによって行います。組み込んだイベント処理ルーチンは,PopTask関数で取り除きます。PopTaskに与えるイベントIDは,PushTaskの返り値として得られる値です。

お試し

 さて,どうでしょう?MacMETHにおけるイベント処理御理解頂けましたでしょうか?
 ちなみに,今回紹介したプログラムを実行すると下の様になります。動きましたか?