練習8-1から8-5まではステップ・バイ・ステップでプログラムを 完成させるものだったので、プログラム例としては完成したものの 一例をex8-1.rbとex8-2.rbに示す。 ex8-1.rbが計算機能を実現しているクラスCalcを 定義しており、スタックが空の時などに例外を発生するようになっている。 ex8-2.rbはキーボードから入力と画面への表示をするための ユーザインタフェースのプログラムとなる。ここでは、クラス CUICalc を定義しているが、今回の練習程度であればクラスとして定義しなくても 構わない。なお、ループ全体が長く見えるのを避けるため、入力に応じた 処理をするための条件分岐部分を別メソッドとして定義している。
練習8-6については、 ex8-3.rbがテーブル駆動型プログラムの手法で同様に クラスCUICalcを定義したものである。初期化のメソッドで Calcのオブジェクトを生成するだけでなく、Hashを 生成し、@cmdtblをいうインスタンス変数に入れておく。このハッシュの キー(key)は入力される文字列に対応し、値(value)にはそれに対応した処理を 呼び出すための手続きオブジェクトを入れている。 mainloopではそのハッシュを入力文字列で探索し、値(手続きオブジェクト) が得られれば、それを実行(call)する。見つからなければ、数値入力 と見なして、整数化してプッシュする。
前回の練習8-7にも示したようにRubyでGUIを備えたプログラムの開発が 可能である。Rubyで利用可能なGUIプログラムライブラリには様々なものが あるが、比較的使いやすく、様々なOS上で利用可能な Ruby/Tk をここでは 用いる。
GUIを備えたプログラムでは利用者がまず何を使ってどのように操作するかが 予想できない。例えば、マウスを使ってどのボタンをクリックするのか、 あるいはキーボードから文字列を入力するのかを事前に知ることはできない。
そのため、旧来からのメインプログラムによって処理を順に進めていき、 必要になった時点で利用者に入力を求めるというプログラムの作り方をすることが できなくなる。そこでイベント駆動型プログラムという手法を用いる。
ここで言う「イベント」とは、プログラムの実行状況に関わらず、 プログラムに対して外部で起こされる事象のことで、GUIについては、 マウスの移動やクリック、キーボード入力が該当する。
イベント駆動型プログラムとは、あらかじめどのイベントが発生したら、 どういう処理を行うかを定義しておき、イベントが生じる毎にそれに対応する 処理を呼び出し、処理が終ればまた次のイベントを待つと言うものである (先のイベントの処理を待たずに次のイベントの処理を同時並行的に 行う場合もある)。
先週の練習8-7のプログラム例ではTkButtonのインスタンス 生成時に引数で 「"command"=>lambda{ puts("さようなら"); exit(0) }」 と指定しているものが「そのボタンをクリックされた」というイベントが 発生した時に実行すべき処理を指定していることになる。 こういうイベントに応じた処理を記述したもの(上の場合は手続きオブジェクト となっている)を一般にイベントハンドラと呼ぶ。
Ruby/Tkでは一般に画面(ウィンドウ)上に配置されるボタンなどの 部品毎にどのような操作が可能かが決まっているので、その操作毎に 対応するイベントハンドラをインスタンス生成時などに登録することが できる。
GUIではウィンドウ上にボタンやテキスト入力フィールドなど様々な 部品を、利用者にわかりやすく、また操作しやすいように配置して 画面を構成する。そのため、複数の部品を並べておく場所のような、 直接利用者の操作を受け付けるのではなく、画面のレイアウトのための 部品というものも存在する。ただし、Ruby/Tkではその部品をあまり 意識せずにレイアウトの指定ができるようになっているので、ここでは それを用いる。そのような配置を司るものをRuby/Tkでは 「ジオメトリマネージャ」と呼ぶ。
今回は以下の2種類のジオメトリマネージャを紹介する。 なお、ここでの使い方はごく一例である。
次のような簡単なプログラムで各ジオメトリマネージャによる配置を 試してみよ。
require("tk") for x in 1..5 for y in 1..5 TkButton.new(nil, "text"=>(x.to_s + y.to_s)).ジオメトリ指定 end end Tk.mainloop
で、「ジオメトリ指定」には
Ruby/Tkで使用可能な部品のクラスには様々なものがあるが、ここでは 「電卓」を構築するのに必要になりそうなもののみを紹介する。この他に ついてはマニュアルや書籍などを参考にすること。
TkEntryを使用して、プログラムにデータを入力したり表示したり するプログラム例は次のようになる。
require("tk") $KCODE = "s" x = TkVariable.new("test") TkEntry.new(nil, "textvariable"=>x).pack TkButton.new(nil, "text"=>"Enter", "command"=>lambda{p(x.value); x.value = ""}).pack Tk.mainloop
ここで、「$KCODE = "s"」というのはこのプログラムで扱う文字列の 文字コードがWindowsで標準のShift-JISであることを指定している。これがないと 日本語文字列が文字化けするおそれがある。
最初にTkVariableのインスタンスを生成し、変数 xに 入れている。このインスタンスには初期値として文字列"test"を 入れておく。
次に文字列の入力フィールドとしてTkEntryのインスタンスを生成するが その際、値(文字列)を入れておくものとしてxに入っている TkVariableのインスタンスを指定している。
TkButtonは文字列を入力した後クリックするために用いる。 イベントハンドラとして、x、すなわちTkVariableのインスタンス から取り出した値を表示し、そして、その値を空文字列にすると言う処理を 行う。
上のTkEntryを用いるプログラム例を実行してみよ。 実行すると、画面に入力フィールドとボタンを備えたウィンドウが現れるので、 入力フィールドに適当に文字列を入力してから、ボタンをクリックすると、 実行しているコマンドプロンプトにその文字列が表示される。
これまでのRubyのプログラムで実行時に例外が発生すると rescue の記述がない限り、エラーメッセージを表示して、実行を終了していた。
それに対し、Ruby/Tkのプログラムではイベントハンドラの処理を行っている 最中に例外が発生すると、そのメッセージを表示するウィンドウが 表示されるのみで、プログラムの実行そのものは止まらない。そのメッセージ のウィンドウの「OK」ボタンをクリックすると、次のイベントの受け付け処理に 戻る。
本来であれば、利用者向けにより親切なダイアログウィンドウなどを 表示すべきであるが、今回はその辺の機構については略すことにする。
TkButtonやTkEntryではインスタンスの生成時の 第1引数に「nil」を指定してきた。これは、その部品を配置する 台になるオブジェクトを指定するための引数である。 ここにnilを指定した場合は、このプログラムで 表示するウィンドウそのものを指定したのと同じ解釈がされる。
単純なレイアウトや機能の場合はウィンドウ全体に対して配置すれば 良いが、ウィンドウの構成が複雑になる場合には、適宜サブ・ウィンドウと なる部品を準備して、それを指定することができる。
最後に第7回目から作ってきた電卓プログラムを GUIを備えたものとして完成させることを最後の課題として、 この勉強会シリーズの仕上げとしたい。
完成させる電卓としては次のようなものを目指すこと
詳細は随時相談してもらえば良い。期限は厳密には設けないが、 今年度中に仕上げるようにすること。
細かな演算機能は別として、教育用計算機システムのLinux 環境で、 「xcalc -rpn」 を実行すると、HPの逆ポーランド方式の電卓風の計算機が表示されるはず なので、ある程度参考にすると良い。
by Tetsuo Sakaguchi
2008年 2月15日 金曜日 16時14分05秒 JST