GUIアプリケーションは「メインとは」で定義された初期描画のあとは、すべてイベントによって処理が進みます。イベントにはマウス操作、キーの入力、その他(状態変化など)があります。
このテーマに関連するものとして、 バインドとイベント名 でも解説をしているので併せてお読みください。
GUIライブラリ内で発生したイベントをMindのプログラムで受け、処理するための仕組みです。
まずサンプル "hellowinButton.exe" を実行してみて、次にソースコードもご覧ください。
プログラム例: hellowinButton.src
メインとは
ラベル1は ID
ボタン1は ID
メインウィンドウで ラベルのIDを取得し ラベル1に 入れ
「こんにちは。」を ラベル1に テキストを設定し
メインウィンドウで ボタンのIDを取得し ボタン1に 入れ
「 終り 」を ボタン1に テキストを設定し
実行終りの 実行情報を ボタン1に イベント処理を登録し
ラベル1と ボタン1を 2個で これらを有効化し配置する。
このプログラムではラベルとボタンが1つずつあります。
「終り」のボタンを押すとイベントが発生しプログラムが終了します。
■イベント処理を登録
先のソースプログラムで強調表示されている行がイベントの登録をおこなう部分です。
構文=
<実行情報>を <ウィジェットID>に イベント処理を登録 → ・
「実行情報」とは、すべての処理単語に備わっている属性で、その処理単語を間接的に呼び出すための特殊な整数値です。実行情報を得るには、
<処理単語>の 実行情報
と記述します。上記サンプルでは、
実行終りの 実行情報を ボタン1に イベント処理を登録し
と書いているので、処理単語「実行終り」の実行情報を渡すことになります。結果としてボタン1が押されると処理単語「実行終り」が実行されプログラムが終了します。
「実行終り」はMindに標準で備わっている単語なのでユーザが定義する必要はありませんが、通常はユーザがイベント処理をおこなう処理単語を明に定義し、その処理単語の実行情報を「イベント処理を登録」に渡して登録する段取りになります。
注: 既に有効化したウィジェットに2度目の「イベント処理を登録」すると誤動作が起きます。イベント処理の登録はウィジェット有効化前の1回だけとしてください。
記:「実行情報」は 「Mコード」 と等価な単語です。
旧名である「Mコード」と書いても構いません。
■イベント処理の書き方
プログラム例: color-change.src
ボタン1記憶は ID。
イベント処理語
↓
ボタン1押下処理とは (引数1、引数2、・・、引数個数 → ・)
イベント引数を捨て
赤色を ボタン1記憶に 文字色を設定し
黄色を ボタン1記憶に 背景色を設定し
ボタン1記憶を 変更を反映する。
メインとは
〜略〜
(イベントを登録)
ボタン1押下処理の 実行情報を ボタン1に イベント処理を登録し
〜略〜
上記がイベント処理の定義例です。基本的には普通の処理単語定義と同じなのですが、パラメータの渡し方が特殊です。
まず、引数無しでイベント処理が呼ばれた場合は(スタック仕様をコメントとして書けば)次のようになります。
ボタン1押下処理とは (0(整数) → ・)
スタックトップに整数0が積まれて入ってきます。スタックトップは引数個数を表す整数値‥という取り決めなので引数が無いことを表します。
一方で、「ボタン1押下処理」の呼び出し親側は、
ボタン1押下処理の 実行情報を ボタン1に イベント処理を登録し
と書かれていて引数の指定がありませんからバランスは取れています。このような記述の場合、引数無しとして扱われます。
注: ボタンなど、イベント登録可能なウィジェットにおいては、テキストの設定(ボタンの名札)にて、末尾に ¥ がある文字列は指定できません
■イベント処理に引数を渡す
イベントが発生したときに対応する処理単語が起動される・・イベント処理としてはこれで十分と思われるかもしれません。
たとえば電卓のアプリケーションを考えてみます。10個の数字キーそれぞれに対応するイベント処理語を10個も定義し、それらを呼び出すのは煩雑です。
イベント処理語は1つだけ定義しておき「どのキーが押されたか」の情報をイベント処理語に与えるようにすれば、プログラムが簡単になります。そのためにイベント引数という仕組みがあります。
イベント処理語に引数を渡したい場合は次のように書きます。
構文=
<引数>を <ウィジェットID>に イベント引数を設定 → ・
注:「イベント引数を設定」は「イベント処理を登録」の
直後に書いてください
注:引数全体あるいは個々の引数で 末尾に ¥ があると
実行時にエラーになるため工夫により回避してください
プログラム例:
ボタン1押下処理の 実行情報を ボタン1に イベント処理を登録し
「"abc def" 234」を ボタン1に イベント引数を設定し
上では「"abc def" 234」という引数群をイベント引数として渡しています。
この場合、「ボタン1押下処理」が起動されたときスタックには次のようなデータが積まれます。(スタック仕様をコメントして書けば・・)
ボタン1押下処理とは (「abc def」、 「234」、 2 → ・)
上記のように、スタックの3番目に引数1が、スタックの2番目に引数2が、そしてスタックトップには引数の数であるところの整数(=2)が積まれて入ってきます。
注:引数個数だけは整数値ですが引数本体は文字列です
複数の引数を空白で区切り最終的に1本の文字列として「イベント引数を設定」に渡してください。もし引数のどれかが空白文字を含む場合は上記例のように、"〜"の形式でダブルクオート記号で囲んでください。
注:引数群全体あるいは個々の引数で末尾に ¥ があると実行時にエラーになるため、工夫により回避してください。
引数を持つイベント処理の例としてサンプル "event-withargs.src" を見てみましょう。イベント処理語の定義は以下のようになっています。
ボタン1押下処理とは (引数1、引数2、・・、引数個数 → ・)
引数1は 文字列
引数2は 文字列
(引数個数が) 2と 異なる ←引数個数のチック
ならば 「引数数異常」で 重大エラー
つぎに
引数1と 引数2に 入れ ←引数を局所変数に降ろす
〜略〜
引数のある処理単語の場合は上記が典型的な受け取り方になります。
まずはスタックトップにある引数個数が設計通り”2個である”ことを確認します。
次に、その奥にあるスタックデータ2つを局所変数に降ろしています。
■イベント処理内で参照するウィジェットID
先のプログラムをもう一度見てみます。
プログラム例: color-change.src
ボタン1記憶は ID。
ボタン1押下処理とは (引数1、引数2、・・、引数個数 → ・)
イベント引数を捨て
赤色を ボタン1記憶に 文字色を設定し
黄色を ボタン1記憶に 背景色を設定し
ボタン1記憶を 変更を反映する。
メインとは
ボタン1は ID
メインウィンドウで ボタンのIDを取得し ボタン1に 入れ
ボタン1を ボタン1記憶に 入れ
〜略〜
上記の赤字部分に注目してください。「ボタン1記憶」という変数を設置しています。
一方、「メイン」の中でも類似した「ボタン1」という変数を設置しています。
IDを入れる変数「ボタン1」は「メイン」の中で局所変数として定義されていますが、メインが初期画面を描画している間はこの変数が使えるので良いのですが、イベント処理語に飛んだときは既に「メイン」の処理は終わった後なので変数「ボタン1」は不定であるだけでなく、外の処理単語からこのラベルを参照することもできません。
そこで、別に大域変数として「ボタン1記憶」というものを設け(定義場所はそれを使うイベント処理語の前ぐらい)、メインの中で「ボタン1」の複製として値を格納しておき、イベント処理ではその複製変数からボタン1のIDを参照するようにしています。
イベント処理ではこのような仕組みを多く使うようになるかと思います。
■イベント引数を捨て
構文=
<引数1>、<引数2>、・・、<引数個数>
イベント引数を捨て → ・
もう一度、先のサンプルプログラムを見てみます。
プログラム例:color-change.src
ボタン1押下処理とは (引数1、引数2、・・、引数個数 → ・)
イベント引数を捨て
〜略〜
このプログラムではイベント引数は使っていません。このような場合は処理単語に入ってすぐに、
イベント引数を捨て
と書くことで引数を捨ててください。
- 注:注:
-
イベント引数を使わないイベント処理語では冒頭で「イベント引数を捨て」と書いて明に捨ててください。(忘れるとスタックエラーで落ちます)
ここでの解説とは別に、単に所定の時間だけ待つ「遅延」という処理単語もあります。詳細は 遅延 のセクションをお読みください。
■遅延実行を予約
指定した時間経過後にイベントを発生させる機能です。
短い時間だけ情報を表示したあと自動的に消したり、遅延実行を繰り返すことで一定間隔で繰り返し処理をおこなうなどの用途に使います。
構文=
(ステップ1:予約時)
↓整数 ↓引数不要なら空列を渡す
<ミリ秒>と <実行情報>と <引数>で 遅延実行を予約 → ・
(ステップ2:イベント発生時に渡されるパラメータ)
↓登録時の引数が空列のときは整数0のみ渡される
スタック→ 0(引数個数)
↓登録時の引数が空列でないとき
スタック→ 引数1,引数2,・・、 n(引数個数)
↑引数が3個ならn=3
プログラム例: after.src
遅延後の処理とは (0 → ・)
〜略〜。
2000ミリと 遅延後の処理の 実行情報と 空列で 遅延実行を予約し
↑2秒の遅延。「ミリ」はコメントと同じで意味はありません
<ミリ秒>は経過時間を指定するミリ秒単位の整数です。
<実行情報>については 「
イベント処理を登録」 の解説を参照してください。
<引数>は、もし引数を与える必要が無ければ上の例のように空列を渡してください。
イベント引数は文字列で、
「abc」を ・・
のように指定します。複数の引数は
「abc def」を ・・
のように空白で区切って並べてください。引数に空白文字を含む場合には、
「"ab c" def」を ・・
のように指定してください。
なお、引数(群)文字列末尾に ¥ 記号は付けられません。また、{ } 記号は使えるものの、開きと閉じでバランスが取れている必要があります。(アンバランスで使うとエラーを検出します)
遅延後に実行される処理単語はほかのイベント処理と同じであり、次のように定義します。
プログラム例: after.src
遅延後の処理とは (引数1、引数2、・・、引数個数 → ・)
↓引数が不要なら明に捨てる
イベント引数を捨て
ボタン1記憶を ウィジェットを削除すること。
遅延後のイベント処理の中でさらに遅延実行を予約することで、繰り返しおこなうこともできます。
プログラム例:
遅延後の処理とは 仮定義。 ←互いに呼び合うための機構
次の遅延予約とは
2000ミリと
遅延後の処理の 実行情報と 空列で 遅延実行を予約。
遅延後の処理とは 本定義 (引数1、引数2、・・、引数個数 → ・)
イベント引数を捨て
<何かの処理>
次の遅延予約すること。 ←2回目以降の予約
メインとは
・・・・し
・・・・し
次の遅延予約し ←初回予約
。
繰り返しの遅延実行をおこなう場合、初回の遅延実行予約と2回目以降のそれとで同じ予約内容になるはずなので、予約だけをおこなう処理単語を独立定義しておくことを勧めます。
また、上記で
遅延後の処理とは 仮定義。
を置いていることに注目してください。
「次の遅延予約とは」の中で「遅延後の処理」を呼んでいる一方で、「遅延後の処理」もまた「次の遅延予約」を呼んでおり、互いに呼び合う関係になっています。Mindでは既に定義されている語しか呼ぶことができないのですが、このような状況を解決するのが仮定義/本定義です。最初に仮に処理単語を定義しておき、その少し先で本定義を行うことで”互いに呼び合う”ことを実現します。
時間指定がミリ秒単位になっているものの、その単位で正確なわけではありません。誤差があることを承知の上で使ってください。たとえば1秒の遅延実行を100回繰り返すとぴったり100秒になると仮定しないでください。(誤差は累積されます)
長い時間を要するプログラム・・正確にはMind記述の処理が長く、その間GUIエンジンに制御が返されないもの・・はそのあいだマウスやキーボードが反応しないという問題が起きます。
GUIアプリケーションは、
メインも含めすべてのイベント処理は素早く終わらせ、GUIエンジンにその都度制御を戻す
ことが肝要です。しかし場合によってはやむを得ず長い時間がかかる処理もあるかもしれません。そのような場合は次の方法で対処してください。
■一つの保留イベントを処理
構文=
長くかかる処理の多くはループになります。
そのような場合は、ループの中に 「一つの保留イベントを処理」 を挟むことで長い処理中であっても時々、GUIエンジンにイベント処理をおこなわせることができます。「
時々、少しだけGUIエンジンに制御を与える」というものです。
プログラム例:
○○とは
ここから
・・・
ならば 打ち切り
つぎに
・・・
・・・
一つの保留イベントを処理し ←これを挟む
繰り返すこと。
一つの繰り返しにどのぐらいの時間がかかるかはアプリケーション次第なので具体的には言えませんが上記例であれば、数ミリ〜数十ミリ秒に1回ぐらいの頻度で「一つの保留イベントを処理」実行されると良いと思います。
アプリケーションによってはもっと高速でループを回るものもあるかもしれません、その場合、「一つの保留イベントを処理」の呼び出し頻度が高すぎてしまうかも知れません。
たとえば、文字列を処理するのに1文字ずつの処理をループにしているようなケースではループが高速過ぎます。
GUIエンジンとしてはそれで問題ないのですが、アプリケーション側が重くなりがちなのでそのような場合はループカウンタを設置し、たとえば10回のループで1度だけ「一つの保留イベントを処理」を実行する(間引きする)のも良い方法です。
記:保留中のイベントが無ければ「一つの保留イベントを処理」はすぐ戻って来るので、この処理単語のオーバーヘッドはそれほどではありません
逆に、1回だけのループであってもかなりの時間がかかってしまうというケースでは、「一つの保留イベントを処理」の呼び出し頻度が低くなり、GUIが反応しずらくなることも考えられます。そのような場合の改善策としては次の単語を使ってください。
■すべての保留イベントを処理
構文=
この単語は先の「一つのイベントを処理」と同様に使います。GUIエンジンが保留しているすべてのイベントを一度の呼び出しで処理させます。イベントがかなり溜まっていることが予想される場合に利用します。
使い方としては、やむを得ず長い処理をおこなってしまい、さらにまだ処理が続くようなとき、その合間にこの単語を呼ぶことで多数のイベントの滞留を解消できます。
注:本処理単語をたとえばループ内に入れて頻繁に実行しないでください。ループ頻度や回数によってはGUIエンジンの動作が不安定になることがあります。
■少しの保留イベントを処理
構文=
「一つの保留イベントを処理」と「すべての保留イベントを処理」の中間的な機能です。
たとえばループ1回ごとに「一つの保留イベントを処理」を呼んだのではGUIエンジンに渡す時間が少し足りないような場合に利用します。
■Mind標準の処理単語で処理に長くかかるもの
- 「n秒待つ」系の処理単語
-
コンソールアプリケーションでも使える単語として「一秒待つ」などの時間待ち機能がありますが、これはGUIアプリケーションでも利用できます。それらの処理単語はGUIライブラリリンクの時は内部的に、時間待ち中にGUIエンジンに制御を渡すよう作られているので、時間待ち中もイベント処理がおこなわれます。
- ファイルの複写
-
以下の2語、
ファイル複写
<論理ファイル>を <論理ファイル>に 入れる
はファイルサイズが大きい場合に処理時間がかかりますが、これらの処理単語は「一つの保留イベントを処理」を内部で呼び出しており、対処済みと考えてください。
- 外部プログラムの起動
-
たとえば 「プログラム実行」 によって外部のプログラムを起動すると、そのプログラムが終了するまで戻って来ず、GUIが反応できなくなるためGUIライブラリでは定義されていません。
代替として使えるのは「子プロセス起動」「子プロセス実行」 です。詳しくは 外部プログラム/自プログラムの実行 をお読みください。
- 「子プロセス終了を待つ」、「子プロセス終了?」
-
GUIライブラリにリンクしたアプリケーションでは、これらの処理単語は「一つの保留イベントを処理」を内部で定期的に呼び出しており対処済みです。
- ソケットのアクセス
-
Mind Version 9.0 のGUIライブラリにはソケット機能は含みません。
以下のサンプルプログラムがあります。実際に動かしてみてください(このほかにも多くのサンプルプログラムがイベントを使っています)。
サンプルプログラム:
- hellowinButton.exe
- hellowin.exe に終了ボタンを付加したもの
- color-change.exe
- ボタンを押すとボタンの色と背景色を変える
- event-simple.exe
- 「遅延実行を予約」を使い、時間経過後に表示テキストをクリアする
- event-withargs.exe
- 前記の event-simple に似ているが、「遅延実行を予約」に引数を与えるもの