イベント発生時のコールバックとしてのサブルーチンの利用

タイトルを見るとオドロオドロシイ感じがするかもしれませんが、まずはサンプルプログラムを見てみましょう。

GraphicsWindow.MouseDown = OnMouseDown                         <=== (1)

Sub OnMouseDown                                                <=== (2)
  x = GraphicsWindow.MouseX
  y = GraphicsWindow.MouseY
  GraphicsWindow.PenColor = GraphicsWindow.GetRandomColor()
  GraphicsWindow.DrawEllipse(x,y,20,20)
EndSub

実行したら、グラフィックウィンドーが開くのでマウスを左クリックしてみよう。

プログラム自体はとてもシンプルで、メインの処理が一行(1)と、サブルーチンの定義(2)があるだけです。しかも、(1)ではサブルーチンの名前をGraphicsWindow.MouseDown にセットしているだけで、このサブルーチン自体を呼び出してはいません。ちなみに前回、サブルーチンの定義を行っている部分(Sub からEndSubまで)はメインの処理の流れのなかでは無視されることを説明しました。しかし、このプログラムを実行すると明らかにサブルーチンOnMouseDownの処理が何度か、しかもマウスボタンをクリックした際に実行されることがわかると思います。しかし、このサブルーチンOnMouseDownを呼び出してる箇所は実際にはどこにも見当たらないことに気づくと思います。

Small Basicのプログラムは書かれた順に実行されます。プログラムの流れを指で辿るように追ってゆくことができます。しかし例外があります。なにか特別な状況が発生した際にはプログラムの通常の流れとは外れたところで処理の流れに割り込みをかけて、その状況のための特別な処理を実行することができます。このような「特別な状況」のことを「イベント」と呼び、イベントを処理するプログラムの部分を「イベントハンドラ」と呼びます。ハンドラ、とは「処理する者」位の意味です。

あまりいい例えではないかもしれませんが、テレビの番組を放送中に重大事件が発生すると番組をいったん中断し、その事件についての報道を行った後もう一度番組に戻ってくることがありますよね。この場合、重大事件がイベント、重大事件の報道をイベントハンドラに相当します。重大事件はいつ発生するか分からず予定の立っているものではありません。また、重大事件の報道が終わると、通常の番組の中断された箇所に戻り番組が再開されるでしょう。

イベントとイベントハンドラの関係もそれに似ています。

でも、イベントとは具体的にどんなものなのでしょうか? Small Basic では幾つものイベントが提供されています。例えば、マウスのボタンが押された時、キーボードからキーが押された時等などにイベントが発生します。イベントハンドラはサブルーチンの形で定義します。そして、イベントハンドラ(サブルーチン)をイベントに代入することでイベントとイベントハンドラを関係づけることができます。

先ほどのサンプルプログラムの場合、(1)がイベントとイベントハンドラを関連付けている箇所になります。GraphicsWindow.MouseDown はマウスボタンが押された瞬間に発生するイベントです。ちなみに、GraphicsWindow.MouseUpというイベントもあり、こちらはマウスボタンが離された瞬間に発生します。

ところでイベントに対してイベントハンドラを複数関連付けた場合どうなるのでしょうか?

GraphicsWindow.MouseDown = OnMouseDown1                               <==== (1)
GraphicsWindow.MouseDown = OnMouseDown2                               <==== (2)

Sub OnMouseDown1
  GraphicsWindow.PenColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.BrushColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.FillEllipse(GraphicsWindow.MouseX, GraphicsWindow.MouseY,50,50)
EndSub
  
Sub OnMouseDown2
  GraphicsWindow.PenColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.BrushColor=GraphicsWindow.GetRandomColor()
  GraphicsWindow.FillRectangle(GraphicsWindow.MouseX, GraphicsWindow.MouseY,20,20)
EndSub

(1)と(2)でGraphicsWindow.MouseDownというイベントに対し、イベントハンドラが二回定義されています。実行すると分かりますが、イベントに対し複数回イベントハンドラが関連付けられた場合、最後に関連付けられたイベントハンドラが有効になります。この場合、(2)で関連付けられたサブルーチン、OnMouseDown2ですね。

その他のイベント

他にどのようなイベントがあるのでしょうか?ここに列挙したものはSmall Basic で提供される全てではありませんが、以下に主なイベントをリストしてみました。

マウスイベントのイベント
GraphicsWindow.MouseDown
マウスボタンが押されたときに発生するイベント
GraphicsWindow.MouseUp
マウスボタンが離されたときに発生するイベント
GraphicsWindow.MouseMove
マウスカーソルが移動した際に発生するイベント
グラフィックスウィンドーでのキーボードのイベント
GraphicsWindow.KeyDown
グラフィックスウィンドーでキーボードのキーが押されたときに発生するイベント
GraphicsWindow.KeyUp
グラフィックスウィンドーでキーボードのキーが離されたときに発生するイベント
グラフイックスウィンドーでテキストを入力した際のイベント
GraphicsWindow.TextInput
グラフィックスウィンドーでテキストが入力されたときに発生するイベント
タイマーイベント
Timer.Tick
前もってTimer.Interval に対して定義された時間間隔(ミリセカンド[1/1000秒]単位で指定)ごとに発生するイベント

マウスイベントを利用して絵を描いてみる

さて、では試しにマウスイベントを利用して絵を描く簡単なプログラムを作ってみましょう。まずは、どういう方針で作るか考えてみます。例えば…マウスカーソルが動いたときにその座標に点を打ってみることにします。とは言っても、マウスカーソルが動いた場合常に点を打つのではなく、やはり例えば左ボタンが押されている間だけ描画したいので、Mouse.IsLeftButtonDown というプロパティを使ってみます。

GraphicsWindow.MouseMove = OnMouseMove

Sub OnMouseMove
  If Mouse.IsLeftButtonDown Then
    GraphicsWindow.SetPixel(GraphicsWindow.MouseX, GraphicsWindow.MouseY, "Black")
  EndIf
EndSub

Mouse.IsLeftButtonDown はマウスの左ボタンが押されているかどうかを調べるためのプロパティです。"Is Left Button Down ?(左ボタンが押下されている?"位の意味ですね。試しに実行してみましょう。

うーん...正直微妙。

練習問題として試しに作ってみるとよいでしょう。ヒントを幾つか出しておきます。マウスが移動する直前の座標を記憶しておいて、マウス移動後現在の座標と記憶しておいた座標の間で線を引くとそれらしくなります。こんな感じ。

例を置いておきますが、一応隠しておきますね。

GraphicsWindow.MouseMove = OnMouseMove
GraphicsWindow.MouseDown = OnMouseDown
GraphicsWindow.MouseUp   = OnMouseUp

Sub OnMouseMove
  If fmouse = 1 Then
    x = GraphicsWindow.MouseX
    y = GraphicsWindow.MouseY
    GraphicsWindow.DrawLine(px, py, x, y)
    px = x
    py = y
  EndIf
EndSub

Sub OnMouseDown
  fmouse = 1
  px = GraphicsWindow.MouseX
  py = GraphicsWindow.MouseY
EndSub

Sub OnMouseUp
  fmouse = 0
EndSub

イベントを使わないで同じようなプログラムを作ってみる

実際にはイベントを使わないで同じようなことはできます。

GraphicsWindow.Show()

loop:
If Mouse.IsLeftButtonDown Then
  x = GraphicsWindow.MouseX
  y = GraphicsWindow.MouseY
  If fmouse = 1 Then
    GraphicsWindow.DrawLine(px, py, x, y)
  EndIf  
  px = x
  py = y
  fmouse = 1
Else
  fmouse = 0
EndIf
Goto loop

これはポーリング方式と呼ばれることもあります。ポーリング(polling)のpollとは「投票する」とか「聞き取り調査する」と言うような意味ですが、コンピュータ用語として使われる場合、入出力装置(マウスとかキーボードなども含む)から入力があるかを一つずつ聞いて回るやり方をさします。このサンプルプログラムの場合、loop: と Goto Loop の間の処理を延々と続けながら毎回マウスボタンの状態をチェックしています。

イベントを使う方法とポーリングする方法、どちらがよいかというと、それはケースバイケースとしか言えないのですが、イベント方式の場合イベントは勝手に発生しますので、イベントが発生しているタイミング以外の時間、プログラムは好きなことができるという利点があります。ポーリング方式の場合、処理は全てLoop:とGoto Loopの間になければいけません。

この程度の小さなプログラムの場合大した問題にはなりませんが、もっと多きな複雑なプログラムの場、この制限はプログラムの全体を見通すのに問題となることがあります。

また、ポーリング方式の場合、マウスボタンが押されておらず実質的に何もする必要のない場合でも、loop: とGoto loopの間のループは常に実行されており、ある意味パソコンにとっては必要のない余計なしょりを延々と繰り返しているともいえます。イベント方式の場合、イベントが発生した場合にだけ処理を行えばよく、たいていの場合パソコンの負荷を下げるのに有効です。

一方、プログラミングにまだ慣れていない人の場合、イベント方式はプログラムの流れがうまく理解できず悩むことがあるようです。

まとめ

イベントとイベントハンドラについて説明しました。イベントを利用すると通常のプログラムの流れとは外れた突発的な出来事に対して処理を行う事ができます。