Hatena::Grouprubyist

Going My Ruby Way このページをアンテナに追加 RSSフィード

Ruby ロゴ (C) Ruby Association LLC

2011年09月23日(金)

Ruby/Tk

| 10:21 | Ruby/Tk - Going My Ruby Way を含むブックマーク はてなブックマーク - Ruby/Tk - Going My Ruby Way Ruby/Tk - Going My Ruby Way のブックマークコメント

標準ライブラリ Ruby/Tk のメモです。

Tcl/Tk

Ruby/Tk は Tcl/TkTk の移植(?)です。

Ruby/Tk を利用するには環境に Tcl/Tk がインストールされている必要があります。

Tcl はスクリプト言語です。Tk はその GUI ツールキットです。

Tcl/Tk がインストールされた環境では、それぞれのインタプリタとして tclsh、wish がインストールされていると思われます。

wish (tk) スクリプトは以下のような感じになります。(hello.wish)

#!/bin/bash
# \
exec wish "$0" "$*"

#「hello」とラベルされたボタン1つから成る GUI です
# ボタンを押下するとプログラムを終了させます
button .b -text "hello" -command {exit}
pack .b

これは、実際には bash スクリプトです。

2行目のコメント行の末尾が「\」で終端しています。

bash (sh) ではコメント行は継続しませんが、wish では継続します(3行目もコメントになる)

3 行目で wish に exec しています。以降の行は bash としては実行されません。

exec した wish では、「button .b 〜」の行の前までコメントになります。

それ以降が wish スクリプトとして実行されます。

これは、以前から(少くとも15年以上前から)使われる wish の常套テクです。

スクリプトは以下のようにして実行できます。

$ bash hello.wish
または
$ sh hello.wish
または
$ wish hello.wish
または
$ ./hello.wish           # 実行権が付与(chmod +x 〜)されている必要がある

以下は Ubuntu Linux で実行したところです。「hello」ボタンを押すと終了します。

f:id:lnznt:20110923104123p:image

Tcl/Tk のバージョンは wish を起動して以下のように確認できます。

$ wish
% puts $tcl_version
8.4
% puts $tk_version
8.4
% exit    # 終了は exit または CTRL-D

Ruby/Tk

同じ GUIRuby/Tk で書いてみます。(hello.rb)

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'tk'

TkButton.new { text "hello"; command {exit} }.pack

Tk.mainloop

# vim:set ts=2 sw=2 et ai fenc=utf-8:

実行すると wish で作成したのと同じ GUI が表示されます。

$ ruby hello.rb

f:id:lnznt:20110923104123p:image

スクリプトでは、

  1. TkButton.new でボタンウィジェット作成
  2. TkButton#pack でレイアウト
  3. Tk.mainloop の呼び出しによる明示的なメインループ突入

しています。

エンコード(utf-8など)の指定は (多分、Ruby1.9以降では) 日本語のメッセージを扱うときに(多分)重要になります。

pack

Tk ではジオメトリマネージャ*1が以下の 3 種類あります。

  • Packer : ウィジェットを順に詰める。よく使われる
  • Placer : ウィジェットを指定位置に配置する
  • Grid : ウィジェットを格子状に配置する

pack メソッドは Packer によるレイアウトを指定します。

オプションの指定

ウィジェットのオプション指定は上記のように new にブロックを与える方法の他に以下のようにもできます。

# インスタンスメソッドで設定する
button = TkButton.new
button.text "hello"                # TkButton#text による設定 
button.command {exit}              # TkButton#command による設定
button.pack
または
button = TkButton.new
button.text = "hello"              # TkButton#text= による設定
button.command = proc {exit}       # TkButton#command= による設定
button.pack
または
button = TkButton.new
button.text "hello"
button.command = "exit"            # proc の代わりに文字列を与える
button.pack
# new の引数に Hash として渡す
TkButton.new(:text => "hello", :command => proc {exit}).pack
または
TkButton.new('text' => "hello", 'command' => proc {exit}).pack
または
TkButton.new(:text => "hello", :command => "exit").pack
# メソッドチェーン (引数ありのオプション設定メソッドは self を返す)
TkButton.new.text("hello").command{exit}.pack
または
TkButton.new.text("hello").command("exit").pack
  • text で取れる方法は他の値を与えるオプションでも使えます(多分)。
  • command で取れる方法は他の proc を与えるオプション*2でも使えます(多分)。
  • これらは、ボタン(TkButton)以外のウィジェットでも同様です(多分)。

new に与えたブロックでは self が TkButton インスタンスになります。若干注意が必要です。

以下の場合、ボタン押下時に表示される self が異なります。

TkButton.new.text("button1").command{ puts self }.pack  # => self は実行環境(このスクリプトの場合 main)
TkButton.new{text("button2").command{ puts self }}.pack # => self は TkButton インスタンス

以下の1行目の場合、ボタンに文字列は設定されません。

TkButton.new{ text = "hello" } # この場合 text はダイナミックローカル変数なので TkButton#text= を呼び出せていない
TkButton.new{ text "hello" } # この場合 text は TkButton#text なので意図した通りになる

引数なしでオプション設定メソッドを呼び出すとその値を返します。

(引数ありの場合(設定する場合)は self を返します)

button = TkButton.new
button.text "hello"
p button.text      # => "hello"  

プログラムランチャー

だいぶ適当ですが、簡単なプログラムランチャーです。(launcher.rb)

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'tk'

TkButton.new { text "firefox" ; command {`firefox`}       }.pack
TkButton.new { text "eclipse" ; command {`eclipse`}       }.pack
TkButton.new { text "squeak"  ; command {`squeak`}        }.pack
TkButton.new { text "terminal"; command {`gnome-terminal`}}.pack
TkButton.new { text "exit"    ; command {exit} }.pack

Tk.mainloop

# vim:set ts=2 sw=2 et ai fenc=utf-8:

実行。

$ ruby launcher.rb

f:id:lnznt:20110923120131p:image

pack は :side オプションで詰め方を指定できます。

以下のようにするとウィジェットを左から詰めていきます。(デフォルトは :side=> :top)

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'tk'

TkButton.new { text "firefox" ; command {`firefox`}       }.pack(:side=>:left)
TkButton.new { text "eclipse" ; command {`eclipse`}       }.pack(:side=>:left)
TkButton.new { text "squeak"  ; command {`squeak`}        }.pack(:side=>:left)
TkButton.new { text "terminal"; command {`gnome-terminal`}}.pack(:side=>:left)
TkButton.new { text "exit"    ; command {exit} }.pack(:side=>:left)

Tk.mainloop

# vim:set ts=2 sw=2 et ai fenc=utf-8:

f:id:lnznt:20110923171229p:image


ルートウィジェットとウィジェット階層

全てのウィジェットの親となるルートウィジェットを明示的に作ることもできます。

require 'tk'

root = TkRoot.new {      # X クライアントとしての設定ができる
  geometry '320x320+0+0' # ジオメトリの指定 (X11 書式)
  title "hello,world"    # タイトルの指定
}

# 第一引数に明示的に親ウィジェットを指定できる
TkButton.new(root, :text => "exit", :command => proc {exit}).pack

Tk.mainloop
  • ルートウィジェットは 1つしか作成されない*3 *4
  • ルートウィジェットは明示的に作成しなければ暗黙のうちに作成される
  • ウィジェットの new の時に明示的に指定しなければ*5親はルートウィジェットになる
  • mainloop を抜ける(けどプログラム自体は終了しない)場合は、ルートウィジェットの destroy メソッドを呼びだす

ルートウィジェット以外を親ウィジェットに指定されるものとしては、

  • TkToplevel
  • TkFrame
  • TkCanvas

などがあります。

ちなみに、Tcl/Tk ではルートウィジェットは「.」と表現されます。

「.b」はルートウィジェットを親とする b ウィジェットです。

「.a.b.c」はルートウィジェットの子の a ウィジェットの、子の b ウィジェットの、子の c ウィジェット、という意味になります。(ファイルシステムのパスに似ています)

Ruby/Tk のウィジェットのパスは path メソッドで確認できます。

p root.path   # => "."
p button.path # => ".w0000"  (パス名は実装依存)

イベントのハンドリング

イベントのハンドリングには bind メソッドを使います。

以下、例です。(bind-test.rb)

#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'tk'

TkLabel.new{
  text    "Click me!"
  width   20 # in characters
  height  10 # in rows

  bind("1", proc {|x,y| puts "Button<1> [#{x},#{y}]" }, "%x %y")
  bind("2", proc {|x,y| puts "Button<2> [#{x},#{y}]" }, "%x %y")
  bind("3", proc {|x,y| puts "Button<3> [#{x},#{y}]" }, "%x %y")

  bind("Control-Button-1") { exit }
}.pack

Tk.mainloop

# vim:set ts=2 sw=2 et ai fenc=utf-8:

bind 引数は以下のように扱われます。

  • 第一引数 : イベント名。"1" はマウス第 1 ボタン押下イベント
  • 第二引数 : コールバックする proc
  • 第三引数 : イベントで取得するパラメタの書式。パラメタはコールバック proc に引数として渡される。

取得するイベントパラメタがなければ、第二引数以降省略できます(コールバック proc は ブロックとして渡す)。

実行してみます。

$ ruby bind-test.rb 

f:id:lnznt:20110923161624p:image

これをマウスでクリックすると以下のように出力されます。

Control キーを押しながら マウス第1ボタンでクリックすると、このプログラムは終了します。

  :
Button<1> [78,54]
Button<1> [78,54]
Button<1> [78,54]
Button<1> [78,54]
Button<3> [103,123]
Button<2> [96,108]
  :

----

これも bind の例です。

動的にウィジェットの属性を変更します。

マウス第1〜第5 ボタンの「クリック」で背景色を変更します。

Ubuntu Linux の X 環境では以下のように割り当てられています。

  • マウスの第4ボタンの「クリック」= マウスホイールの回転(上方向)
  • マウスの第5ボタンの「クリック」= マウスホイールの回転(下方向)
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-

require 'tk'

label = TkLabel.new(:text   => "Click me!",
                    :width  => 20,      # in characters
                    :height => 10,      # in rows
                    :bg     => :White). # background color
                    pack

# 色の指定は「'#ff0000'」のようにRGB値でも指定できます。
label.bind("1") { label.bg :Red     }  # label.bg '#ff0000' でも可
label.bind("2") { label.bg :Blue    }
label.bind("3") { label.bg :Yellow  }
label.bind("4") { label.bg :Magenta }
label.bind("5") { label.bg :Green   }
label.bind("Control-Button-1") { exit }

Tk.mainloop

# vim:set ts=2 sw=2 et ai fenc=utf-8:

f:id:lnznt:20110923165913j:image f:id:lnznt:20110923165915j:image f:id:lnznt:20110923165917j:image f:id:lnznt:20110923165916j:image f:id:lnznt:20110923165914j:image

----

参考URL

*1Java で言うレイアウトマネージャ

*2:command しか無いかも...?

*3:sigleton パターンのように、new は同じインスタンスを返す

*4:トップレベルウィジェットが複数必要な場合は TkToplevel ウィジェットを利用する

*5:新しいバージョンの Ruby/Tk では parent メソッドでも親ウィジェットの指定が可能らしい