Ruby/Tk入門

Author: RoNor
Mail:

Ruby/Tkを先日触り始めたのですが、ウェブ上にドキュメントが散在していてなかなか情報を得にくかったのと、更に日本語のドキュメントを探そうとするとなかなか見つからない状態だったので、調べつつまとめのようなものを作ろうと思いこのページを作成しました。

私はRuby真面目に触ったことない&「Ruby/Tk? GUIツールキットならならGTK+でしょ……なんか名前変だし、古そうだし」とか一週間くらい前まで思ってた人間なので、絶対間違いが含まれてると思います(えっへん)。

でもまぁ、お勉強ついでにまとめサイト書けば、理解も深まるし、自分がわからないところがどこなのかもわかるかなーと思ったので書いてます。あなたのハッキングライフ?の手助けになれば幸いですが、あなたに間違った情報を与えて足を引っ張る可能性も多々あることをご了承ください。ちなみに、Ruby/Tkにハマった理由というか、単純に食わず嫌いでした。うん、良く見ればTk(ToolKit)という名前に潔さを感じる今日この頃。良いですね、シンプルな名前。

とりあえず私が書いたものを読む前に、ウェブ上には有益な情報が多々あると思いますので、このページの最初の章にRuby/Tkに関連するサイトへのリンクを記しています。

もし、文中の間違いや、リンク切れ等を発見された場合は、お手数ですがまでご連絡頂けると嬉しいです。お前のサイトしょぼいんじゃーとかいうメールは、心が砕け散りそうな予感がするので貴方の胸にそっとしまっておくか、庭に穴を掘ってその中に叫んでみてください。

更新履歴

公開: 2009-12-19

目次

Ruby/Tkに関連するサイトへのリンク

はじめに

Ruby/Tkは、Tcl/TkのTk(ツールキット)をRubyから利用できるようにしたライブラリです。スクリプト言語TclのGUIツールキットTk=Tcl/Tkなので、スクリプト言語RubyのGUIツールキットTk=Ruby/Tkという名前になってます(たぶん)。スラッシュで区切ったライブラリ名はたくさんあって、SDLをRubyから利用できるようにしたものはRuby/SDLとなっていますし、GTK+ライブラリをRubyから利用できるようにしたものは、Ruby/GTKと呼ばれています。なんでスラッシュで区切るようになったのでしょう? ファイル名にスラッシュ付けられないよ……。

Wikipediaの記事を参照すると、Tcl/Tkは1988年に開発され、2000年にオープンソース化したと書いてあります。Tclというスクリプト言語自体はそこまで流行しなかったものの、TkというGUIツールキットはPerl、Python、Rubyと言った様々なスクリプト言語の標準的なGUIツールキットになっているようです。

Tcl/Tkについてもっと詳しく知りたい場合には、http://www.tcl.tk/や、http://tcltk.web.fc2.com/intro.htmlを参照してください。また、Windows環境向けにActiveStateのサイトからActiveTclというソフトウェアをダウンロードできます。これは、Windows環境で動作するTcl/Tkみたいです。

tcltklibを導入する

通常、Rubyに標準で入っているため改めてtcltklibを導入する必要はありません。Tcl/TkをRubyから利用するには、tcltklibというライブラリを用います。Ruby公式からダウンロードしたRubyのソースコードを見ると、/ext/tk以下にtcltklibのソースコードを確認できます。

Xubuntu9.10の環境では、libtcltk-ruby1.9.1というパッケージ名で存在するので、synapticパッケージマネージャ、もしくはapt-getでそれをインストールします。Rubyのバージョンごとにパッケージが存在するので、自分が使用しているRubyのバージョンに合うものを選択するようにします

libtcltkを使用する際に依存関係にあるパッケージは、私の環境だと以下のものが確認できます。

パッケージマネージャは依存関係を自動で解決してくれますが、万が一動かない場合にはTcl/Tk、tcltklib、そしてRubyがきちんとインストールされているかどうかを確認します。

tcltklibは、通常/usr/lib/ruby/x.y.z/以下にインストールされ、ドキュメントは/usr/share/doc/libtcltk-rubyx.y.z/以下にインストールされます。また、ドキュメントディレクトリの/example以下にはRuby/Tkのサンプルがあります。

使用しているtcltklibに合ったバージョンのTcl/Tkをインストールしていないと正しく動作しません。正常にプログラムが動作しない場合には、まず、Tcl/Tkが正しくインストールされているかどうかをwishコマンドで確認します。

ターミナルから、
$ wish
と入力しエンターを押すと wish プログラムが起動します。wishはRubyのirbのように、対話形式でTkに対して命令を与えることのできるコマンドプログラムです。

例えば、
$ wish
% button .btn -text "hello" -command "exit"
% pack .btn

と入力すると、ウインドウにhelloと書かれたボタンが表示されます。これがきちんと実行できれば、tcl/tkは無事インストールされているはずです。wishコマンドを終了させるには、exitと入力するか表示されたウインドウを閉じるか、もしくはhelloボタンをクリックしてください。

上記コマンドを正常に実行できているのにRuby/Tkで書いたプログラムが実行できない場合は、バージョンの問題が考えられます。2009年12月時点でのXubuntuパッケージで提供されるRuby1.9.1向けlibtcltkでは、tcl8.4、tk8.4を要求するため、バージョン8.4.x以外のものを使用できません。バージョンの確認方法は、
$ wish
% puts $tcl_version
% puts $tk_version

とすることで確認できます。もし、パッチレベルの番号まで知りたい場合には、$tcl_versionを$tcl_patchLevelに、$tk_versionを$tk_patchLevelに置き換えて実行してください。

最期に、Rubyが正しくインストールされているかどうかを確認するには、ターミナルから
$ ruby -v (もしくは ruby --version)
と入力し実行します。Rubyのバージョンが表示されればOKです。実行できないときにはインストールされていないと思うので、パッケージマネージャからインストールするか、ソースコードをコンパイルするか、WindowsであればOne Click Installerなるものがあったと思うのでそれを使用します。あと、PATHが正しく通っているかも確認してください。特に断りがない限り、本ページのすべてのプログラムは、Xubuntu9.10、Ruby1.9.1p243、Tcl/Tk8.4で動作確認を行っています。

Ruby/Tkの概要

Ruby/Tkの概要は、「るびま」のhttp://jp.rubyist.net/magazine/?0003-RubyTkMovementを見るとわかりやすいです。Ruby/Tkのメンテナである永井氏が記事を書かれています。

GUIライブラリには他にも数多くあり、Ruby/GTK、QTRuby、wxRuby、VisualuRuby、RubyCocoaなどが有名どころ?でしょうか。どれを選択するかは、使いやすさやターゲットにするOSなどによって悩むところかもしれません。Linux環境の多くで、Tcl/Tkは初めからインストールされており、MacOSXでもTcl/Tkは利用できます。またWindows環境でもActiveTclというTcl/Tkのソフトウェアを無料で利用できるため、RubyとTcl/Tkがインストールされている環境ならば同一のスクリプトを動作させるようにすることもできます。

マルチプラットフォームという点で言えば、Ruby/GTKも存在し、GTKのほうがウィジェットの豊富さやテーマを設定することもできるので高機能かもしれませんが、気楽さや記述量の少なさ、コードを見たときのわかりやすさは、Ruby/Tkのほうが優れているかなーと思ったりしています(このあたりは個人の趣味趣向?なので、実際に触り比べて判断したほうが良いと思います)。多分利用者が一番多そうなWindows環境向けについては、あんまりWindowsを使っていないので詳しいことはわかりません。

Ruby/TkでGUIプログラミング

Ruby/Tkでウィジェットを表示

ウィジェット(widget)とは、Window Gadgetを縮めた造語で、Ruby/TkではGUIの部品(ボタンやスクロールバー、チェックボックスなど)のことを指します。WindowsのsidebarやMacのDashboardや、iGoogleなどはここでは何も関係ありません。

他の多くのGUIツールキットにおける最初のプログラムはウインドウの作成から始まりますが、Ruby/Tkではウインドウの表示処理のための記述を省略できます。以下に、ボタンを表示し、そのボタンをクリックするとプログラムが終了する、どこにでもあるような退屈なサンプルを提示します。

1 require 'tk'
2 
3 button = TkButton.new
4 button.text = 'exit'
5 button.command = proc { exit }
6 button.pack
7 
8 Tk.mainloop

上記プログラムを実行すると、ウインドウとボタンが表示されます。TkButtonには、textやcommandオプションを設定したが、他にもwidthやheight等、幅や高さを設定するためのオプションを持っています。また、packはウィジェットを格納させるメソッドで、これを実行しないとウィジェットは表示されません。

commandについて

ボタンを押した際に、どのような処理を実行させるかは、commandに渡す手続きで設定します。button.rbのプログラムでは、
button.command = proc { exit }
と書いていますが、これを例えば文字列をコンソールに表示したいとするならば、
button.command = proc { puts 'clicked' }
という形に書き換えます。procについてはRubyの範囲になりますが、ブロックをオブジェクト化して、他のメソッドや、今回のようにオプションに引き渡すことができるようにするものです。

Procについての詳しい説明は、Rubyのドキュメントに記載されています。(参考URL http://doc.okkez.net/static/191/class/Proc.html)
button_proc = Proc.new { 処理 }
として処理をオブジェクトにしておけば、commandにオブジェクトを渡すだけで済みます。複数のボタンに対して同じ処理を実行させたいときなどには、Procオブジェクトを作成しておいたほうが楽かもしれません。

Proc.newを使ってブロックをオブジェクト化しておく例

1 require 'tk'
2 
3 button_click_proc = Proc.new { puts 'hello' }
4 
5 button = TkButton.new
6 button.text = 'exit'
7 button.command = button_click_proc
8 button.pack
9 
10 Tk.mainloop

button.hogehoge=~と言った形でボタンウィジェットのスタイルや表示内容、処理を記述するのは視覚的にもわかりやすく、またJavaのSwing等を触ったことがある人にとっては、この記法はすんなり受け入れやすいものかもしれませんが、ブロックを用いることで、記述量を減らし、更に視覚的にすっきりしたものにすることができます。

例えば、先ほどのbutton.rbというプログラム上で表示したボタンウィジェットは、ブロックを用いると以下のように記述することができます。

1 require 'tk'
2 
3 button = TkButton.new {
4   text 'exit'
5   command proc { exit }
6   pack
7 }
8 
9 Tk.mainloop

更にもうひとつの方法として、以下のようにインスタンス化する際に設定値を渡すことができます。渡す値は必ずハッシュにしてください。また、インスタンス生成時の一番目の引数には、生成しようとしているウィジェットの親を登録できます。この詳細については後述しますが、nilを指定している場合、もしくは何も指定してない場合は自動的にルートウィンドウが親となります

インスタンス生成時にオプションにパラメータをセット

1 require 'tk'
2 
3 button = TkButton.new(nil, 'text'=>'exit','command'=>proc { exit }).pack
4 
5 Tk.mainloop

このように、同じウィジェットを表示させるだけでもいくつかの方法があるため、他の人が書いたRuby/Tkプログラムを読むためには、生成の方法を覚えておく必要があります。と言っても、おそらく?一番見かけるのはブロックでオプションをセットする方法かなーと思ったりもするので、以降、特に理由がない限りブロックを使ってオプションパラメータをセットするようにします。

Hello,world!

おそらく少しでもプログラミングの経験があるならば一度は書いたことのある(というか一番最初に書くことになる)プログラム、Hello,world!をすっかり忘れていました。ので、もしあなたがHello,world!を出力しなければとてもじゃないが今夜は眠れそうにないという気分なら、それを書いてみましょう。

前節のプログラムでは、ボタンウィジェットを表示させ、それをクリックするとプログラムが終了するようにしていました。そしてコードを見ると、どこにもウインドウを表示させる処理がないことがわかります。ウインドウというものも、GUIプログラミングではひとつの立派なウィジェットなので、ボタンウィジェットを表示させる処理を記述しただけでは表示されないはずなのですが、Ruby/Tkでは明示的にウインドウを表示するという処理を書かなくてもウインドウが表示されるようになっています。

でも、書いてないものが表示されるってちょっと気持ち悪くない?っていうのは置いておいて、例えばウインドウのタイトルバーに表示される文字列を指定したいとき、ウインドウのリサイズを不可にしたいときなどに、このままだと勝手に作られるウインドウウィジェットに対して値を設定することができません。

そこで、本節のHello,world!プログラムでは、明示的にウインドウを作成するプログラムをまず作成します。

1 require 'tk'
2 
3 window = TkRoot.new {
4   title 'window'
5   resizable [0,0]
6 }
7 
8 button = TkButton.new {
9   text 'exit'
10   command proc { exit }
11   pack
12 }
13 
14 Tk.mainloop

上記のプログラムを実行すると、前節のプログラムの実行結果とまったく同様の外観、部品をもつウインドウが表示されたと思います。ひとつだけ違うのは、resizableパラメータを設定することで、ウインドウのサイズを固定していることです。

TkRootというものが、ウインドウウィジェットを表しています。すべてのウィジェットは、標準でTkRootを親にするため、ボタンウィジェットは自動的にTkRootの上にpackされる形になります。

次に、Hello,world!という文字列を表示させるために、TkLabelというウィジェットを使用することにします。TkLabelは、その名の通りラベルの役割をもつ部品で、指定した文字を表示させておくことができます。

1 require 'tk'
2 
3 window = TkRoot.new {
4   title 'window'
5   resizable [0,0]
6 }
7 
8 helloworld = TkLabel.new {
9   text 'Hello,world!'
10   pack
11 }
12 
13 button = TkButton.new {
14   text 'exit'
15   command proc { exit }
16   pack
17 }
18 
19 Tk.mainloop

実行結果は以下のようになると思います。

詳しく説明しなくてもなんとなくもう感じがわかるかとは思いますが、TkLabel.newでラベルのインスタンスを生成し、ブロックでtextオプションに'Hello,world!'というパラメータを渡して、ウインドウに載せるためにpackしています。

複数のウインドウを表示

前節のTkRootというウインドウウィジェットは、1つしか作ることができません。TkRootウィジェットを二つ以上、インスタンス生成した場合には、一番最後に生成したTkRootウィジェットを利用するようになっており、それ以外のTkRootウィジェットは表示されません。これは、TkRootがすべてのウィジェットの標準の親であるため、1つのプログラム中に1つしか存在できないようになっているためです。

もし、複数のウインドウを表示させたい場合には、TkToplevelというウィジェットを利用することになります。これは、ほぼTkRootと同様の役割をもつウインドウウィジェットですが、他のすべてのウィジェットと同様に、TkRootに支えられています。

1 require 'tk'
2 
3 
4 window = TkRoot.new {
5   title 'TkRoot'
6   resizable [0,0]
7 }
8 
9 window2 = TkToplevel.new {
10   title 'TkToplevel1'
11 }
12 
13 window3 = TkToplevel.new {
14   title 'TkToplevel2'
15 }
16 
17 helloworld = TkLabel.new {
18   text 'Hello,world!'
19   pack
20 }
21 
22 button = TkButton.new {
23   text 'exit'
24   command proc { exit }
25   pack
26 }
27 
28 Tk.mainloop

上記プログラムは、TkToplevelを用いてウインドウを合計3つ作成し表示しています。ウインドウ位置を指定していないため、実行するとウインドウが三つとも重なって表示されると思いますので、マウスでウインドウを移動させて3つ表示されたかどうかを確認してください。ウインドウサイズ、ウインドウの位置の初期化方法は後述します。

新しく作ったTkToplevelには何も表示されていないので、TkLabelで何らかの文字列を表示させてみましょう。

1 require 'tk'
2 
3 
4 window = TkRoot.new {
5   title 'TkRoot'
6   resizable [0,0]
7 }
8 
9 window2 = TkToplevel.new {
10   title 'TkToplevel1'
11 }
12 
13 window3 = TkToplevel.new {
14   title 'TkToplevel2'
15 }
16 
17 helloworld = TkLabel.new {
18   text 'Hello,world!'
19   pack
20 }
21 
22 label1 = TkLabel.new(window2) {
23   text 'label1'
24   pack
25 }
26 label2 = TkLabel.new(window3) {
27   text 'label2'
28   pack
29 }
30 
31 button = TkButton.new {
32   text 'exit'
33   command proc { exit }
34   pack
35 }
36 
37 Tk.mainloop

新たにlabel1,label2というラベルのオブジェクトを生成しています。注意すべきなのは、すべてのウィジェットは標準でTkRootにpackされるため、TkToplevelにpackするためには、明示的に、親ウィジェットを指定してあげる必要がある部分です。

今まで、ウィジェットのインスタンス生成時に引数を渡したのは、オプションパラメータをインスタンス生成時に指定する部分くらいでしたが、そのとき第一引数にnilを指定していたのを覚えているでしょうか。第一引数に親となるウィジェットを指定することで、生成するウィジェットがどの親の上にpackされるかが決まります。

例えば、TkLabel.new(window2)は、TkLabelのインスタンスはwindow2というインスタンスを親に指定しています。この状態でpackすれば、TkLabelはwindow2の上に乗ります。TkButtonも同様に、TkButton.new(対象のTkToplevel)と指定すれば、指定したTkToplevelに載せることができるので、色々ウインドウを増やしたり、与える引数を変更したりしてどのように変化するかを試してみるとわかりやすいと思います。

ウインドウの位置とサイズ

TkRootもしくはTkToplevelでウインドウを作成する際、そのサイズと表示位置を指定するには、geometryオプションにパラメータを与えます。300x200で、位置がx=100,y=100の位置ならば、'300x200+100+100'という形で記述します。またもうひとつの方法として、width,heightオプションに対してパラメータを与えウインドウのサイズを指定する方法があります。width,heightと、geometryの両方を指定していた場合geometryの値が優先されます。

TkRoot.new {
  geometry '300x200+100+100'
}

プログラムの終了について

複数ウインドウを表示させるプログラムでは、TkRootウィジェットのウインドウを閉じない限りプログラムは終了しません。また、ボタンを表示させたサンプルでは、exit命令を用いることでプログラムを終了させていました。もし、プログラム中で、Tk.mainloopから抜け出したいが、プログラム自体は終了させたくないと言った場合、TkRoot.destoryメソッドを利用することでそれが可能になります。TkRootのdestroyメソッドは、クラスメソッド、インスタンスメソッド共に実装されているので、TkRootの構築を記述していない場合でも、クラスメソッドで実行することが可能です。

exitとTkRoot.destroyの明確な違いは、exitはプログラムの実行自体をそこで終了させるのに対し、TkRoot.destroyはTk.mainloopを終了させるだけで、プログラム自体は終了させない点です。Tk.mainloopは、文字通りのループ処理を行う部分で、作成したウィジェットに対する様々な操作をプログラムに従って受け取り、処理するようになっています。そのため、もしTk.mainloopの後に何らかのプログラムを書いたとしても、Tk.mainloopが終了しない限り(つまりTkRootのウインドウが閉じられるか、TkRoot.destroyが呼ばれるか)それが実行されることはありません。

1  require 'tk'
2
3  ~
4
5  Tk.mainloop
6
7  puts 'hoge' # このputsはTk.mainloopが終了した際に実行される

もし、TkRootが破棄されたときに、何らかの後処理をしたければ、TkRoot.destroyメソッドで破棄し、後処理を実行させることができますし、別にその時点ですぐにプログラムが終了して構わない場合には、exitでプログラムを終了させるのも有りです。

ちなみに、destroyインスタンスメソッドは、TkRoot以外にも備わっており、TkToplevelに対して実行した場合には、表示しているウインドウを破棄します。TkLabelに対して使用した場合、TkLabelを破棄します。TkRoot以外のウィジェットを破棄したとしてもTk.mainloopは終了しません。

ウィジェットのレイアウト(Packer)

ウインドウ内のウィジェットの配置には、packというメソッド(packerというジオメトリマネージャのコマンド。他に、gridder、placerがある。ウィジェットの配置は、ジオメトリマネージャが担っている)を使用していましたが、ただpackと記述しただけでは上から下に順に配置されていくため、複雑なレイアウトを持つプログラムを作成することができません。ここでは、packの指定方法による配置方法の指定について説明していきます。

ウィジェットの格納方法を定めるオプションにはside、fill、expandの三つの種類があります。

sideについて

sideは、top、left、bottom、rightの4つのパラメータを受けることができます。sideという名から連想できるように、ウィジェットを、指定した側から順に格納していくように配置されます。topを指定した場合には上から、leftを指定した場合には左から、bottomを指定した場合には下から、rightを指定した場合には右から格納されます。以下のコードは、4つのウインドウそれぞれに、top、left、bottom、rightの指定をしたボタンを5つずつpackしています。何も指定しない場合は、topを選択していることになります。

1  require 'tk'
2 
3  root = TkRoot.new {
4    title 'pack top'
5  }
6  window1 = TkToplevel.new {
7    title 'pack left'
8  }
9  window2 = TkToplevel.new {
10   title 'pack bottom'
11 }
12 window3 = TkToplevel.new {
13   title 'pack right'
14 }
15 
16 5.times do |i|
17   TkButton.new {
18     text "button#{i}"
19     pack 'side'=>'top'
20   }
21   TkButton.new(window1) {
22     text "button#{i}"
23     pack 'side'=>'left'
24   }
25   TkButton.new(window2) {
26     text "button#{i}"
27     pack 'side'=>'bottom'
28   }
29   TkButton.new(window3) {
30     text "button#{i}"
31     pack 'side'=>'right'
32   }
33 end
34 Tk.mainloop

実行結果は以下のようになります。

pack 'side'=>'top'

pack 'side'=>'left'

pack 'side'=>'bottom'

pack 'side'=>'right'

fillとexpandについて

fillとexpandは、その特性から利用時に二つが関係してくることも多いのでひとまとめに説明します。ウインドウ上に乗せられているボタン等のウィジェットサイズは、通常TkButtonのwidth,heightオプションにパラメータを与えることで設定できますが(ボタンのwidth、heightに与えるパラメーターの単位は、pxではなく文字単位であることに注意してください)、ウインドウをリサイズした際にそのサイズは通常固定されたままになります。メモ帳というアプリケーションを見たときに、ウインドウをリサイズすれば、メモ帳の入力エリアもウインドウに合わせてサイズが変わるのが普通です。ウインドウのリサイズに合わせて、中身のウィジェットのサイズもリサイズさせたいというときなどに、fill、expandによる指定を行います。

fillは、「ウィジェットの横方向、縦方向に利用可能な領域(余計な空白)がある場合に、そこをウィジェットで埋めるかどうか」を指定します。fillが取る値は、'x'(横方向)、'y'(縦方向)、'both'(どっちも)の三つになります(デフォルトにしたい場合には'none'を与えます)。ですが、これだけではウインドウのリサイズに合わせてウィジェットのリサイズを行うことができません。fillは利用可能な領域を埋めるように指定することはできますが、ウィジェットが利用可能な領域を作り出すように指定することができないからです。

ウィジェットが利用可能な領域と、sideによるpackの方向について、実際に適当なウィジェットをpackして動かしてみるのが一番ですが、ウインドウリサイズ時、'fill'=>'both'という指定をしており、expandがデフォルトの場合、sideのpackしている方向(横or縦)でウィジェットのリサイズの挙動が変化します。縦方向にpackしている場合、リサイズ時に横方向に追従しますが、縦方向には追従しません。また、横方向にpackしている場合、縦方向には追従しますが、横方向には追従しません。

pack 'side'=>'top', 'fill'=>'both', 'expand'=>'false'
縦方向(画像はtop-bottom方向にpack)の場合、ウインドウをリサイズすると縦方向には追従しない

pack 'side'=>'left', 'fill'=>'both', 'expand'=>'false'
横方向(画像はleft-right方向にpack)の場合、ウインドウをリサイズすると横方向には追従しない。

ウィジェットが、視覚的に確認できる余白をpack方向によらず上下左右を利用できるようにするには、expandオプションにパラメータを与えます。expandは'true'、'false'の二つの値を受けます。余白を利用できるようにするには'true'、利用できないようにするには'false'を与えてください。

pack 'side'=>'top', 'fill'=>'both', 'expand'=>'true'

pack 'side'=>'left', 'fill'=>'both', 'expand'=>'true'

ウィジェットのレイアウト(Gridder)

まだありません

ウィジェットのレイアウト(Placer)

まだありません

command

まだありません

イベント処理

まだありません

色等の見た目に関わるオプションについて

まだありません

Ruby/Tkで使用できる基本ウィジェットの説明

まだありません

カレンダー表示プログラム作成チュートリアル

まだありません

スティッキープログラム作成チュートリアル

まだありません

TkCanvasを用いたペイントプログラム作成チュートリアル

まだありません

inserted by FC2 system