Ruby/tk

Ruby/tkによるGUIプログラミング

これまで作成してきた対話的プログラムは,入力と出力がそれぞれ 1つの流れでできていた。 GUI(Graphical User Interface)プログラミングでは, 利用者が働きかけを行なう対象が,ボタン,入力窓,メニューなど複数あり, どんな順番で働きかけが来ても対応できる形となっていなければならない。 ユーザからのキーボードやポインティングデバイス(マウスなど)を用いた 働きかけのことをイベントといい,なんらかのイベントが発生したら それに対応してあらかじめ登録しておいたプログラム部分が動くような 構成となっている。このような動きを取るプログラムを イベント駆動型プログラムといい,GUIプログラムの 典型的な形式である。

GUIプログラムでは,ウィンドウ部品を作ったり,イベント処理を 行なうライブラリを用いて行なう。GUI用の部品が一式揃ったライブラリの ことをツールキットといい,言語や用途に応じて様々な ツールキットが存在するが,その利用の基本的な考え方は 共通している。

Ruby/tk

GUI用のツールキットは多種多様である。 現在でも利用されているもののうち最も長い部類の歴史を持つツールキットが Tcl/Tk(http://www.tcl.tk) である。元々 Tcl/Tk はGUIを含むスクリプトを簡単に作成できることを目指して作られた スクリプト言語(Tcl)とGUI用ツールキット(Tk)の合わさったものであるが, そのツールキット部分を他の言語から使えるようにしたものが徐々に増えた。 Ruby/tk はそのRuby版であり,Rubyの文法でtkを制御できる。 Tk がシンプルで手軽なツールキットという地位を1990年代から 変わらずに保ち続けていることからも, Tk の習得が比較的容易で, なおかつ今後も長期に渡って通用することが予想できる。 また,基本的な概念は後発のツールキットにも共通するため, GUIプログラム入門用としても有用であると言える。

Ruby/tkの初歩

他のGUIツールキットを利用したプログラミングと同様, Ruby/tkでもイベント駆動型プログラムを作成していく。 そのおおざっぱな流れとしては,

  1. ウィンドウの部品(ウィジェット)を作成する。

  2. 作成した部品を土台部品に貼り付ける

  3. どのイベントにどのアクションを起こすかを登録する。

  4. メインループを呼ぶ

となる。イベントに対する反応をとくに決めないものなら3は不要である。 まずは,ウィジェットを出すだけの簡単なプログラムを示す。

tk-hello.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new("text" => "Hello, world!").pack   # 1.と2.に相当
Tk.mainloop                                   # 4.に相当

TkLabelは,ラベルとなるクラスで new によって,ひとつのラベルを生成する。ラベルは,文字や画像などを 表示するためのウィジェットである。引数にどのようなラベルを生成するかの 情報を持たせた属性値をハッシュ形式で与えると,それに応じた ラベルオブジェクトを生成する。実際には生成するだけでは表示されず, pack メソッドを用いて初めて表示される。 pack は,あるウィジェットをどのようにウィンドウ上に 配置するかを決めるメソッドである。このように配置を 司るものをジオメトリマネージャといい,GUI部品を 効果的に配置する重要な約割を担っている。他のツールキットにも同様のものがあり レイアウトマネージャなどと呼ぶこともある。

最初の例 tk-hello.rb に,イベントに対する反応を 登録する部分を追加してみる。書き方は様々だが,いくつかの実例を含んだ 以下のプログラムを示す。

tk-hello-ev.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new("text" => " Hello, world! ") {
  bind('1', proc {exit})
}.pack
Tk.mainloop

網掛け部分が追加した部分である。 追加部分は,TkLabel.newメソッドに与えたブロックで, このブロック内は TkLabel クラスに属すメソッド呼び出し が列挙できる。上記リストは以下のいずれの書き方でも同じ働きをする。

その1: ブロックへの仮引数はそのオブジェクト自身を指す値となる。

TkLabel.new("text" => " Hello, world! ") {|x|
  x.bind('1', proc {exit})
  x.pack
}
Tk.mainloop

その2: オブジェクトをローカル変数に格納してあとで使う場合。

lab = TkLabel.new("text" => " Hello, world! ")
lab.bind('1', proc {exit})
lab.pack
Tk.mainloop

その3: テキスト属性指定もメソッド呼び出しで。

TkLabel.new() {
  text(" Hello, world! ")
  bind('1', proc {exit})
  pack
}
Tk.mainloop

その4: packがオブジェクト自身を返すのでbindメソッドが呼べる。

TkLabel.new() {
  text(" Hello, world! ")
}.pack.bind('1', proc {exit})
Tk.mainloop

イベント処理

ウィンドウ上で発生する様々なイベントに対して, 処理を行なう部分をイベントハンドラという。この登録には 上述の例のとおり bind メソッドを使う。

bind メソッドは

bind(シーケンス, 処理)

の形式で指定する。シーケンスの指定方法を 説明する前に,いくつかの指定を含む例題プログラムを示す。

tk-ev.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

def erase(widget)
  widget.value = ""             # Entryの入力文字列を消す
end

TkLabel.new("text" => " Hello, world! ") {
  # ラベルでは,マウス第3ボタンが効き,キーは効かない
  bind('Button-3', proc {exit}) 	# 右ボタンがクリックされたら
  bind('Key-3', proc {exit})    	# キー3が押されたら(でも効かない)
}.pack
TkEntry.new {|tke|
  # 入力窓では,マウス第3ボタン,第2ボタン,キー'q','x'が効く
  bind('Key-3', proc {erase(tke)}) 	# キー3が押されたら(これは効く)
  bind('2', proc {erase(tke)})          # 1,2,3とだけ書くとマウスボタン
  bind('Key-q', proc {                  # Key-q でも q でもよい
         erase(tke)
         Tk.callback_break		# q そのものの入力を回避
       })
  bind('x', proc {erase(tke)})          # xならキー 'x'
}.pack
puts "ラベル上のボタン3で終了"
Tk.mainloop

イベントパターンとシーケンス

シーケンスの部分は発生するイベントにマッチするパターンを固有の記法で 表したものの並びを指定する。このパターンをイベントパターン といい,

modifier-modifier-type-detail

の形式,あるいはその省略できる部分を省いた形式で記述する。 modifierは修飾(モディファイア)を示すシンボルで, 指定できる代表的なものを示すと以下のようになる。

シンボル意味
AltAlt
ShiftShift
ControlControl
LockCapsLock
Meta
M
Metaキー
Mod1
M1
Mod1
Mod2
M2
Mod2
Mod3
M3
Mod3
Mod4
M4
Mod4
Mod5
M5
Mod5
Button1
B1
マウス第1ボタン
Button2
B2
マウス第2ボタン
Button3
B3
マウス第3ボタン
Button4
B4
マウス第4ボタン
Button5
B5
マウス第5ボタン
Double2連
Triple3連
Quadruple4連

Mod1からMod5 は,ウィンドウシステムの モディファイアキーで,X Window System では5種類のモディファイアキーが 利用できる。現在どのキーがモディファイアとして登録されているかは, コマンドラインで

xmodmap

と起動してみれば分かる。

キー入力で複数のキーを続けて押したものを表したいときなど, 複数のイベントの組み合わせをバインドすることもできる。 これにはイベントシーケンスを配列化したもので表現する。たとえば,

bind(['Control-x', 'Control-c'], proc{exit})

とすると,C-x に続けて C-c を押したときの処理を定義することになる。ただしこの場合 C-x のみを押したときの処理が別に定義されている場合は C-x が押された段階でそちらも呼ばれることに注意する。

type の部分はイベントタイプで発生したイベントの 種別を表すシンボルである。代表的なものを以下に示す。

シンボル意味
Button
ButtonPress
マウスボタンのクリック
ButtonRelease押されていたマウスボタンが離された
Key
KeyPress
キーが押された
KeyRelease押されているキーが離された
Destroyウィンドウが強制終了された
FucusInウィンドウがフォーカスされた
FucusOutウィンドウフォーカスが外れた
Enterウィンドウ内にポインタが入った
Leaveウィンドウからポインタが出た
Motionウィンドウ内でポインタが動いた

イベントシーケンス最後の部分,detail はイベント発生源の 具体的な指定で,マウスボタンならば 1,2,3,4,5のいずれか, キー入力ならばそのキーを表すキーシンボル(keysym)を指定する。 英数字キーは文字そのものがkeysymであり,たとえば r と 書けばRのキーを押した場合を意味する。したがって,イベントシーケンス

Control-B3-Triple-Key-r

は,Control キーとマウス第3ボタンを押しながら r のキーを3連打した場合の イベントを意味する。 各種記号やReturnキーやBSキーなどのkeysym は, システムによってあらかじめ決められている。 これらを調べるにはコマンドラインで,

xev

と起動し,出てきたウィンドウ内部で調べたいキーをタイプするか,

xmodmap -pke | less

で割り当てキーの一覧を見るとよい(X Window Systemの場合)。

ただし,ウィジェットによって反応できるイベントは異なり, 割り当てたイベントシーケンスがどこでも効くとは限らない。たとえば ラベルウィジェットではキー入力のイベントに反応させることはできない。

イベントハンドラ

bind メソッドの第2引数には, なんらかのイベントに結び付けるイベントハンドラを指定する。 ここにはRubyの Proc オブジェクトを指定する。Proc オブジェクトに渡すブロックは その位置で有効な変数が評価される。

tk-proc.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new("text"=>"変数xが有効なブロック\nこっちでクリックするとx=5") {
  x = 5
  bind('1', proc {printf("x=%d\n", x)})
  bg("pink")
}.pack("fill"=>"x")
TkLabel.new("text"=>"変数xが有効ではないブロック\nこっちはエラー") {
  bind('1', proc {printf("x=%d\n", x)})
  bg("#aef")
}.pack

TkButton.new("text"=>"Exit", "command"=>proc{exit}).pack
Tk.mainloop

この例は,後者のラベルでクリックすると変数 x が未定義でエラーを起こすという分かりやすいものだが, クラス定義を用いたスクリプトではスコープによるエラーを引き起こしやすい。 あえてエラーを起こすものを示す。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

class Test
  def hogehoge
    puts "Hoge!"
  end
  def initialize
    @hello = "Hello"
    @path  = "pathpathpath"   # Tkで用いている変数なので上書きはよくないが...
    ohayo = "Ohayo"
    TkLabel.new("text"=>"変数確認") {
      bind("1", proc {
             printf("ohayo=%s\n", ohayo.inspect)
             printf("@hello=%s\n", @hello.inspect)
             printf("@path=%s\n", @path.inspect)
           })
    }.pack
    myself = self               # 現在のオブジェクトをローカル変数に記録
    TkLabel.new("text"=>"メソッド確認") {
      bind("1", proc {
             p self
             hogehoge                # エラーになり処理はここで停止
             self.hogehoge           # これもエラー
             myself.hogehoge         # これならOK
           })
    }.pack
    TkButton.new("text"=>"Exit", "command"=>proc{exit}).pack
  end
end
Test.new
Tk.mainloop

実際に起動して「変数確認」の部分をクリックすると以下のように出力される。

ohayo="Ohayo"
@hello=nil
@path=".w00000"

initialize メソッドで定義した変数のうち, 代入どおりに出ているのはローカル変数 ohayo だけである。 @hello@pathは, 評価されるのが TkButton クラス内なので, そこでの値が得られているのが分かる。本来 @path 変数はTkのオブジェクトの論理的な位置を示す値が入る。

また,「メソッド確認」の部分をクリックすると以下の出力後にエラーが発生する。

#<Tk::Button:0x7f7ff6289d80 @path=".w00001">

command

ボタン型のウィジェットでは,bindによるイベントハンドラの 登録をせずに,command メソッドでボタンをクリックしたときの 挙動を定義できる。

tk-command.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkButton.new("text"=>"Button") {
  command(proc {puts "これはボタン"})
}.pack
TkCheckbutton.new("text"=>"Check Button") {
  command(proc {puts "これはチェックボタン"})
}.pack
TkFrame.new {|f|
  v = TkVariable.new
  TkRadiobutton.new(f, "text"=>"Radio Button") {
    command(proc {puts "ラジオボタン-1"})
    variable(v)
    value("1")
  }.pack("side"=>"left")
  TkRadiobutton.new(f, "text"=>"Radio Button") {
    command(proc {puts "ラジオボタン-2"})
    variable(v)
    value("2")
  }.pack("side"=>"left")
}.pack

Tk.mainloop

イベントハンドラへの情報

bindメソッドに指定する手続きに対し, 発生したイベントの詳細情報を渡すことができる。 たとえば以下のようにするとクリックが起きたときの画面上のポインタの X座標,Y座標が得られる。

tk-xy.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkCanvas.new {
  width(400)
  height(300)
  bind('1', proc {|x, y, a, b|
         printf("絶対:(%d,%d)\t", x, y)
         printf("相対:(%d,%d)\n", a, b)
       }, "%X %Y %x %y")
}.pack
TkButton.new() {		# なくてもいいがquitボタンを足す
  text("quit")
  command(proc{exit(0)})
}.pack("side"=>"right")

Tk.mainloop

上記のように,bind メソッドの第3引数に空白区切りの %置換文字列を指定すると,それらが手続きオブジェクトに 引数化されて渡される。

%記号意味
%%%自身
%#イベントのシリアル番号
%ddetailフィールドの値
%fEnter/Leaveイベントでのフォーカス値(0か1)
%kkeycode値
%x, %yイベントのウィンドウ内の相対x座標・y座標
%X, %Yイベントのx座標・y座標
%AUnicode値
%Tイベントの種別
%Wイベントを捕捉したウィジェット

即時キー入力処理

文字入力を主目的としないウィジェットでは,キー入力イベントを 捕捉するようにはできていない。ウィジェットの種類を問わず ショートカットキーなどの即時キー入力処理を行ないたい場合は, ルートウィジェット(Tk.root)に対してキー入力を bind すればよい。

仮想イベント

同じ機能に複数のキー割り当てを用意したいときや, 別のプラットフォーム用に異なるキー割り当てを用意したいときなどは, 複数のイベントシーケンスからなる仮想イベントを作って, それに機能を割り当てるとよい。仮想イベントは TkVirtualEvent クラスのオブジェクトとして, 登録したい複数のイベントシーケンスを渡して生成する。

たとえば,C-q のみ,C-x C-c の連続押し 両方のキーバインドを設定したいときは以下のようにする。

event_quit = TkVirtualEvent.new('Control-q', ['Control-x', 'Control-c'])
# この例では2個だがイベントシーケンスは何個でも
  :
# 特定のオブジェクトのbind部分で以下のように仮想イベントを使う
 bind(event_quit, proc{exit})

ジオメトリマネージャ

生成した部品は土台となる部品に配置される。一つの土台には複数の部品を 装着できる。このときにここの部品をどのように配置するかを決めるのが ジオメトリマネージャ(レイアウトマネージャ)である。tkでは pack,grid,place のジオメトリマネージャが使える。

いずれも土台となるウィジェット1つに対して 1つのジオメトリマネージャが使える (複数のマネージャを混合利用すると暴走する恐れがある)。 複数のジオメトリマネージャを混在させて使いたいときは, 後述するフレームウィジェットを 新たな土台として組み合わせる。

pack

最もラフに部品を配置できるジオメトリマネージャが pack で,土台となるウィジェット(最初はルートウィジェット)の空き領域の どこに次のウィジェットを置くかおおざっぱに「上の方」,「下の方」, 「右の方」,「左の方」いずれかで指定して配置を決定する。

土台となる部品があり,その上に3つの部品を

  1. 上の方に
  2. 左の方に
  3. 下の方に

という順番でpackを使って配置した場合次のような配置状態の遷移を取る。

  1. 上の方に配置 (pack("side"=>"top")
    部品1
    (空き領域)
  2. 左の方に配置 (pack("side"=>"left")
    部品1
    部品2 (空き領域)
  3. 下の方に配置 (pack("side"=>"bottom")
    部品1
    部品2 (空き領域)
    部品3

以上ですべての部品追加が完了した場合,空き領域が詰められ 各部品が必要最小限の大きさに調整される。 最終的な部品配置は以下のようなものとなる。

部品1
部品2 部品3

実際に上記の3手順で配置するプログラム

TkLabel.new("text"=>"部品1", "bg"=>"green").pack("side"=>"top")
TkLabel.new("text"=>"部品2", "bg"=>"pink").pack("side"=>"left")
TkLabel.new("text"=>"部品3", "bg"=>"yellow").pack("side"=>"bottom")
Tk.mainloop

を実行すると以下のような配置結果となる。

result of tk-pack0

「部品1」の背景部分の範囲を見ると分かるように, 部品と土台に隙間ができる場合がある。 隙間を埋めたい場合は "fill" を指定する。指定できる値は

"x"x軸方向(左右両側)を埋める
"y"y軸方向(上下両側)を埋める
"both"x・y軸方向(上下左右)を埋める
"none"埋めない

のいずれかで,たとえば上記の「部品1」の貼り付けを

pack("side"=>"top", "fill"=>"both")

に変えた場合は,以下のような配置結果となる。

result of tk-pack0

ただし,できあがったこのウィンドウも,ウィンドウサイズを大きくすると 次のようになる。

Enlarged window

これは,部品1だけが隙間を埋める設定になっていたからで, 部品2,部品3も "fill"=>"both" でpackすると ウィンドウサイズを大きくしたときに以下のようになる。

enlarged window(2)

部品3の上にまだ隙間があるのは,そこが空き領域だからで, 空き領域を侵蝕するように隙間を埋めさせるためには, "expand" を指定する。"expand" は,本来の持ち領域を超えてウィジェットを拡大させるかを決めるもので これに true を設定すると有効になる。部品3に "expand"=>true を設定し,最終的に

TkLabel.new("text"=>"部品1", "bg"=>"green").
 pack("side"=>"top", "fill"=>"both")
TkLabel.new("text"=>"部品2", "bg"=>"pink").
 pack("side"=>"left", "fill"=>"both")
TkLabel.new("text"=>"部品3", "bg"=>"yellow").
 pack("side"=>"bottom", "fill"=>"both", "expand"=>true)

として出したウィンドウを大きくすると以下のように隙間がなくなる。

enlarged window(3)

packジオメトリマネージャの配置を変えるための引数では, 以下のパラメータが使える。

"side" 空き領域のどちら側に配置するか。
"top"(上), "bottom"(下), "left"(左), "right"(右)
"fill" 配置するウィジェットが割り当て区画より小さいときに引き延ばして 隙間を埋めるか。"x", "y", "both", "none" のいずれかで指定。
"expand" 空き領域を埋めるように領域拡張するか
truefalse
"before" 指定したウィジェットより前に配置
"after" 指定したウィジェットのあとに配置
"ipadx" ウィジェットの左右の縁の内側の隙間間隔
"ipady" ウィジェットの上下の縁の内側の隙間間隔
"padx" ウィジェットの左右の縁の外側の隙間間隔
"pady" ウィジェットの上下の縁の外側の隙間間隔

"ipadx""ipady""padx""pady" に指定するのは長さで, 整数を指定するとピクセル, 単位付きの整数文字列を指定するとその単位での長さになる。単位は

のいずれかで,たとえば "pady"=>"5c" のように指定する。

grid

各部品を表形式で格子状に並べるのに適しているのが grid ジオメトリマネージャである。

tk-grid0.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'
TkLabel.new() {
  text("ラベルの1番"); bg("green")}.grid("row"=>0, "column"=>0) # 0行0列
TkLabel.new() {
  text("ラベル2"); bg("pink")}.grid("row"=>0, "column"=>1)      # 0行1列
TkLabel.new() {
  text("ラ\nベ\nル3"); bg("pink")}.grid("row"=>1, "column"=>0)  # 1行0列
TkLabel.new() {
  text("L 4"); bg("green")}.grid("row"=>1, "column"=>1)         # 1行1列

とすると,以下のような配置結果が得られる。

Grid

格子の各マス目の幅と高さは各列,各行が同じになるように調整される。 マス目の幅・高さより小さいウィジェットは中央に配置され隙間ができる。 "sticky" 属性を指定して縁に密着させる辺を指定することができる。

"n"上辺を密着 (North)
"s"下辺を密着 (South)
"w"左辺を密着 (West)
"e"右辺を密着 (East)

の1字以上を指定してどの辺を密着させるか決める。たとえば, 上のtk-grid0.rb の 「L4」ラベルのgridで "sticky"=>"wes" の指定を追加すると以下のようになる (tk-grid1.rb)。

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new() { # 0行0列
  text("ラベルの1番"); bg("green")}.grid("row"=>0, "column"=>0)
TkLabel.new() { # 0行1列
  text("ラベル2"); bg("pink")}.grid("row"=>0, "column"=>1)
TkLabel.new() { # 1行0列
  text("ラ\nベ\nル3"); bg("pink")}.grid("row"=>1, "column"=>0)
TkLabel.new() { # 1行1列
  text("L 4"); bg("green")}.grid("row"=>1, "column"=>1, "sticky"=>"wes")
Tk.mainloop

実行例:

sticky=>wes

さて,余白が気になるので,すべて余白を消すことを試みる。 4つのラベルすべてのgridに,"sticky"=>"news" を追加すると初期ウィンドウから余白は消える (tk-grid2.rb)

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new() { # 0行0列
  text("ラベルの1番"); bg("green")}.
  grid("row"=>0, "column"=>0, "sticky"=>"news")
TkLabel.new() { # 0行1列
  text("ラベル2"); bg("pink")}.
  grid("row"=>0, "column"=>1, "sticky"=>"news")
TkLabel.new() { # 1行0列
  text("ラ\nベ\nル3"); bg("pink")}.
  grid("row"=>1, "column"=>0, "sticky"=>"news")
TkLabel.new() { # 1行1列
  text("L 4"); bg("green")
}.grid("row"=>1, "column"=>1, "sticky"=>"nwes")

Tk.mainloop

実行例:

all 'news'

gridで作成したマス目はウィンドウサイズを変えたときにも変わらない。 このため上に示したウィンドウを大きくしても周りに余白ができるだけである。

grid+enlarged

ウィンドウサイズを変えたときに,中味のウィジェットも連動して 大きさを変える設定が可能である。これは,特定の列全体あるいは特定の行全体 に対して,拡大するときの他の列・行との伸縮負担の重み付けを行なうことで 制御する。上記の4ラベル配置例で,第0列と第1列の伸縮配分を1:3にするには 以下の文を追加する。

TkGrid.columnconfigure(Tk.root, 0, "weight"=>1)
TkGrid.columnconfigure(Tk.root, 1, "weight"=>3)

tk-grid3.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new() { # 0行0列
  text("ラベルの1番"); bg("green")}.
  grid("row"=>0, "column"=>0, "sticky"=>"news")
TkLabel.new() { # 0行1列
  text("ラベル2"); bg("pink")}.
  grid("row"=>0, "column"=>1, "sticky"=>"news")
TkLabel.new() { # 1行0列
  text("ラ\nベ\nル3"); bg("pink")}.
  grid("row"=>1, "column"=>0, "sticky"=>"news")
TkLabel.new() { # 1行1列
  text("L 4"); bg("green")
}.grid("row"=>1, "column"=>1, "sticky"=>"nwes")

TkGrid.columnconfigure(Tk.root, 0, "weight"=>1)
TkGrid.columnconfigure(Tk.root, 1, "weight"=>3)
Tk.mainloop

第1引数の TkRoot は,今回gridジオメトリマネージャで土台 となっているウィジェットで,新たな土台を作らない場合の最初の土台は Tk.Root,つまりルートウィジェットとなる。 この記述を追加したウィンドウを大きくすると以下のような結果となる。

grid+weight

上下に隙間があるのは,行方向の設定をしていないからで, 上下の隙間を埋めさせるためには TkGrid.rowconfigure で同様の設定をすればよい。

place

place は,ウィジェットの配置位置をx座標,y座標で直接指定できる ジオメトリマネージャである。大きさの決まっている土台に 座標を決めて部品を置いたり,ウィジェット間に重なりのある 配置をしたい場合に有用である。

tk-place.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'
TkOption.add("*font", "ipagothic 20")	# 標準フォントを大きく設定
Tk.root.width = 200
Tk.root.height= 80
TkLabel.new("text"=>"その1", "bg"=>"pink").place("x"=>10, "y"=>10)
TkLabel.new("text"=>"その2", "bg"=>"yellow").place("x"=>50, "y"=>30)
Tk.mainloop

tk-place

デフォルトでは配置するウィジェットの左上位置を基準とするが, "anchor" 属性でこれを変えることもできる。 属性値には

"n"上辺中央
"s"下辺中央
"w"左辺中央
"e"右辺中央
"nw"左上角
"ne"右上角
"sw"左下角
"se"右下角
"center"中央

のいずれかを指定する。

フレームウィジェット

frame は,複数のウィジェットを内部に配置するためのウィジェットで Rubyでは TkFrame で生成する。

既に述べたとおり,1つの土台に対しては 1つのジオメトリマネージャしか使えない。 複雑な部品レイアウトを実現したいとき,複数のジオメトリマネージャを 組み合わせたいことがある。このような場合,複数のフレームを ルートウィジェットに配置し,さらに各フレームごとに違う ジオメトリマネージャを適用して内部のウィジェットを配置するようにするとよい。

たとえば次のようなレイアウトを考える。

Nested Layout

上半分は何かの値の入力を促すラベルとエントリを対にしたものの集合, 下半分は左右に分かれたボタン。 上半分については,項目名とエントリの桁位置を揃えたいので grid ジオメトリマネージャを, 下半分についてはボタンを「左の方と右の方」とラフに置きたいので pack ジオメトリマネージャを使うことにする。

具体的な組み合わせ方としては,土台の上半分を占めるフレームを 上からpack,残った下半分を左と右からpack,さらに上半分の フレーム内をgridで制御してラベルとボタンを配置する。

(フレーム)
項目エントリ
項目エントリ
左のボタン右のボタン

このような配置を行なうプログラムの例を以下に示す。

tk-frame.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

=begin
+-------------------------+       +------------+
|住所      [            ] |       | j1   j2    |
|おなまえ  [       ]      |   =>  | n1   n2    |
|                         |       |            |
| [登録]         [クリア] |       | b1     b2  |
+-------------------------+       +------------+
=end

TkFrame.new {|f|
  # bg("yellow")	# レイアウトデバッグ時には背景色が有用
  j1 = TkLabel.new(f, "text"=>"住所")
  j2 = TkEntry.new(f, "width"=>20)
  n1 = TkLabel.new(f, "text"=>"おなまえ")
  n2 = TkEntry.new(f, "width"=>12)
  j1.grid("row"=>0, "column"=>0, "sticky"=>"w")
  j2.grid("row"=>0, "column"=>1, "sticky"=>"w")
  n1.grid("row"=>1, "column"=>0, "sticky"=>"w")
  n2.grid("row"=>1, "column"=>1, "sticky"=>"w")
  TkGrid.columnconfigure(f, 0, "weight"=>4) # 項目名の列
  TkGrid.columnconfigure(f, 1, "weight"=>1) # Entryの列
}.pack("fill"=>"x", "expand"=>true, "padx"=>10)
TkLabel.new("text"=>"").pack   # spacer
b1 = TkButton.new("text"=>"登録")	# 押しても何も起こらない
b2 = TkButton.new("text"=>"クリア")	# 押しても何も起こらない
b1.pack("side"=>"left", "padx"=>10, "pady"=>5)
b2.pack("side"=>"right", "padx"=>10, "pady"=>5)
Tk.mainloop

フレームウィジェットに限らず,新規のウィジェットを フレームなど別の親の子として生成するときには,ウィジェット生成 のnewメソッドの第1引数に親とするウィジェットのオブジェクト を指定する。

画像

画像を扱うには, まず元となる画像ファイルを画像オブジェクトに変換し, そののち画像を配置できるウィジェットに貼り付けるという手順をとる。

画像のラベルへの貼り付け

tkの標準では gif, ppm, pgm のみ扱える。例としてgif画像 (cool.gif) をラベル上に貼り付けて表示するものを示す。 cool.gif を同一ディレクトリにコピーしてから実行する。

tk-img.rb

#!/usr/koeki/bin/ruby
require 'tk'
img = TkPhotoImage.new("file"=>"cool.gif")
TkLabel.new("image"=>img).pack
TkButton.new("text"=>"quit", "command"=>proc{exit(0)}).pack
Tk.mainloop

tkimg拡張ライブラリ

JPGやPNGなど,他の画像形式を利用する場合は, tkextlib/tkimg/FORMAT が必要で, たとえば,PNG画像を使うには

require 'tkextlib/tkimg/png'

を追加記述する。例として,透過部分を含むPNG画像 (nikusoba.png) を表示するものを示す。

tk-imgpng.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'
require 'tkextlib/tkimg/png' # PNGを利用する場合必要

img = TkPhotoImage.new("file"=>"nikusoba.png")
TkLabel.new("image"=>img, "bg"=>"pink").pack
TkButton.new("text"=>"食べる",
             "command"=>proc{puts "ごちそうさま"; exit(0)}).pack
Tk.mainloop

画像ファイルを作業ディレクトリに保存し, 実行すると以下のようなウィンドウが現れ, [食べる]ボタンを押すと終了する。

tk-imgpng.rbの実行結果

対応している画像フォーマットは,Ruby の tkextlib ライブラリのあるディレクトリ中の tkimg/ にあるファイル一覧を見れば分かる。

ls `ruby -e 'puts $:[-3]'`/tkextlib/tkimg
bmp.rb     jpeg.rb    png.rb     setup.rb   tga.rb     xbm.rb
gif.rb     pcx.rb     ppm.rb     sgi.rb     tiff.rb    xpm.rb
ico.rb     pixmap.rb  ps.rb      sun.rb     window.rb

上記で得られない場合は,ruby -e 'puts $:' で 得られる各ディレクトリについて tkextlib/tkimg/ を探せばよい。

なお,tkimg の利用には,Rubyのライブラリだけでなくシステムに tkImg (http://sourceforge.net/projects/tkimg/) パッケージが必要である。

画像の手配方法

作成したプログラムを他者に渡して利用してもらう場合, 以上2つの例では,画像ファイルをあらかじめ保存しておかせる必要がある。 利用者に画像を用意する手間を省かせたい場合は,

  1. ソースプログラムに埋め込んでロードする
  2. Web経由でロードする

などの方法が使える。それぞれの具体例を示す。

  1. base64でソースプログラムに埋め込む場合

    画像ファイルがあまり大きくない場合はこの方法が有効である。 まず,元画像をbase64エンコードした文字列に変換する。 以下のいずれかの方法で,エンコード文字列が得られることを確認する。

    uuencode -m image.jpg image.jpg | tail +2
    ruby -rbase64 -e 'Base64.b64encode(ARGF.read)' image.jpg
    

    uuencode プログラムは BSD系やSolaris系システムでは標準装備されている。Linux系システムでは sharutilsパッケージを追加インストールすることで利用できる。

    画像を使いたいRubyプログラムを開き,ヒアドキュメントで base64エンコード文字列を代入する。

    image = <<_EOS_
    
    _EOS_
    

    のように入力しておき,挟まれた部分にエンコード文字列を挿入する。

    実際のプログラムは以下のような構成になる。

    #!/usr/koeki/bin/ruby
    # -*- coding: utf-8 -*-
    require 'tk'
    require 'tkextlib/tkimg/jpeg'
    
    shell = <<_EOS_                 # JPEG画像のbase64エンコード文字列
    /9j/4AAQSkZJRgABAQEASABIAAD//gAGS2FtZf/bAEMAEQwNDw0LEQ8ODxMSERUaKxwaGB
      〜〜 この部分に Base64 文字列が続く 〜〜
    IQgEIQgEIQgEIQgEIQgEIQg//Z
    _EOS_
    
    TkLabel.new() {
      image(TkPhotoImage.new("data"=>shell))
    }.pack
    TkButton.new() {
      text("kick")
      command(proc {exit(0)})
    }.pack
    Tk.mainloop
    

    実例を tk-imgheredoc.rb に示す。

  2. openurlでHTTPで取得する場合

    プログラムで利用する画像ファイルを,利用者が Web アクセスできる場所に置く。そのURLを open-uri 拡張込みの open で開き, read メソッドですべて読み取った文字列を TkPhotoImage.new に渡す。ただし, 通常行なわれる自動漢字コード変換をさせないよう, Tk::BinaryString メソッドに渡した結果を渡す。

    tk-imghttp.rb

    #!/usr/koeki/bin/ruby
    # -*- coding: utf-8 -*-
    require 'tk'
    require 'open-uri'
    require 'tkextlib/tkimg/png'
    
    img = open("http://www.yatex.org/lect/ruby/star.png", "r") do |s|
      s.read
    end
    
    TkLabel.new() {
      image(TkPhotoImage.new("data"=>Tk::BinaryString(img)))
      bg("white")
    }.pack
    TkButton.new() {
      text("exit")
      command(proc {exit(0)})
    }.pack
    Tk.mainloop
    

フォント

文字を表示できるウィジェットでは,表示する文字のフォントを選べる。 フォントはフォントオブジェクト(TkFont)で指定する。

tk-font.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

txv = TkVariable.new('24のフォント')
def enlarge(me)		# meには下のラベルオブジェクトが渡されて来る
  f = me.font
  v = me.textvariable
  f.size = (f.size.to_f*1.2).to_i
  v.value = sprintf("%dのフォント", f.size)
end

f = TkFont.new("ipagothic 24 italic")
TkLabel.new() {
  textvariable txv
  text("24のフォント")
  font(f)
  bind('Button-1', proc {enlarge(self)})
}.pack
Tk.mainloop

フォント名の指定は,Xのフォントファミリ名,サイズ,variant の3要素を空白で区切って指定する(後ろのものは順に省略可能)。

tkで使えるフォントファミリの一覧は TkFont.families で得られる。

irb -rtk
print TkFont.families.sort.join(", ")

フォント名に空白が含まれる場合は,各要素を配列化するか, 空白を含む文字列部分を { } で囲む。 下記の2つは同じ指定となる。

f = TkFont.new(["vl gothic", 30])
f = TkFont.new("{vl gothic} 30")

また,フォントファミリ,サイズ,太さ,傾きを個別に 属性設定する指定方法もある。それぞれ, "family", "size", "weight", "slant" で指定する。たとえば,

TkFont.new("mikachan 24 italic")

という指定は,

TkFont.new("family"=>"mikachan", "size"=>24, "slant"=>"italic")

と同様の指定に置き換えられる。

フォント指定は必ずしもフォントオブジェクトを介さず,

TkLabel.new("text"=>"hello", "font"=>"times 24 bold").pack

のようにしてもよいが,その都度フォントオブジェクトが作られ, あとから制御できないことから,共通フォントを複数のオブジェクトで 使う場合や,動的にフォントを変えたい場合はフォントオブジェクトを 利用した方がよい。

fnt = TkFont.new("family"=>"aquafont", "size"=>20)
l = TkLabel.new("text"=>"Hello", "font"=>fnt)

としてラベル生成しておくと,

fnt.family = "y.ozfont"
fnt.size = 40

などとして,既存のラベルのフォントを変えられる。

代表的なウィジェット

GUI部品を作る上で有用なウィジェットを列挙する。

ラベル

tk の label に基づくラベルは 表示するのみのテキストを配置することを主目的としたウィジェットで, テキストの色やフォントを変えたり,背景として画像を表示することが容易で, 手軽に使える。 最初の tk-helloプログラムが よい例となっている。

ポインティングデバイス関連のイベントに反応して色を変える例を示す。

tk-labelev.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new() {
  text("Hello, world!")
  bg("#fdd"); fg("black")
  bind("Enter", proc {bg("#dfd")})
  bind("Leave", proc {bg("#fdd")})
  bind("Button-1", proc {exit(0)})
}.pack
Tk.mainloop

Enterイベント(ラベル内にマウスポインタが入ること)で背景色を #dfd (淡い緑)に,Leaveイベント(同じく出ること)で背景色を #fdd (淡い赤)に変更する。

メッセージ

複数行に渡る文章を提示するには message が使いやすい。Rubyでは TkMessage のオブジェクトとして 生成する。パラグラフの縦横比を百分率で指定する(aspect)か, 折り返し幅をピクセル数(数値)か, 文字幅(文字列)で指定する(width)。

tk-message.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkMessage.new() {
  aspect(400)
  text("これはアスペクト比400%に指定したメッセージエリアである。
ソース中の改行はそのまま改行として反映されるが,
必ずしも改行させたくない\
位置にはバックスラッシュを入れる。")
  bg("pink")
}.pack
TkMessage.new() {
  width(200)
  text("これは幅200ピクセルに指定したメッセージエリアである。")
  bg("yellow")
}.pack
TkMessage.new() {
  width("10c")
  text("これは幅10cmに指定したメッセージエリアである。")
  bg("#aef")
}.pack

Tk.mainloop

これを実行すると以下のようになウィンドウが現れる。

tk-message.rbの実行結果

改行位置が柔軟に設定されていることに注意せよ。

ボタン

tkの button に基づくボタンも,ラベルと同様画像,テキストを設定できる。押したときの アクションは command メソッドにて指定するのは 既に出た例のとおり。

チェックボタン

チェックボタン(checkbutton)は, ボタンが押されている(ON)か解除されている(OFF)かで, 2値を取得するメソッドである。Rubyでは TkCheckButton オブジェクトとして生成する。

ボタンには状態を保存するためのtk変数を割り当てて, それ経由で値を取得する。デフォルトではOFFのとき "0" が,ONのとき "1" が得られる。 tk変数は TkVariable で生成し,それを variable メソッドで割り当てる。ただし,このtk変数は, チェックボタンの選択・解除操作をして初めて値が入るので, チェックボタン生成時に選択(select)か, 解除(deselect)しておく方がよい(例参照)。

tk-checkbutton.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

v = TkVariable.new
TkCheckButton.new {
  text("抜ける")
  width("10")
  height("5")
  variable v
  deselect
}.pack
TkButton.new() {
  text(" GO! ")
  command(proc {
            if v == "1"
              puts "抜けます。"; exit 0
            else		# "0" のはず
              puts "まだまだ"
            end
          })
}.pack
Tk.mainloop

ラジオボタン

radiobutton は,複数のボタンでグループをなし,どれか1つだけが 選択された状態になるものである。TkCheckButton と ほぼ同様の使い方だが,同じtk変数を使うものどうしがグループとなる。 当該ボタンが押されたときにtk変数に設定する値は value メソッドで定義しておく。

tk-radiobutton.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

v = TkVariable.new
TkRadioButton.new {
  text("あじ")
  variable v
  value "鯵"
}.pack("fill"=>'x')
TkRadioButton.new {
  text("いか")
  variable v
  value "烏賊"
}.pack("fill"=>'x')
TkRadioButton.new {
  text("うなぎ")
  variable v
  value "鰻"
}.pack("fill"=>'x')
TkButton.new {
  text("決定")
  command(proc {
            if v.value == "" then     # 何も選んでいないとき
              puts("何か選んでね。")
            else
              printf("%s食べよう!\n", v.value)
              exit 0
            end
          })
}.pack
TkButton.new {
  text("リセット")
  command(proc {v.value = ""})
}.pack
Tk.mainloop

文字列入力

1行内の短文入力には entry ウィジェットを利用する。Rubyでは TkEntry を利用する。 入力窓のみのウィジェットであるため,その直前に入力ガイドを表示する label を配置するのが望ましい。

entry ウィジェット利用時には 入力された値を保持するためのtk変数を割り当て,これを TkEntrytextvariable メソッドで指定する。 このtk変数を他のウィジェットでも用いると,入力途中の値も共有される。 以下の例では,名前を入力するためのentryウィジェット(e1)と, それとは全く別の label ウィジェット(n2) でtk変数 vname を共有させている。

tk-entry.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

# 入力中の値を別のウィジェットで共有させることができる。
# それには同じ TkVariable を指定する。
vname = TkVariable.new("名無")

# Entryに何をいれるべきかのラベルを付ける。
# 桁が揃った方が気持ちよいので,グリッドマネージャを使う。
TkFrame.new() {|f|
  # f はフレーム自身。内部でnewするウィジェットは親にfを指定すること
  # l1 = TkFrame.new(f) {|f2|
  #   bg("yellow")
  #   TkLabel.new(f2, "text"=>"名前?:", "justify"=>"left", "bg"=>"yellow") {
  #     pack("side"=>"left", "fill"=>"both")
  #   }
  # }
  l1 = TkLabel.new(f, "text"=>"名前は?:")
  e1 = TkEntry.new(f, "bg"=>"pink", "textvariable"=>vname)
  l2 = TkLabel.new(f, "text"=>"じゅうしょは?:")
  e2 = TkEntry.new(f, "bg"=>"pink")
  TkGrid(l1, e1, "sticky"=>"news")
  TkGrid.columnconfigure(f, 0, "weight"=>1)
  TkGrid(l2, e2, "sticky"=>"e")
  TkOption.add("*foreground", "#915711")
  n1 = TkLabel.new(f) {
    textvariable vname
  }
  n2 = TkLabel.new(f) {
    text("さん こんにちは")
  }
  TkGrid(n1, n2)
  TkButton.new(f) {
    text(" 登録 ")
    command(proc {
              printf("%sにおすまいの%sさんですね!\n5万円になります。\n",
                     e2.value, e1.value)
              exit(0)
            })
  }.grid("columnspan"=>2)
}.pack("fill"=>"both", "expand"=>true)
Tk.mainloop

このウィンドウレイアウトは,上半分にフレームウィジェットを作成し, その中でグリッドレイアウトを利用している。実行すると以下のような ウィンドウが現れる。

tk-entry.rbの実行例

テキストとスクロールバー

textウィジェット

text ウィジェットは,短くないテキストの入力に有用で,Rubyでは TkText を利用する。入力された値は value で取得する。

tk-text.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new("text"=>"今日の一言").pack
txt = TkText.new("width"=>40, "height"=>3).pack
TkButton.new("text"=>"登録") {
  command(proc {
            printf("復唱: %s\n", txt.value) if txt.value > ""
            exit(0)
          })
}.pack("side"=>"left", "padx"=>5, "pady"=>5)
Tk.mainloop

textウィジェット内のマークとタグ

textウィジェットの編集領域内には,特定の文字挿入ポイント (文字と文字の間)に好きな名前の「マーク」を付けられる。 また,2つのポイントで囲まれた領域に好きな名前の「タグ」を付けられる。 タグ付けされた領域はさらに,他の部分とは独立して属性を変えることができ, たとえば特定部分だけ背景色を変えたりフォントを変えたり, あるいはbindメソッドで特定のイベントに対するアクションを定義できたりする。

以下のマークとタグは特別な意味を持つ。

insert マーク 次の入力文字が入る場所(テキストカーソルの左位置)
current マーク マウスポインタのある場所(流動的)
sel タグ 選択された領域で次のコピーやカット操作の対象範囲

これらの値は,ユーザのカーソル移動や領域選択操作によって自動的に 更新されると同時に,プログラムからも操作することができる。

textウィジェット内の位置指定

textウィジェットの編集領域内に対して文字列挿入や領域削除など, 様々な操作を行なえる。操作対象となる場所の指定にはtk固有の index 記法を用いる。

base modifier modifier modifier ...

という書式で,base は基準となる位置,modifier は,その位置からどれだけずらすかを指定する。 たとえば,"1.0 + 3 chars" という表記は 「1行目の0カラム目から3文字後ろ」を意味し,"1.0"base に,"+ 3 chars"modifier に相当する。base として使える表記の主なものを示す。

line.char line 行目の char 文字目。行数は1から, 文字数は0から数える。charend を指定すると行末の改行位置を示す。
@x,y textウィンドウの座標 (x, y) 位置に最も近い文字位置を示す。
end テキスト末尾。
mark mark という名前のマークの保持する位置。
tag.first tag という名前のタグの保持する領域の開始位置。
tag.last tag という名前のタグの領域の終了位置。

また,位置指定の modifier には以下のものがある。

+ count chars 基準位置から count 文字分後ろ
- count chars 基準位置から count 文字分前
+ count lines 基準位置から count 行分下
- count lines 基準位置から count 行分上
linestart 基準位置の行頭
lineend 基準位置の行末
wordstart 基準位置の単語先頭
wordend 基準位置の単語末

charslines によるずらし量はテキスト全体の先頭や末尾を越えない。また, count の前後の空白は省略してもよい。

テキスト操作メソッド

その他,テキスト系ウィジェットへの操作メソッドは, http://docs.ruby-lang.org/ja/2.1.0/class/TkText.html に一覧がある。ただし,具体的な使い方の詳細は http://www.tcl.tk/man/tcl8.5/TkCmd/text.htm を併せて参照する必要がある(8.5の部分は導入されている TclTk のバージョンに置き換える)。

スクロールバーの追加

限られた面積で長い文を入れさせたいときはスクロールバー (scrollbar) を付ける。 スクロールバーはテキストエリアと一体化させ,ウィンドウサイズを変えても 操作できるように,スクロールバーを先にpackする。

tk-scroll.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

ysb = TkScrollbar.new.pack("fill"=>"y", "side"=>"right")
txt = TkText.new("width"=>40, "height"=>5, "bg"=>"#fee") {
  selectbackground("pink")
  yscrollbar(ysb)
}.pack("side"=>"right")
TkButton.new("text"=>"決定") {
  command(proc {
            printf("[%s]\n", txt.value)
            exit(0)
          })
}.pack("before"=>ysb, "side"=>"top")
Tk.mainloop

tk-scroll.rb を実行した結果を示す。

tk-scroll.rbの実行例

リストボックス

複数の候補から1つ,または複数の値を選ばせるときは listbox を用いる。Rubyでは TkListbox のオブジェクトを生成する。

選ばせるアイテムは insert メソッドで足していく。 ユーザがアイテムの選択状態を変えるたびにインスタンス変数の curselection に選んだものの添字番号が入る。 デフォルトでは1つのアイテムしか選べないが,selectmode を変えることにより選択操作の体系が変わる。

single常に1つ選べる。
browse 常に1つ選べる。ボタン1で選択をドラッグできる。
multiple 複数選べる。ボタン1での選択が他の選択に影響を与えない。
extended 複数選べる。ボタン1単体で押すとそれを選んで他を解除する。 SHIFTを押しながらの範囲選択や, CTRLを押しながらの追加選択/解除が使える。

tk-listbox.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkLabel.new("text"=>"何ラーメンにしますか?", "bg"=>"white").pack("fill"=>"x")
TkListbox.new {|men| # Listboxウィジェット自身が men に入る
  mode = TkLabel.new("text"=>"(1杯のみ)").pack # あとで値を変える
  insert("end", "塩")		# 末尾にアイテムを追加
  insert("end", "しょうゆ")
  insert("end", "味噌")
  insert(2, "とんこつ")		# 2番目の位置にアイテムを追加
  insert("end", "四川")
  selection_set(1)		# 明示的に選択する
  pack("side"=>"right")
  # 以下のボタンではListboxを持つブロック変数(men)にアクセスしたいので
  # ブロック内に記述してグローバル変数化せずに済ます。
  TkButton.new("text"=>"1杯のみ") {
    command(proc{
              men.selectmode = "single"		# 1つだけ選べる
              mode.text("(1杯のみ)")		# 連動してラベルを変える
              # 選ばれたものの添字の配列が curselection に入っている
              men.curselection[1..-1].each do |i|
                men.selection_clear(i)	# 明示的に選択解除
              end
            })
  }.pack("fill"=>"x")           # デフォルトは "side"=>"top"
  TkButton.new("text"=>"何種類も") {
    command(proc{
              men.selectmode = "extended"	# 何個でも選べる
              mode.text("(何種類も)")		# 連動してラベルを変える
            })
  }.pack("fill"=>"x")
  TkButton.new("text"=>"決定") {
    bg("#efe")
    command(proc{
              for i in men.curselection
                printf("%sラーメン一丁\n", men.get(i))
              end
            })
  }.pack("fill"=>"x")
}
TkButton.new("text"=>"店を出る") {
  bg("#ecc");  command(proc{exit})
}.pack("side"=>"bottom", "fill"=>"x")

Tk.mainloop

tk-listbox.rb を実行して, [何種類も] ボタンを押して selectmode"extended" にしてから CTRL+クリック で複数の候補を選択している様子を示す。

tk-listbox.rbの実行例

アイテムが多いときは,スクロールバーを付けることもできる。

tk-listboxscr.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkListbox.new() {
  yscrollbar(TkScrollbar.new.pack("fill"=>"y", "side"=>"right"))
  0.upto(100) do |i| insert("end", i.to_s+"番のアイテム") end
}.pack("side"=>"right")
TkButton.new("text"=>"quit", "command"=>"exit").pack
Tk.mainloop

100個のダミーアイテムを生成したリストボックスに スクロールバーを付けた様子を示す。

tk-listboxscr.rbの実行例

スケール

一定範囲の整数を選ばせるためには数直線状のスケールを出す scale を用いる。Rubyでは TkScale を利用する。

1から12までの整数を数直線状のスケールで選べるプログラムの例を示す。

tk-scale.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

v = TkVariable.new
TkScale.new() {
  variable v
  from	1
  to	12
  set	Time.now.month
  # showvalue false
  label "開始月"
  orient "horizontal"	# or "vertical"
  command(proc {STDERR.printf("\r%d", v)})
}.pack
# 同じtk変数で連動するウィジェットを作れる(なくてもよい)
TkEntry.new("textvariable"=>v).pack
TkButton.new("text"=>"Set") {
  command(proc {printf("\n%s", `cal #{v} #{Time.now.year}`); exit(0)})
}.pack
Tk.mainloop

このプログラムでは,入力値を保持するtk変数 vTkEntry ウィジェットと共有させ, スケール部分のスライドでも,入力窓への直接数値入力どちらでも 数値指定ができるようになっている。さらに,TkScale ウィジェットで値が変更されたときの処理を command で設定(STDERR.printf の部分)しているため, 端末画面にも選択途中の数値がその都度出力される。

tk-scale.rbの実行画面

スピンボックス

spinbox は,指定した範囲の数値をエントリボックスで直接入力させつつ, マウスクリックでも数値の増減を制御できる(下図参照)。

spinbox

Rubyでは TkSpinbox で作成する。

tk-spinbox.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

v = TkVariable.new
TkSpinbox.new() {
  textvariable v		# tk変数を利用するならtextvariableで
  to	12			# toを先に指定する必要がある。
  from	1
  font	"times 18"
  # values [1,3,5,7,8,10,12]	# 有効な値を配列で与えることも可
  set	Time.now.month
  width	4			# 入力窓の幅
  bg	"khaki"
  command(proc {STDERR.printf("\r%d", v)})
}.pack
TkButton.new("text"=>"Set") {
  command(proc {printf("\n%s",
	 `cal #{v} #{Time.now.year}`); exit(0)})
}.pack
Tk.mainloop

scaleウィジェット同様,command によって値が変更されたときの動きを定義できる。

メニュー

GUIアプリケーションのためのメニューは menu ウィジェットで作成する。Rubyからは,一括でメニューバーを作れる TkMenubar クラスを用いると手軽に構築できる

たとえば,以下のようなメニュー構成を作るものとする。

このメニュー階層を表す配列を TkMenubar に与えて以下のようにする。

tk-menubar.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

menuspec=
  [[["F ファイル", 0],
    ["O 開く", proc {puts "open"}, 0],
    ["C 閉じる", proc {puts "close"}, 0],
    ["--"],
    ["Q 終了", "exit", 0]],
   [["E 編集", 0],
    ["C コピー", proc {puts "copy"}, 0],
    ["X カット", proc {puts "cut"}, 0],
    ["V ペースト", proc {puts "paste"}, 0]]]

TkFrame.new {|f|
  pack("fill"=>"x")
  TkMenubar.new(f, menuspec).pack("side"=>"left")
}
TkScrollbar.new {|s|
  pack("side"=>"right", "fill"=>"y")
  TkText.new("width"=>40, "height"=>10, "bg"=>"#f8f0f0") {
    yscrollbar(s)
  }.pack("side"=>"right")
}
Tk.mainloop

menuspec の値にある整数 0 はアクセラレータ指定で, たとえば,

["Preference", proc {setpref()}, 7]

とすると,メニュー文字列 "Preference" の0から数えて7バイト目,つまり n に下線が引かれ, キーボードで n をタイプしたときにその項目が選ばれるようになる。 日本語メニューを作りたい場合でもメニュー文字列を日本語だけにせず, アクセラレータキーを先頭に書いておくとよい。

ただし,TkMenubar によるメニューにはカスケードメニュー (メニューの1アイテムを選ぶとさらにメニューが出てくるもの) は作れない。その他,メニューのアイテムにはチェックボタンや ラジオボタンも作れるが,これらは TkMenuTkMenubutton を直接制御する必要がある。 メニューのみ作成するサンプルプログラムを示す。

tk-menu.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkOption.add("*font", "ipagothic 20")
menubar = TkFrame.new {
  relief	"raised"
  borderwidth	3
}.pack("fill"=>"x")

menu_f = TkMenubutton.new(menubar) {
  text "File"
  underline 0
}.pack("side"=>"left")
menu_e = TkMenubutton.new(menubar) {
  text "Edit"
  underline 0
}.pack("side"=>"left")

filemenu = TkMenu.new(menu_f, "title"=>"ファイルメニュー")
filemenu.add('command',
             "label"=>"O 開く",
             "command"=>proc {puts "open"},
             "underline"=>0)
filemenu.add('command',
             "label"=>"C 閉じる",
             "command"=>proc {puts "close"},
             "underline"=>0)
filemenu.add('separator')
filemenu.add('command',
             "label"=>"Q 終了",
             "command"=>"exit",
             "underline"=>0)

editmenu = TkMenu.new(menu_e, "title"=>"編集メニュー")
editmenu.add('command',
             "label"=>"C コピー",
             "background"=>"pink",
             "command"=>proc{puts "copy"},
             "underline"=>0)
editmenu.add('command',
             "label"=>"X カット",
             "command"=>proc{puts "cut"},
             # "columnbreak"=>1,	# 次の行に行く
             "underline"=>0)
editmenu.add('command',
             "label"=>"V ペースト",
             "command"=>proc{puts "paste"},
             "underline"=>0)

zoom = TkVariable.new("100")
zoommenu = TkMenu.new(menu_e)

zoommenu.add("radiobutton",
             "label"=>"50%",
             "variable"=>zoom,
             "value"=>"50",
             "command"=>proc{
               puts zoom.value
               zoom.value="50"
             },
             "underline"=>0,
             "indicatoron"=>true)
zoommenu.add("radiobutton",
             "label"=>"100%",
             "variable"=>zoom,
             "value"=>"100",
             "underline"=>0,
             "indicatoron"=>true)
zoommenu.add("radiobutton",
             "label"=>"200%",
             "variable"=>zoom,
             "value"=>"200",
             "underline"=>0,
             "indicatoron"=>true)

zoommenu.add("command",
             "label"=>"see zoom",
             "underline"=>0,
             "command"=>proc {STDERR.puts zoom.value})
editmenu.add('cascade',
             "label"=>"Z ズーム",
             "menu"=>zoommenu,
             "underline"=>0)


menu_f.menu(filemenu)
menu_e.menu(editmenu)

TkScrollbar.new {|s|
  pack("side"=>"right", "fill"=>"y")
  TkText.new("width"=>40, "height"=>10, "bg"=>"#f8f0f0") {|t|
    yscrollbar(s)
    # 第3ボタンクリックでzoommenuを出す
    bind('Button-3', proc{|x, y| zoommenu.popup(x, y)}, "%X %Y")
  }.pack("side"=>"right")
}
Tk.mainloop

このプログラムでは以下のような階層メニューが出る(実際に機能するのは 「ファイル→終了」のみ)。

tk-menu.rbの実行例

ダイアログ

ユーザになんらかの明示的な確認をさせたいときに新たな小ウィンドウを出して メッセージとともに確認ボタンを押させるものが messageBox で,Rubyでは Tk.messageBox メソッドで作成する(クラスではなくメソッド)。 独立したウィンドウを作成するため既存ウィジェットにpack したりする必要はない。

ダイアログウィンドウの作成は,

Tk.messageBox(ハッシュ)

の形で行なう。 ハッシュには以下のキーと値の組み合わせが指定できる。

defaultデフォルトで選択されるボタン
icon アイコンの種類 ("error", "info", "question", "warning" のいずれかでデフォルトは "info")
message出力するメッセージ文字列
parent親ウィンドウ(その上に出現する)
titleウィンドウタイトルとする文字列
type提示されるボタンセットのタイプ
  • abortretryignore

    [abort] [retry] [ignore] の 3つのボタン

  • ok

    [ok] ボタンのみ

  • okcancel

    [ok] と [cancel] ボタン

  • retrycancel

    [retry] と [cancel] ボタン

  • yesno

    [yes] と [no] ボタン

  • yesnocancel

    [yes] [no] [cancel] の 3つのボタン

このメソッドを呼ぶと,選択されたボタンの名前の文字列が返る。 たとえば,

Tk.messageBox('type'=>'yesnocancel',
	'default'=>'cancel',
	'message'=>"ファイルがありません\n作成しますか",
	"icon"=>"question")

とすると, Tk.messageBox のようなウィンドウが出され,そのままReturnを押すと デフォルトの Cancel が選ばれ,メソッドの返却値として "cancel" が返る。別のボタンを押すとそれに対応する値が 全部小文字の文字列として返る。

Canvas

丸や多角形,直線などの部品だけでなく,他のウィジェットの 土台ともなりうる多機能なウィジェットが canvas である。

Canvas内に配置できるそれ専用のウィジェットが各種揃っている。 それらは,Tkc で始まる名前のもので,newのときの 第1引数に親となるcanvasウィジェット,残りの引数に 座標や大きさなど,図形の形に則した値を指定して生成する。 一度生成した図形は自動的に親となるCanvasに貼り付けられ, 生成した後でも属性を変更することができる。たとえば,canvas内の 座標や大きさを決める属性値を後から変更し, 画面上でアニメーションのように動かしたりするなどのことが容易にできる。

tk-canvas.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

w = 400
c = TkCanvas.new("width"=>w, "height"=>w).pack		# 正方形のCanvas
TkButton.new("text"=>'quit', 'command'=>'exit').pack	# 終了ボタン
r = 100                         			# 円の初期半径=100
width = c.width
cx, cy = c.width/2, c.height/2
# 円は,外接する四角形の対角頂点座標を4値で指定して生成する
ovl = TkcOval.new(c, cx-r, cx-r, cx+r, cy+r,
                  'fill'=>'yellow', 'outline'=>'black')

Thread.new {			# Tk.mainloopをメインスレッドで走らすため
  while true			# この無限ループは別スレッドで動かす
    r = (r+8)%(width/2)		# rを8ずつ増やして枠を超えたら戻るように
    # あとで(楕)円のパラメータを変更できる
    ovl.coords(cx-r, cx-r, cx+r, cy+r)
    sleep 0.1
  end
}
Tk.mainloop

上記の例では,0.1秒ごとに円の面積を変えている。 この部分は Thread を生成して別スレッドで実行しているが, ツールキット付属のタイマである TkAfter 利用する方がよい。

TkAfter.new(ミリ秒, 繰り返し回数, 処理)

の形式でタイマオブジェクトを生成し,以下のメソッドで制御する。

start開始する
stop停める
cancel停める
skip処理を1回飛ばす
restart再開する
continue再開する(待ち時間を再設定可)
info情報配列を返す

第1引数の実行間隔は整数で与えることもできるが, 手続オブジェクトを渡して,その返却値で各回の待ち時間を動的に変えることも できる。第2引数に -1 を指定すると処理を実行し続ける。
参考: http://jp.rubyist.net/svn/rurema/doctree/trunk/refm/api/tk_off/tkafter.rd.off

弾むボール tk-ball.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'
require 'tkextlib/tkimg/png'

img = TkPhotoImage.new("file"=>ENV["IMG"]||"ball.png")
$top = img.height*2
$base = $top - img.height/2
$e = 0.8
def gety(t)
  # y = -(x-1)^2 + 1
  x = ((t-10)%20)/10
  y = -(x-1.0)**2 + 1.0
  return $base - $top*y/2
end

wait = TkVariable.new('50')
TkCanvas.new {|c|
  width img.width*3
  height $top
  cx = width/2; cy = $base
  ball = TkcImage.new(c, cx, cy, "image"=>img)
  i = 0.0
  tm = TkAfter.new(proc{val=wait.value}, -1, 
                   proc {ball.coords(cx, gety(i+=1))
                   }).start
  TkScale.new {
    variable wait
    to(100)
    from(1)
    label 'wait'
  }.pack('side'=>'left')
  TkButton.new("text"=>"stop", "command"=>proc{tm.stop}).pack("side"=>"left")
  TkButton.new("text"=>"start", "command"=>proc{tm.start}).pack("side"=>"left")
  TkButton.new("text"=>"quit", "command"=>"exit").pack("side"=>"left")
}.pack
Tk.mainloop

マウスでオブジェクトをつかみ,ドラッグで移動する

tkc-drag.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'

TkCanvas.new() {|c|
  width 400
  height 300
  TkcRectangle.new(self, 50, 50, 100, 80) {
    fill("yellow")
    ox, oy = nil, nil           # 以前の座標値
    bind("ButtonPress-1", proc {|x, y|
           ox, oy = x, y        # ボタン押された瞬間の座標を記憶
           c.cursor("fleur")    # 移動カーソルに変更
         }, "%x %y")            # %x %y はウィンドウ内の相対座標
    bind('Motion', proc {|x, y|
           next unless ox
           move(x-ox, y-oy)     # 前回からの差分だけ移動
           ox, oy = x, y
         }, "%x %y")
    bind('ButtonRelease-1', proc {
           c.cursor("")         # カーソルを戻す
           ox, oy = nil, nil}
         )
  }
}.pack
Tk.mainloop

Tkcウィジェット

Tkc* で始まる様々な図形オブジェクトを示す。 irbでルートウィジェットとCanvasを出しそれに順次以下の ウィジェットを貼り付けていくと分かりやすい。まず,irb を起動しスレッドで Tk.mainloop を起動しておく。

irb -rtk
cv = TkCanvas.new('width'=>400, 'height'=>300).pack
Thread.new { Tk.mainloop }

以下,cv の保有するキャンバスに貼り付けていく 形式で例示を進める。

座標の取り方

Canvasにウィジェットを配置する位置を決めるときは, Canvas内の相対座標を取得するリスナをバインドしておくとよい。 上記のcv変数にあるCanvasであれば,

cv.bind('1', proc{|x, y|
	printf("(%d,%d)\n", x, y)}, "%x %y")

としておくと,第1ボタンのクリックで知りたい位置の座標が分かる。

TkcTag

TkcTag を利用すると, 複数のキャンバスウィジェットをグループ化して,メンバーすべての 属性を変えるなどのことができる。上記の例の折れ線(l)と 画像(img)をまとめて移動してみる。

grp = TkcTag.new(cv)
l.tags = grp
img.tags = grp

grp.move(100, 50)
grp.move(-100, -50)

# state属性で隠す hidden, normal, disabled
grp.state = 'hidden'
grp.state = 'normal'

バウンディングボックス

単一ウィジェット,あるいはグループ化したウィジェット群を すべて包含する長方形の対角座標を bbox メソッドで取得できる。上記の例では,

grp.bbox
=> [100, 48, 220, 150]

この矩形に線を引けばドローイングツールの 矩形選択のような枠を描くことができる。

box = TkcRectangle.new(cv, grp.bbox, 'dash'=>'.')

Canvas内ウィジェットの検索

Canvasには子供となる多数のウィジェットを配置して使うことになるため, 処理対象となる特定の子ウィジェットを選別する必要が発生する。 このときに使うのが TkCanvasfind メソッドである。

canvas.find(Command[, Args])

第1引数 Command の部分には以下のいずれかが指定できる (指定時には他と区別の付く範囲で省略が可能)。

以上のCanvasウィジェット群のirb操作をまとめたものを tkc-irb.rb に示しておく。

# -*- coding: utf-8 -*-
cv = TkCanvas.new('width'=>400, 'height'=>300).pack
Thread.new { Tk.mainloop }

r = TkcRectangle.new(cv, 50, 50, 100, 100, 'outline'=>'blue')
re = TkcRectangle.new(cv, 120, 170, 60, 230,
                      'fill'=>'green', 'outline'=>'orange')
a = TkcArc.new(cv, 50, 50, 100, 100,
               "start"=>0, "extent"=>60, "fill"=>"pink")
a.configure('extent'=>120)
a.coords(50, 25, 100, 75)
a.move(0, 25)
a.style = 'chord'
a.style = 'arc'
a.style = 'pieslice'

require 'open-uri'
require 'tkextlib/tkimg/png'
url = 'http://www.yatex.org/lect/ruby/star.png'
star = TkPhotoImage.new('data'=>
	Tk.BinaryString(open(url){|s| s.read}))
img=TkcImage.new(cv, 100, 50, 'image'=>star)
img.delete
img=TkcImage.new(cv, 100, 50, 'image'=>star, 'anchor'=>'nw')
require 'tkextlib/tkimg/jpeg'
url2='http://www.yatex.org/lect/ruby/shell.jpg'
kame = TkPhotoImage.new('data' =>
	Tk.BinaryString(open(url2){|s| s.read}))
img.image = kame
img.image = star
l = TkcLine.new(cv, 111, 80, 212, 80,
                121, 145, 162, 52, 191, 142,
                111, 80, 'fill'=>'red', 'width'=>3)
ya = TkcLine.new(cv, 120, 200, 300, 200,
	 'arrow'=>'first', 'width'=>3)
ya.arrow = 'last'
ya['arrow'] = 'both'
po = TkcPolygon.new(cv, 30, 30, 30, 160, 250, 150, 220, 35,
                    'fill'=>'cyan')
po.smooth = true
po.splinesteps = 2
po.splinesteps = 12
po.lower
img.raise
l.raise
cx, cy, r = 300, 200, 30
ov = TkcOval.new(cv, cx-r, cy-r, cx+r, cy+r, 'fill'=>'navy')
txt = TkcText.new(cv, 300, 120) {
  text 'ぐるぐる'
  fill 'darkgreen'
  font 'times 20'
}
def guru(x, y, r, ya, ov)
  theta = 2*Math::PI*Time.now.to_f
  vx = x + r*Math.cos(theta)
  vy = y + r*Math.sin(theta)
  ya.coords(120, 200, vx, vy)
  ov.coords(vx-r, vy-r, vx+r, vy+r)
end

job = TkAfter.new(50, 100, proc {guru(cx, cy, r, ya, ov)})
job.start
grp = TkcTag.new(cv)
l.tags = grp
img.tags = grp

grp.move(100, 50)
grp.move(-100, -50)
grp.state = 'hidden'
grp.state = 'normal'
box = TkcRectangle.new(cv, grp.bbox, 'dash'=>'.')

Canvasウィジェット使用例

cursesの例で示したキャラクタがジャンプするプログラム cur-jump.rb と同等のものをRuby/tkで作成したものを示す。

tkc-jump.rb

#!/usr/koeki/bin/ruby
# -*- coding: utf-8 -*-
require 'tk'
require 'tkextlib/tkimg/jpeg'
require 'tkextlib/tkimg/png'
require 'open-uri'

class Jump
  def initialize(width = 600, height = 400)
    dir='http://www.yatex.org/lect/ruby/'
    imgsrc = {'data'=>Tk.BinaryString(open(dir+'star.png'){|s| s.read})}
    @star = TkPhotoImage.new(imgsrc)
    imgsrc['data'] = Tk.BinaryString(open(dir+'shell.png'){|s| s.read})
    @kame = TkPhotoImage.new(imgsrc)
    imgsrc['data'] = Tk.BinaryString(open(dir+'shell2.png'){|s| s.read})
    @kame2 = TkPhotoImage.new(imgsrc)
    imgsrc['data'] = Tk.BinaryString(open(dir+'shell3.png'){|s| s.read})
    @kame3 = TkPhotoImage.new(imgsrc)
    @manimg = [@kame, @kame2, @kame3]
    @getstar = nil
    @st_x, @st_y = 400, 220
    @job = nil
    f = TkFrame.new()
    @bt0 = TkButton.new(f, "text"=>"Start(TkAfter)").pack('side'=>'left')
    @bt1 = TkButton.new(f, "text"=>"Start(sleep)").pack('side'=>'left')
    @bt2 = TkButton.new(f, "text"=>"QUIT",
                        'command'=>"exit").pack('side'=>'left')
    f.pack
    @stage = TkCanvas.new('width'=>width, 'height'=>height) {
      bind('1', proc{|x, y| printf("(%d,%d)\n", x, y)}, "%x %y")
    }.pack 
    @i_x, @i_y = 20, height-20
    @g_x, @g_y = width-@kame.width/2, @i_y
    @tgt = TkcImage.new(@stage, @st_x, @st_y, 'image'=>@star)
    @man = TkcImage.new(@stage, @i_x, @i_y, 'image'=>@manimg[0])
    @unit_x, @unit_y = 10, 20
    @jmax = 6; @jnow = 0
    @wait = 20 #msec
    @tx = TkcText.new(@stage, 10, 10,
                      "anchor"=>:nw, "text"=>"Start",
                      "font"=>"Times 24")
    @tx.bind('1', proc{reset(); @move.start()})
    TkRoot.bind('space', proc{jump()})
    TkRoot.bind('q', proc{exit()})
    # TkAfterとsleep,それぞれでアニメーションを行なう
    @move = TkAfter.new(@wait, -1, proc {mv()})
    TkRoot.bind('x', proc{reset(); @move.start})
    @bt0.command = proc {reset(); @move.start}
    @bt1.command = proc {reset(); mvBySleep()}
  end
  def jump()
    @jnow = @jmax
  end
  def reset()
    @getstar = nil
    @tx.text = "Jump!"
    @x = @i_x
  end
  def mv()
    if @x < @g_x                # 右端に着く前は描画処理
      @y = @g_y - ((@jmax-@jnow)*@jnow/2)*@unit_y
      @man.coords(@x, @y)
      step = (@x-@i_x)/@unit_x/6%3
      @man.image = @manimg[step]
      if @stage.find('overlapping', *@man.bbox)[0] == @tgt
        @getstar = true
      end
      @x += @unit_x
      if @jnow > 0 then
        @jnow -= 1              # ジャンプ中の処理
      end
    else                        # 右端に着いたら終了
      @move.stop
      @tx.text = "You " + (@getstar ? "WIN!" : "Lose...")
    end
  end
  def mvBySleep()		# 同じ処理をsleepで記述
    @x = @i_x
    while @x < @g_x
      @y = @g_y - ((@jmax-@jnow)*@jnow/2)*@unit_y
      @man.coords(@x, @y)
      step = (@x-@i_x)/@unit_x/6%3
      @man.image = @manimg[step]
      if @stage.find('overlapping', *@man.bbox)[0] == @tgt
        @getstar = true
      end
      @x += @unit_x
      @jnow -= 1 if @jnow > 0
      @stage.update		# canvasをupdateしないと画面に出ない!!
      sleep(@wait/1000.0)
    end
    @tx.text = "You " + (@getstar ? "WIN!" : "Lose...")
  end
end
k = Jump.new
Tk.mainloop

状態遷移の実現

tk-widgets.rb

リンク集

Ruby/tkの情報を得るために有用なURLを示す。


目次
[Arc]
start, extent
fill, outline
style = pieslice|chord|arc
tags
width

[bitmap]
anchor, foreground, background, bitmap, tags

[image]
anchor, image, tags

[line]
arrow = first|last|both|none
arrowshape = 
dash = 
capstyle = 
fill
joinstyle bevel|miter|round
smooth = true|false
splisteps = Number
stipple = Bitmap
tags, width

[rectangle]
fill, outline, stipple, tags, width
メモ
obj.configinfo で各種属性一覧
----------------------------------------------------------------------
http://www27.cs.kobe-u.ac.jp/~masa-n/misc/cmc/perltk/basic/grid.html
■ 相対配置

-row, -column で格子点の絶対値を指定する以外に,相対的に配置する方法も用
 意されています.行ごとに互いの縦横の関係を並べるものです.

$but00->grid($but01,    '-', $but03, -sticky=>'news');
$but10->grid(   '^', $but12,    'x', -sticky=>'news');
----------------------------------------------------------------------
packの属性
  :side		top, left, right, bottom
  :fill		x, y
----------------------------------------------------------------------
menu
  tearoff メニューを切り離せるか
----------------------------------------------------------------------
TkListbox の選択モード
  :selectmode	single, browse, multiple, extended
----------------------------------------------------------------------
text/TkEntry
   obj.focus	ここにフォーカス
   obj.cursor	カーソル位置
   obj.selection_range(0, 'end')
   sel.first, sel.last
----------------------------------------------------------------------
Checkbutton
  variable	変数
  text		説明文
  onvalue	on値
  offvalue	off値
  select/deselect
----------------------------------------------------------------------
Radiobutton
  variable	変数
  text		説明文
  value		それが選ばれたときの変数値
----------------------------------------------------------------------
Panedwindow
  add(obj, obj, ...) でペイン分けされたオブジェクト
----------------------------------------------------------------------
イベントシーケンス
	modifier-modifier-type-detail
  Activate, Deactivate
  Button(ButtonPress), ButtonRelease
  Circulate	ウィンドウの重なりが変わった
  Colormap	カラーマップが変わった
  Configure	ウィンドウの位置・サイズが変わった
  Destroy	強制終了された
  Enter		マウスポインタが入った
  Expose	ウィンドウが配置・表示された
  FocusIn, FocusOut
  Gravite	親ウィンドウのサイズ変更で自分が変化した
  Key(KeyPress), KeyRelease
  Leave		マウスが出た
  Map		ウィンドウがスクリーンに配置された
  Motion	ウィンドウ内部でのマウス移動
  Property	ウィンドウ属性が変わった
  Reparent
  Unmap		ウィンドウ最小化
  Visibility	表示属性変化
イベントキーワード
  %%	%自身
  %#	イベントのシリアルナンバー
  %a	above
  %b	ボタン番号
  %c	oountフィールド
  %h	height
  %w	width
  %k	keycode
  %s	state (VisibilityUnobscured等)
  %t	time
  %x, %X
  %y, %Y
  %A	ascii code
  %K	keysym
  %N	keysym number
  %T	type
  %W	path