第4回: イベントとイベントループ (前編)

前回でやり残した、

しかし、このウィンドウ、実に困った問題があります。ウィンドウをつかんで動かしたり、閉じたりなどのウィンドウ操作が全く出来ません。なぜでしょうか? この問題の解決は次回以降に譲りますが、ぜひとも少し考えてみてください。

の問題について、今回メカニズムを説明し、その上でどのようにすれば良いかを説明したいと思います。難しいかもしれませんが、頑張ってください。

今回のプログラム全体を固めたものはこちらです: lecture3.tar.gz

1. 問題の原因はどこに?

まず、問題の原因をはっきりさせましょう。

  wid = wopn_wnd(WA_STD, 0, &r, NULL, 2, text, NULL, NULL);

  slp_tsk(7000);

  wcls_wnd(wid, CLR);

のようになっていますが、slp_tsk(7000);によってタスク、つまりプログラムが一定時間だけ停止しています。プログラムが停止してしまっているわけですから、マウスをクリックしようとキーボードを叩こうと、何も反応しようがありません。(たとえばウィンドウを動かしたりとか)

そこで、プログラムを停止させるのではなく、その代わりに

の処理を繰り返し実行させる必要があります。この繰り返し処理のことを「イベントループ」と言います。以降ではこのイベントループの書き方を説明していきます。

ちなみに、ドラッグによるウィンドウの移動とか、そういうプログラムの本質とは離れたイベントに対する処理は、マイクロスクリプトの場合は記述する必要はありません。従って、例えば ACTION function ... PRESS ウィンドウの枠 のような物を書く必要はなく、暗黙に定義されているものと考えてください。(つまり、C/C++ではそういった物を含めて全部定義しなくてはなりません。)

2. イベントループをまず見てみる

イベントループの処理のフェーズは前に書いたように、「何が起こったかを知る」・「起こった事象に対してプログラムを反応させる」の二つのフェーズに分けることができます。まずはイベントループの全体を見てみましょう。(下のコードは1Bプログラミング道場のprac03.Cから抜き出したものに、少し手を加えたイベントループです。

ソース自体は今回も一つだけです。お手元にダウンロードして手を動かしながら以下の記事を読んでみてください。

  for (;;) {
    wget_evt(&wev0, WAIT);    //イベントを取得
    
    switch (wev0.s.type) {
      
    case EV_NULL:                 // ----  NULLイベント
      if (wev0.s.wid != wid)            // ウィンドウ外
        break;
      if (wev0.s.cmd != W_WORK)          // 作業領域外
        break;
      if (wev0.s.stat & ES_CMD)          // 命令キーが押されている
        break;
      gset_ptr(PS_SELECT, NULL, -1, -1);
      break;

    case EV_REQUEST:              // ---- 要求イベント
      switch (wev0.g.cmd){
      case W_REDISP:	               // 再表示要求
        redisp(wid);
        break;
      case W_PASTE:                      // 貼込み要求
        wrsp_evt(&wev0, 1);
        break;
      case W_DELETE:                     // 保存終了
      case W_FINISH:                     // 廃棄終了
        wrsp_evt(&wev0, 0);
        goto exit_label;
      }
      break;

    case EV_RSWITCH:              // ---- 切り替え+再表示
      redisp(wid);
    case EV_SWITCH:               // ---- 切り替え
    case EV_BUTDWN:	          // ---- マウスボタンプレス
      switch (wev0.s.cmd) {
      case W_PICT:                     // ピクトグラム
        type = wchk_dck(wev0.s.time);
        if (type == W_DCLICK)
          goto exit_label;
        else if (type == W_CLICK)
          break;
      case W_FRAM:                       // ウィンドウの枠
      case W_TITL:                       // タイトル文字
        if (wmov_drg(&wev0, NULL) > 0)
          redisp(wid);
        break;
      case W_WORK:
        // <--- ここにマウスボタン処理を書く
        break;
      }
      break;

    case EV_INACT:	           // ---- 他ウィンドウへ切り替え
      break;
    case EV_DEVICE:	           // ---- 新しいデバイスが挿入された
      oprc_dev(&wev0.e, NULL, 0);
      break;
    case EV_MSG:	           // ---- 他のプロセスからメッセージが来た
      clr_msg(MM_ALL, MM_ALL);
      break;
    }
  }

exit_label:

どうでしょうか? …長いですね。ただし、これでも最小のイベントループです。なぜなら、当たり前の処理(ウィンドウをドラッグしたらウィンドウが移動するとか)を全て書いているからです。

以下、少しずつ上のイベントループを読み解いていきましょう。

3. イベントループの概観

まず、一行目の wget_evt(&wev0, WAIT); は、イベントが発生するのを待って、発生したイベントをwev0に代入します。

二行目以降は長いですが、wev0.s.typeの中身に応じた場合分けになっています。一行目でwev0にイベントを取り出してありますが、その中のwev0.s.typeには、イベントの種類、つまり「何が起こったか」という内容が入っています。

具体的にはwev0.s.typeの中身としては以下のような物があります。合計20個ほどあり、結構煩雑です。詳しくはBTRON3仕様書の「3.1.ウィンドウマネージャ」に譲るとして、ここで興味があるものだけをピックアップします。

EV_NULL
何も起こっていないことを示すイベントです。wev0.s.posに、マウスポインタの位置が入ります。
EV_BUTDWN
マウスボタンが押されたことを示すイベントです。wev0.s.cmdに、ウィンドウのどこが押されたかが入ります。例えば、W_PICTならピクトグラムが、W_FRAMならウィンドウ枠が押されたことを示します。詳細は仕様書を参照してください。
EV_KEYDWN
キーボードのボタンが押されたことを示します。

ここに挙げたのは一部だけです。細かいことに関しては、不親切なようですが、仕様書を見ましょう。(ここでは残念ながら説明し切れません…。)

あと、redisp()という謎の関数を用意しているのですが、その部分は次回以降に説明します。興味のある方はソースを読んでください。

4. イベントループをいじってみる

上のプログラムは、いわゆる「スケルトン・プログラム」というもので、プログラムの骨組みだけをあたえた物です。従って、ウィンドウの移動とかそういった当然の処理は入っていますが、例えばマウスをクリックしたら点を書くとか、線を書くとか、そういったプログラムごとに異なる処理は全く入っていないわけです。

この骨組みに対して「肉付け」をしていくことで、思い通りのプログラムが書けるようになります。なにも、骨から作る必要はないわけで、今後はこのプログラムをベースに、肉付けをすることで単純なプログラムから複雑なプログラムまで、様々なものを書いていきましょう。

4.1. とりあえずいじってみましょう

今回はまず、単純なところからいじってみましょう。

      case W_WORK:
        // <--- ここにマウスボタン処理を書く
        break;
      }

の部分ですが、イベントループの構造をよく見ると、ここに制御がわたるのは、EV_BUTDWNが発生し(つまりマウスのボタンが押されて)、その場所がW_WORK(ウィンドウ作業領域; ウィンドウの中身の部分のこと)だった、ということです。

そこで、作業領域が押されたときにブザーを鳴らすようにしてみましょう。上の部分を以下のように書き換えて、再コンパイル・実行してみてください。

      case W_WORK:
        sig_buz(0);
        break;
      }

すると、ウィンドウ内作業領域でマウスボタンをプレスした時にマウスボタンが押された時にだけ、ブザーが鳴るはずです。うまくいきましたか?

演習問題

  1. ウィンドウ内作業領域でマウスボタンがプレスされたときに、システムメッセージ領域に何か文字列を表示してみてください。
  2. マウスボタンではなく、キーボードがプレスされたときにブザーを鳴らしてみましょう。(ヒント: キーボードが押されたときにはEV_BUTDWNが発生しますよね?)
  3. マウスボタンがクリックされた場所の座標値を得るにはどうしたら良いでしょうか? 仕様書を見て調べてみましょう。(ヒント: 仕様書で"PD"と表記されているのは、"Pointing Device"のことで、つまりマウスやペンのようなデバイスの事です。つまり、PDの座標値を知りたいわけですね。)
  4. 関数redisp()は何を行っているのでしょうか? 調べてみましょう。

[ 戻る ]

Last modified: Sun Oct 3 16:39:17 2010
Valid XHTML 1.0!