eminus hatena lab

技術系のメモと解説

LabVIEWで電卓を作る(5): Event Structureでボタンが押された時に動かす

今日からは昨日紹介したプロトタイプの動作を解説しながら各部品の動作の説明をする。まずは昨日の電卓がどのようなブロックダイアグラムで動いていたかを下図に一部示す。(正直に言うとあの後少しデバッグをしてあの動画のときのとは変わっているが動作は同じ)*1

f:id:eminus:20190111201658p:plain

電卓のブロックダイアグラム

といってもこれをいきなり全部解説するのは大変なので部分毎に区切り、かつそれぞれの部品の動作が分かるような形で分割して説明する。今日はユーザーから入力を受け取るときに欠かせないEvent Structureに注目する

f:id:eminus:20190111202429p:plain

今回解説する範囲

フロントパネル

まず電卓と言えば0から9までの数値のキーがあるので、それに該当するボタンをフロントパネルに作ってしまう。形は何でも良いが、Mechanical ActionはSwitch Until Released(右上のやつ。電子工作で言うタクトスイッチ的な動作で、押している間だけONになり、離すとOFFになる)を選ぶ。おすすめはBooleanにあるOKボタンのラベルを書き換えることだが、ボタンのスタイル(デフォルトだと「Modern」)がWindows98かと思うような感じで古めかしいので好みに応じて「System」や「Silver」スタイルのボタンを使ってみても良いだろう。LabVIEW2018だとNXGスタイルというのが登場して筆者は研究室ではそれを好んで使っている。(家のPCは2017なので使えない...)

f:id:eminus:20190111203342p:plain

電卓のボタンを配置

ところで、このボタンには左上にラベルが一つとボタン本体にラベルが1つある。左上に付いているのがボタン本体の名前で、ボタン自体にのっかっている文字列はBoolean Textといって単純に人間が読むための文字列になっている。Visual Studioなどでプログラミングしたことがあれば、ボタンの名前とボタンのラベルは別々だったと思うが、そういうことである。

Event Structure

Event Structureではボタンが押されたかどうかを監視し続け、押されたタイミングでしかるべきプログラムが走るようにできる。

簡単な例を示そう

f:id:eminus:20190111204046p:plain

Event StructureでNumeric Controlの変化を読み取る

作る手順と解説は以下の通り(初心者向けを意識しているので人によってはくどい説明だと思うが、適宜読み飛ばして構わない)

  1. Numeric Controlを一つ作る
  2. Block diagramでEvent Structureを適当に作る
  3. Event Structureの枠を右クリックし、Add Event Caseを押す
  4. 出てきたウィンドウでどの部品がどうなったときを監視するかを選ぶ。今回は作ったNumeric ControlであるXのValue Changeという事象が起きた場合のプログラムを書いてみることにする。もちろん、マウスでクリックしたとき、とかキーボードのキーを離したときなどいろいろある。
  5. するとEvent Structureに新たなページができる。これはCase Structureと似たような感覚で、たくさんのブロックダイアグラムがまとまった状態になっており、検出されたイベントに応じて対応するプログラムが実行される。
  6. Value Changeのイベントの場合、便利なことに値が変わる前と変わった後の値が直接読み出すことができ、それはEvent Structure左側のOldValとNewValという端子からアクセスできる。
  7. 今回の例では引き算をしてdXというIndicatorに表示してみることにする。dXというIndicatorは引き算の部品の出力端子を右クリックし、Create Controlすれば簡単に作ることができる。今回の例ではEvent Structureの中でdXに表示しても一旦外に引き延ばしてからdXに表示しても(示した例は後者)どちらでも良い。

実行してみるとイベントの待機状態に入る。何もしなければいつまでも待ち続けるが、Xの値を変えた瞬間、dXが表示されプログラムが終了するだろう。何が起きているかと言えば、

  1. VIを実行するとEvent Structureブロックの実行に取りかかる
  2. デフォルトの状態では待機時間の制限なく(i.e. タイムアウトなしで)登録されているイベントが起きるまで寝ている
  3. Xが書き換わると起床して該当するイベントのページにあるプログラムが実行される
  4. これが終わるとEvent Structureの実行は終わるのでそのままVIも終了する

ここが筆者も初めてEvent Structureを見たときに誤解したポイントで、Event Structureは一度に一つのイベントしか処理できず、しかもその後2つめのイベントが来てもそれは処理しない。

ならどうするか

結論から言うとWhileループで囲んでしまえば良い。つまり、一度に処理できるのは一つのイベントだけだが、ループを回せば何度でもEvent Structureを走らせることができる。筆者はほとんどの場合でループで囲んで初めて御利益がある、と思っているのだが言い過ぎだろうか?

電卓の数字キーのイベント

話を電卓に戻すと、やりたいのは「ボタンが押された」というイベントを検出することと、「どの数字が押されたか」を知ることである。今まで述べた様に、ボタンが押されたことを検出するのはAdd Event CaseであるボタンのMouse Upのイベントを使えば良いのだが、どの数字を押されたかを上手くやる方法は少し悩んだ。

すぐに思いつく方法はイベントを10種類登録して、0のボタンの時は0の処理、1なら1、...等とやれば良いのだが、さすがに10個もあると冗長すぎて面倒くさがりの筆者は2個くらいコピーしたところで飽きてしまった

f:id:eminus:20190111213311p:plain

0-9と.のイベントを一括で処理する部分

別な方法としてはButton 0からButton 9までどれかのボタンが押されたら実行される共通のイベントケースを作っておき、押されたボタンのラベルの文字列から場合分けをするという方法がある。Event Structure中にある端子を眺めているとCtlRefというこれに便利そうな出力があるのに気づく*2。これはControlのありとあらゆるPropertyやMethodにアクセスするためのポインタのようなもので、イベントが起きると具体的にどのボタンが押されたかを教えてくれる。ただ、これはボタンのオブジェクトへの参照を返すので直接読んでもラベルは入手できない。(オブジェクト指向をあまり知らない人のために説明すると、CtlRefから返ってくるのはボタンのサイズ、場所、色などボタンの属性が全部固められた情報であって、今ほしいボタンのラベルはその一部なので、ワンクッション挟まないとラベルを入手できない。その方法を以下に書いている)

LabVIEWでは細かいプロパティなどは端子を右クリックした先のCreate->Property for ... Classの中にある。今回はその中のBoolean Text -> Textというのを選べば良い。選ぶと、いままで扱っていない長方形が出てくると思う。これをEvent Structureの中に配置してプログラムを書き始める。

できた長方形は2入力、3出力のブロックになっている。入力はボタンのオブジェクトへの参照とエラー入力(これは今のところ無視して良い)、出力は入力されたボタンへの参照と同じ元とエラー出力(これも無視して良い)、そしてほしかったラベルの文字列になっている。エラーの入出力とオブジェクトの参照の出力は今は使わなくて良いので無視すると、「オブジェクトへの参照を受け取ってラベルの文字列を返す」という長方形ブロックであることが理解できる。

文字を数字に変換

普通ならscanf的な関数で数字に変換するところだが(実際LabVIEWにもそのような部品(サブVI)が存在する)、ひねくれ者の筆者はこういうときはASCIIコードに変換して48を引くということをする(個人用以外でまねをしてはいけない)。これはちょっとしたメリットもあって、0-9だけでなく小数点「.」も条件分岐無しで同じイベントで処理できてしまう。

具体的にどういう構造になっているかというと

  1. 文字を受け取り文字列型から8bit符号無し整数の配列に変換する
  2. 8bit符号無し整数の配列から最初の要素を取り出す
  3. 型変換を行い、8bit符号有りの整数にする
  4. そこから定数48を引く。このマジックナンバー48は文字'0'のASCIIコードで、0から9はASCIIコード上で連続しているので48を引けば数字に変換できる。ちなみにドット'.'は0の2つ前なので、-2が出てきたときはドットが押されたと思えば良い

まとめ

  • Event StructureもCase Structure同様に複数のブロックダイアグラムを分けて実行でき、登録したイベントに応じて条件分岐される
  • Event Structureは一回の実行で一つのイベントしか処理しないため、よくWhile Loopと一緒に使う
  • Event Structureの左側に出てくる端子は使い勝手が良いものが多いので眺めていると幸せになれることがあある

たくさんボタンがある場合の場合分けの仕方については、いろいろやり方があると思うが、これより一般的だったり、エレガントな方法があればコメント欄でご教授ください

*1:User Eventが4つ並列なのは自分でもどうかなぁと思っていてTwitterデモ指摘を受けたのだが、実のところはUser Eventというのを使ってみたかったので無理矢理取り入れてみた、というのが正直な理由

*2:知っていたように書いているがいろいろ試行錯誤した。最初はボタンを全部クラスタに突っ込んでCluster to Arrayでbooleanの配列にしてごにょごにょ、等とやったが、なんかしっくりこないのでやめた