イベントをキャンセルする | マーブルルール

イベントをキャンセルする

Tips

スポンサーリンク

この記事で扱っていること

  • イベントをキャンセルする方法

を紹介しています。

LabVIEWのイベントストラクチャは、ユーザーが起こしたイベントを検出してその種類に応じて処理を分けるのに使用します。

多くの場合、イベントには即時反応してほしいのですが、間違えてイベントを発生させてしまった場合にこれをキャンセルするという機能がイベントストラクチャ自体にはありません。

(フィルタイベントの場合、「破棄?」というノードを使用して条件次第でイベントを無かったことにできなくはないのですが、全てのイベントに対してフィルタイベントが定義できるわけでもないですし。)

また、意図せず連続でイベントを起こさせないように、例えばブールのボタンと無効プロパティを駆使するという方法もありますが、同じプログラムで意図して連続でイベントを起こさせるという操作に対応することができなくなります。

そのため、「イベントの検出は即時行えるようにしつつ、一定時間以内にキャンセルボタンを押せばイベントを無かったことにできる」という仕組みはプログラム的に設ける必要があります。

今回の記事ではそんな実装の例を考えてみました。

スポンサーリンク

どんな結果になるか

本来行いたい処理に、イベントをキャンセルできる猶予を持たせるための仕組みを追加していくことになりますが、この「本来行いたい処理」というのはもちろんプログラムの中身によって様々なので、今回は例として追加や削除ボタンを押したら数値が変化するというシンプルな内容を「本来行いたい処理」の例とすることにします。

フロントパネルには、この「本来行いたい処理」用にあるボタンの他に、キャンセルの猶予時間を決めるための数値制御器と、キャンセルボタンをつけています。

プログラムを実行し、追加ボタンや削除ボタンを押すと数値表示器の値が変化するのですが、例えば追加ボタンを押してからキャンセル猶予時間内にキャンセルボタンを押すと、追加ボタンを押したことがなかったことにできます。

当然、この記事を読んでくれている方の「本来行いたい処理」はこんなシンプルな事ではないと思いますが、以下で紹介する内容を応用して頂ければと思います。

プログラムの構造

プログラムは、いわゆるイベントを検出するためだけのイベント処理ループと、そのイベントに呼応して特定の処理(上記の「本来行いたい処理」のこと)が実行するためのループがあるとして、それらの間に、発生したイベントを一時的にためておく、イベントプールのループがあります。

ブロックダイアグラムでは、各ループにそれぞれEvent Handling Loop、Main Loop、Pool Loopと名前を付けています。(以下の説明でもこの名称を使用します)

よくある、イベント駆動型のプログラム、特に今回のようなステートマシンをベースにしたプログラムの場合、通常であればEvent Handling LoopからMain Loopにキューで列挙体や文字列を送ってその内容に応じてMain Loopのステートが変わります。

イベントをキャンセルする仕組みをPool Loopに持たせて、このPool Loopもステートマシンとして動かすために、Pool Loopステートマシン用の列挙体(event control state)と、本来行いたい処理を行うMain Loopステートマシン用の列挙体(main state)二つを扱えるようにこれらをクラスタでまとめて、Event Handling LoopとPool Loopでキューの通信を行えるようにします。

イベントが発生すると、そのイベントに応じた処理がPool Loopにためられ、Pool Loopで猶予時間が経過するのを待ってから、このPool LoopからMain Loopに改めて別のキューでmain stateの列挙体を送ります。

では、具体的にそれぞれのループの中身を紹介します。

まずは、Event Handling Loopです。

このループはイベントストラクチャを含んだループで、各イベントの内容によってevent control stateとmain stateの二つの列挙体をPool Loopに送ります。

このとき、キャンセルボタン以外のイベントでは、event control stateとしてnew eventを指定します。

一方でmain stateとしては、本来そのイベントに呼応してMain Loopで行わせたい処理に対する列挙体項目を指定します。

例えば追加ボタンが押された場合にMain Loopでaddというステートに遷移させたい場合には、追加ボタンの値変更イベントで、event control stateとしてはnew eventを、main stateとしてはaddを指定する、ということになります。

また、キャンセルボタンの値変更イベント時にはevent control stateではcancelを指定します(このときmain stateはどんな値でも構いません)。

上記イベントに呼応したMain Loopの中身を先に紹介します。

今回はとてもシンプルなステートマシンですが、ここは本来行いたい処理の内容に応じて変えてください。

次にPool Loopです。

このPool Loopもステートマシンになっていて、Event Handling Loopからの指令を受けることで処理が進みます。

まずはnew eventステートです。

このステートでは、「main stateの内容」と、「このイベントを検出したときの時間情報をティックカウントの値として受け取ったevent start timing」の二つの値を扱うためのクラスタを作成し、配列連結追加で配列に組み込んでいます。

キューステータス取得の関数を使用して、このキューに何もデータが入っていない場合(以下の図の、ケースストラクチャが0の時の処理)にはevent control stateとしてwaitを指定した定数をエンキューしています。

こうすることで、イベントが発生して自分自身で次のステートであるwaitに進むことができます。

一方でキューにデータが一つでも入っている状態ではこの後で紹介するように既にevent control stateとしてwaitを指定した要素がキューに入力されているのでこのnew eventの時点で新たにwaitを指定する必要がありません。

waitのステートでは、クラスタ配列の中から最初の要素(つまり一番古い要素)を取り出して、その要素の中のevent start timingの値と、このwaitステートが実行された際に新たに読まれたティックカウントの値とで引き算を行い、キャンセル猶予時間との大小を調べています。

つまり、new eventステートで得られたティックカウント値とwaitステートで都度読まれるティックカウントの値を見て、キャンセル猶予時間が経過したかを判定し、経過していなかったらwaitを再び実行、経過していたらpass event to mainに進む、ということになります。

この仕組み上、基本的にPool Loopでのwaitステートはキャンセルボタンが押されない限り間髪を入れず実行され続けることが多いので、Pool Loop自体も全力で回りがちです。

この点は待機関数で5ミリ秒ほど指定しておく方が健全かもしれません。

pass event to mainでは、クラスタの配列の要素0を一つだけ削除し、その削除対象の要素のmain stateを、Main Loopと通信をしているキューにエンキューしています。

このpass event to mainは、waitステートにて、キャンセル猶予時間を経過したイベントのみたどり着くことができるので、結局「キャンセル猶予時間までキャンセルされなかった一番古いイベント」がMain Loopに渡り、Main Loopで処理されます。

また、残りのクラスタ配列に要素が1つ以上入っている場合には、残りの要素に対してまたwaitステートを実行します。

pass event to mainで配列から削除を実行し残ったクラスタ配列に要素が一つもない場合には何もしません。

最後にcancelステートですが、これはpass event to mainと似ていて、単にクラスタ配列から一番古い要素を削除しており、違いとしてはMain Loopにmain stateの値を渡さない、という点になります。

もし削除されていない配列の要素が1つ以上余っていたら、waitステートに移る点もpass event to mainと同じです。

何も処理されていない場合には即時反応させる場合

上記のプログラムでは、メインのループで何も処理がされていない状態でも、どのイベントに対しても常に指定した待機時間待つ必要がありました。

ただ、場合によっては、「処理がたまっているイベントのみキャンセルを受け付けて、何も処理されていない場合には即時反応させたい」ということもあると思います。

その場合には、イベントが検出された後に他のイベントに紐づいた処理が実行されている状態なのかを確認して、何の処理も実行されていないならすぐにメインのループを動かすような仕組みにする必要があります。

これにはいくつかの実装方法があると思いますが、一例を紹介します(たぶんこれよりも効率のいい実装もあるかとおうので参考程度にどうぞ)。

全体の構造とては、Pool LoopとMain Loopの間にノーティファイアを追加しました。

発生したイベントに対応する処理がMain Loopで何も実行されていないのであれば、キャンセル猶予時間とは関係なく即時その処理が実行され、Main Loopで処理が終わっていない場合にはキャンセル猶予時間が過ぎてもイベントに対する処理がMain Loopに渡らないようにする、これらの判定に使うフラグ(accept flagという名前にしています)をノーティファイアで扱います。

Main Loopの方では、イベントに対する処理が実行され終わったらノーティフィケーションをPool Loopに送信するようにしています。

下記の図でaddやsubtractの中で1000ミリ秒待つという指定をしていますが、これはこの記事のプログラムの通りに組んだ時に効果のほどを確かめる用に設けていたもので、実際のプログラムでわざわざ待機する時間を設ける必要はありません。

Pool Loopでのノーティファイアの扱いは以下の図のようにしていて、ノーティファイア待機はタイムアウト値を0としていて常に最新の値を受け取るようにしています。

Main LoopからTrueのノーティフィケーションが送信されていない場合にはタイムアウトがTrueとなりますが、このときは選択関数の結果として「前のループの値」が出力されます。

そのため、Main LoopからTrueのノーティフィケーションが渡されて以降は、Pool Loopの方でこのフラグをFalseにするステートが実行されない限り、タイムアウトする、しないに関係なく選択関数からはTrueが出ます。

では実際にPool Loopでこのフラグ値がどうなっているかを見ていきます。

まずはnew eventステートです。

ここにはケースストラクチャを追加していてaccept flagのブール値によって即座にイベントを実行させるか、今まで通りクラスタ配列に組み入れてwaitステートに進むかを分けています。

accept flagがTrueになる条件は、プログラム実行後に一番最初にこのnew eventが実行された時か、Main Loopで特定の処理が実行された後、ということになり、それ以外はFalseとなってPool Loopでwaitステートになるので、キャンセルを受け付けることができます。

あとのステートは以下の図を参考にしてみてください。

大きく変わるのはwaitステートで、pass event to mainにいくかwaitにいくかの判定部分で、キャンセル猶予時間を経過したかどうかと、accept flagのブール値のANDをとっています。

ここで紹介したのはキャンセル動作をどう受け付けられるようにするかで組み方が若干変わる、その一例になります。

実際は、ユーザーにどういった処理をさせたいか(例えば一度キャンセルを押すと全てのイベントをキャンセルさせる、一番最後に発生させた新しいイベントをキャンセルさせる、等)については作りたいプログラム毎に変わると思うので、アレンジしてみてください。

本記事では、イベントをキャンセルする方法を紹介しました。

元々フィルターイベントである程度キャンセル操作を行えるとはいえ、どのようなイベントに対しても柔軟にキャンセル操作ができるのが便利な場面もあると思うので、参考になるとうれしいです。

ここまで読んでいただきありがとうございました。

コメント

タイトルとURLをコピーしました