Tcl/Tkの窓を出そう

 さて、ほんとに最初にTcl/Tkをつついてみようという方のための簡単なとっつきぶぶんです。
 Tcl/Tkで開発(というのも大げさなんですが)するのに必要なものは、 次の2つだけです。

  • Tcl/Tk処理系。 ダウンロードしてきましょう。
  • テキストエディタ。Windows ならマイクロソフト メモ帳でもいいですし(ほんとなのです) お気に入りのエディタでもよいです。 UNIXならXEmacs、Mule、viなどが最近は最初からインストールされるでしょう。

 Tcl/Tk処理系のインストールについては、 こちらのページでご紹介しています。 インストールできたら、さっそく何か書いてみましょう。 いちばん簡単なのはこんな感じのプログラムです。

#! /usr/local/bin/wish
label .la -text 出たよ
button .c -text 終了 -command exit
pack .la .c -side top

 1行目はUNIXの場合だけ必要で、 インストールしたTcl/Tkの解釈実行系(wish)のパスを書きます。 で、Tcl/Tkの基本は、上のようにラベル(label)やボタン (button) などのウィジェット(GUI部品)を作っては載せ(pack)、 作っては載せ、という手順でウィンドウを作っていくということです。

 とりあえず、「hello.tcl」と名づけて保存し、実行してみましょう。 Windowsでは、.tcl という名前をつけて保存すると エクスプローラで見るとアイコンがTcl/Tkのものになっているはずなので、 それをダブルクリックすると起動できます。
UNIXでは、chmodコマンドで実行可能 (chmod 0755 hello.tcl)とし、シェルから「./hello.tcl」 と打てば起動できます。

 このようにウィンドウが出ますね。しかしここで喜ばないで、 ちょっと普通と違うことに気づくはずです。 Tcl/Tkのプログラムは(*1)、プログラムの最後まで実行したとき、 そのままの状態で停止します。他の言語のプログラムだと、 文を最後まで実行するとプログラムが終了してしまうので、 このようにウィンドウを出したままにするには特別な処理を書かないといけないのですが、 スクリプト言語であるTcl/Tkは自動的にその処理をやってくれています。 で、ほんとに終了させたいと思ったら、 Tcl言語の「exit」というコマンドを使えばOKです。 上の例だともうなんとなくお分かりと思いますが、 「終了」と書かれたボタンを押すとプログラムが終了します。

button .c -text 終了 -command exit
この「-command」のところで、このボタンが押されたら何をするかを書くわけで、 ここではその何かとして「exit」と書いています。 つまり押されたら終了する、ということですね。

 さて、このように button には -command という「オプション」があるわけですが、 label や button などのウィジェットを作るコマンドでは、 各部品の色や形やその他さまざまなことを、 オプションの形で追加していくことができます。

#! /usr/local/bin/wish
label .la -text 出たよ -foreground red \
  -font {Helvetica 16 normal}
button .c -text 終了 -background midnightblue \
  -foreground white -command exit
pack .la .c -side top

 どうです。「出たよ」の文字が赤く、フォントも大きくなっているはずです。 こんな感じで見た目をアレンジしていくことができます。

(*1)Tcl/Tkのプログラムではなく、Tclだけのプログラムは Perl などと同じように、 文を最後まで実行すると終わります。


親子関係、プロシージャ、イベント

 さあ短いながらもせわしない。 おやっさんページのつごうでどんどん進めちゃいますよ。 特にTcl言語の文法については書籍をあたるか (Tcl/Tkの解説本は少数精鋭で良書が多いのです)、 英語が苦にならなければ付属のマニュアルで十分いけましょう。

 今度はですね、 ウィジェットの上に別のウィジェットを載せようというのと、 すぐ終了するだけじゃつまらん、 というので少し書き加えてみました、みたいな。

#! /usr/local/bin/wish
label .la -text 出たよ -foreground red \
  -font {Helvetica 16 normal}
frame .fa -relief groove -borderwidth 3
button .fa.c1 -text 挨拶 -command aisatsu
button .fa.c2 -text 終了 -command exit
pack .fa.c1 .fa.c2 -side left
pack .la .fa -side top

proc aisatsu {} {
    set hour [clock format [clock seconds] -format %H]
    if {$hour >= 5 && $hour <= 10} {
	set ai {おはようさん}
    } elseif {$hour <= 18} {
	set ai {ちわっす}
    } elseif {$hour <= 23} {
	set ai {こんばんわ}
    } else {
	set ai {ねむくないですか}
    }
    tk_messageBox -message $ai
}

 いくつかのウィジェットをまとめて配置したいというときには、 frameというウィジェットを下に敷いて、 その上にボタンなりリストボックスなりを載せていきます。 で、「下に敷く」とゆーのはなんやねんと。 それをどこで表現するかというと、ウィジェットの名前であります。 ウィジェットの名前は、ファイルのパスみたいに 「.名前.名前.名前…」という形をしていて、 「.fa.c2」という名前のウィジェットは、「.fa」という名前のウィジェットに載る、 というようになっています。 ちなみに、Tcl/Tkを実行すると最初に出てくるウィンドウ自体のウィジェットの名前が 「」(空文字)で、最初に出てきた「.la」や「.c」は、 これに直接載っているのでそういう名前でいいわけです。

 で、ここではボタンをもうひとつ作っているわけですが、 押されたら何をしようというので、とてもありがちーなんですが、 現在の時刻に合わせて挨拶してみようという。 その場合は、Tcl言語のプロシージャの形にしておいて、 ボタンの -command で上の 「aisatsu」 のようにそのプロシージャを指定すれば実行してもらえます。 Tcl/Tkで簡単に例えば「ディスクがいっぱいです」 「ファイル xxx は既に存在します。上書きしますか(y/n)?」 「今日か二日酔いで頭が痛いけど?中止(A)。再思考(R)。無視(I)」 のようなメッセージを出したり、何か確認をしたり、 ということに「tk_messageBox」が簡単に使えます。


もうすこしいろいろなことをしてみよう

 Tcl/Tkにはbutton、label、frame以外にもたくさんの種類のウィジェットがあります。 今度はそのうちの2つ、エントリ(entry) とリストボックス(listbox)を作ります。 また、リストボックスにはつきものの、 スクロールバー(scrollbar)もついでに使ってみます。

#! /usr/local/bin/wish

label .lname -text おなまえ: -anc e -font {Helvetica 12 normal}
entry .ename -width 30
label .lblo -text 血液型: -anc e -font {Helvetica 12 normal}
frame .fblo
set lb [listbox .fblo.lsta -selectmode single \
  -yscrollcommand ".fblo.scrv set" -width 15]
foreach e {A型 B型 AB型 O型 不明} {
    $lb insert end $e
}
$lb selection set 0
scrollbar .fblo.scrv -orient vertical -command ".fblo.lsta yview"
pack .fblo.lsta .fblo.scrv -side left -fill both

frame .fb -relief groove -borderwidth 3
button .fb.c1 -text 挨拶 -font {Helvetica 12 normal} \
  -command "aisatsu .fblo.lsta .ename"
button .fb.c2 -text 終了 -font {Helvetica 12 normal} -command exit
bind .fb.c1 <Enter> {
    set name [.ename get]
    if {"$name" == ""} {
        tk_messageBox -message "ちょっと待て!名前!名前!"
    }
}
pack .fb.c1 .fb.c2 -side left -fill both

. configure -width 300 -height 100
place .lname -relx 0.0 -rely 0.0 -relw 0.4 -relh 0.2
place .ename -relx 0.4 -rely 0.0 -relw 0.6 -relh 0.2
place .lblo  -relx 0.2 -rely 0.2 -relw 0.4 -relh 0.2
place .fblo  -relx 0.6 -rely 0.2 -relw 0.4 -relh 0.8
place .fb    -relx 0.2 -rely 0.75 -relw 0.3 -relh 0.25

proc aisatsu {listbox entry} {
    set hour [clock format [clock seconds] -format %H]
    if {$hour >= 5 && $hour <= 10} {
        set ai {おはようさん}
    } elseif {$hour <= 18} {
        set ai {ちわっす}
    } elseif {$hour <= 23} {
        set ai {こんばんわ}
    } else {
        set ai {ねむくないですか}
    }
    tk_messageBox -message "$ai、[$entry get]さん。"
    set btype [$listbox get [$listbox curselection]]
    if {"$btype" != "不明"} {
        tk_messageBox -message "${btype}の人は今日はラッキーです。"
    }
}

 エントリはとても簡単に作れます。

entry .ename -width 30
 -widthは文字数で幅を指定しますが、省略すると適当な大きさになります。

 リストボックスとスクロールバーは、最初はちょっと理解できないかもしれません。 この書き方でどうしてこう動くのかは、かなり熟達してもわかりにくいので、 こんな感じで作るものだ、という程度でいいでしょう。 私も「yview」と「set」の関係は厳密にはよくわかっていませんが、 これでちゃんとスクロールするんだから、いいか、という程度です。

set lb [listbox .fblo.lsta -selectmode single \
  -yscrollcommand ".fblo.scrv set" -width 15]
foreach e {A型 B型 AB型 O型 不明} {
    $lb insert end $e
}
$lb selection set 0
scrollbar .fblo.scrv -orient vertical -command ".fblo.lsta yview"
pack .fblo.lsta .fblo.scrv -side left -fill both

 さて、bindというコマンドが出てきますが、 これ、本当はかなり難しいのです。 なのでここではほんのさわりだけで通り過ぎます(乱暴)。 ボタンの -commandみたいに、ウィジェットに対して何か操作がされると、 そのときにする処理を登録するコマンドですが、 -command と違って、「ボタンが押される」以外にも、 「画面に表示される」「ボタンが押されたまま動く」「キーが入力される」 「マウスカーソルがウィジェットに合う」 などいろいろな「タイミング」(=イベントと呼びます) が発生した瞬間に何かの処理(=イベントハンドラと呼びます) をさせることができます。

bind .fb.c1 <Enter> {
    set name [.ename get]
    if {"$name" == ""} {
        tk_messageBox -message "ちょっと待て!名前!名前!"
    }
}
これは、ボタン「.fb.c1」にマウスカーソルが合った(Enter) 瞬間に、エントリ「.ename」の入力内容をチェック(.ename get) して、空欄だったら警告する、というものです。 なんかアプリケーションとしての体裁が整ってきたのか、 それともだんだんこうるさくなってきたのかよくわかりませんが。 この Enter のほかにも
  • 画面に出る(Map)
  • マウスの右ボタンが押される(Button-3)
  • Ctrlキーを押したままクリックされる(Control-Button-1)
  • キー「Q」が入力される(Key-q)
  • マウスカーソルがはずれる(Leave)
など多彩なイベントが用意されています(博覧会ですか)。

 さて、部品を載せるのに、packコマンド以外にも 「place」「grid」 というコマンドもあります。 place は、子ウィジェットを親ウィジェットのどこに配置するかを、 ピクセル数による絶対座標か、親ウィジェットのサイズに対する比率(小数) で指定して載せることができます。 比率で指定すると、親ウィジェットがマウスで引っ張られて伸び縮みしたときに、 子ウィジェットもそれに応じて拡縮される(ほんとにいたれりつくせりであります) ので、普通は比率で指定すればよいでしょう。

. configure -width 300 -height 100
place .lname -relx 0.0 -rely 0.0 -relw 0.4 -relh 0.2
place .ename -relx 0.4 -rely 0.0 -relw 0.6 -relh 0.2

こんな感じで、「-relx」「-rely」で左上の位置を、「-relw」「-relh」 で幅と高さを指定できます。 実際に数字を変えながら試してみて、最適な位置を見つけましょう。 Visual Basic などの統合開発環境では、 このあたりがマウス操作で視覚的に確認しながら操作できる利点があります。 Tcl/Tk もものすごく複雑な画面を作りたくなったら、 傍らに方眼紙でも用意して「なんちゃって統合開発環境」 で対抗しましょう。 あるいは、Tcl/Tk 8.0で追加されたgridコマンドの方が便利なので、 gridコマンドを使うようにした方がよいかもしれません。

 リストボックスの、現在の選択状態を知るには、

  1. 上から何番目の項目が選ばれているかを知る(curselection)
  2. その項目に書かれているテキストを取得する(get)
の2段階の処理になります。

    set btype [$listbox get [$listbox curselection]]
    if {"$btype" != "不明"} {
        tk_messageBox -message "${btype}の人は今日はラッキーです。"
    }

 

ここでは、「不明」以外の血液型を選んだ人に「ラッキーです」 などと無責任なことを言っています。 そうだ、いっそのこと占いでもしてみましょうか。はやりの、占い。

どうです。

やねうらの top
(first uploaded 2000/03/24 last updated 2004/04/03, MISUMI URANO - KENICHI USHIRODANI)