BWidget

 BWidgetは、Tkの基本的なウィジェット(GUI部品) に加えて、さらに複雑で高機能なウィジェットを数多く追加する Tkの拡張ライブラリです。 このようなTkのウィジェットセットは他にも Tix や [incr Widget] がありますが、BWidgetがそれらと異なる点は、 C言語を使わず、全てTcl言語で書かれている点です。 その兼ね合いも含めて特徴を幾つか挙げると、

  • コンパイルが不要で、インストールが簡単。
  • Tclで書かれているので、プラットフォーム可搬性が高い。
  • Tixほど複雑なウィジェットは提供されていないので、「器用貧乏」になるおそれもある。
  • 少し遅い。
ちょっと気になる点は3番目ですが、 別にBWidgetとTixなどさらに高機能なウィジェットセットを混ぜて使っても問題はありませんし、 Tkになくて残念なウィジェットの大半はカバーされているので、 これだけでもBWidgetを活用する余地は十分あると言えます。 臨・機・応・変の4大精神が最も大事だということですね。(なんじゃ、そりは) ともあれ、そのTkになくてこのBWidgetでフォローされるウィジェットのうち、 特に目立つものをご紹介していきたいと思います。


ダウンロードとインストール

 BWidgetはTcl/Tkの本体処理系と同じく、 オープンソースのコミュニティで開発されています。 Tcl/Tk本体と近い関係にある Standard Tcl Library(tcllib) の開発プロジェクトの一部となっていて、 tcllibの開発ページからBWidgetも無償でダウンロードできます。
 インストールはごく簡単で、入手したアーカイブを Tcl/Tkをインストールしたときのライブラリのディレクトリで展開するだけです。 アーカイブを展開すると最上位に BWidget-1.7というディレクトリができるので、 UNIXの場合は /usr/local/lib/BWidget-1.7 または /usr/local/lib/tk8.4/BWidget-1.7 となる位置に、Windowsの場合は C:\Program Files\Tcl\lib\BWidget-1.7 となる位置に置けばOKです(パスはTcl/Tkのインストール位置に応じて適当に読み替えてください)。 wishを起動して、

% package require BWidget
と入力して、バージョン番号が返ってくればインストールは完了です。


状態が指定できるエントリ

BWidgetのエントリ(Entry)は、Tkのエントリ(entry) の強化版です。何が強化版かというとちょっと地味ですが、 入力内容をグレイアウトして編集不能状態にさせることができる機能が備わっています。

#! /usr/local/bin/wish
package require BWidget

Label .la -text "名前を入力してください:"
Entry .e -width 30
button .b -text OK -command ok

proc ok {} {
    .e configure -state disabled
    .la configure -text "フハハハハ、[.e get]よ、もはや編集できまい!!"
    .b configure -text 終了 -command exit
}
foreach e {la e b} {pack .$e -side left}
# end.


 
screetshot
うるさいよ
 

Entryウィジェットの使い方はentryウィジェットの完全上位互換です。 エントリを編集不能にするには configure で -state disabled、元に戻すには -state normal を実行します。 ちなみに LabelというBWidgetのウィジェットもlabelの完全上位互換です。


コンボボックス

コンボボックス (ComboBox) は複数の項目の中からひとつをドロップダウンで選択できるほか、 エントリの部分を直接キー入力で編集することもできるウィジェットです。

#! /usr/local/bin/wish
package require BWidget

set a {はまち まぐろ かにサラダ いわし びんとろ あじ ひらめ たい}
ComboBox .cboa -textvariable neta -values $a -editable 1
.cboa setvalue @0
pack .cboa -side top
pack [button .cmda -text 注文 -command "ok .cboa"] -side top -anc e
proc ok cbo {
  set index [$cbo getvalue]
  tk_messageBox -message "カウンターのお客さん[$cbo cget -text]いっちょう!!"
}
# end.

screetshot

ComboBoxウィジェットの使い方ですが、 選択できる項目は -values オプションでリストで指定するところが Tkのlistboxウィジェットとの大きな違いです。 -editable を1にするとキー編集可能なコンボボックスですが、 -editable を0にすると編集できない単なるオプションメニュー (ドロップダウンリストボックス)になります。
現在選択されている項目の文字列(編集可能な場合はその文字列) は、-textvariableを指定すればその変数にセットされる他、 getvalueサブコマンドで取得することもできます。


ラベルフレームとタイトルフレーム

ラベルフレームとタイトルフレームは互いによく似ています。 ラベルフレームは、フレームにラベルがついたものです。(おーい) いや、その、ほんとにそうなので怒らずに聞いて頂きたい。 しかし、タイトルフレームの方はチョト違う (チョトカライ?) なんと、フレームにタイトルがついたものです (コロス)
…ラベルとフレームをそのまま並べても別にあまり違いがないラベルフレームより、 タイトルフレームの方が見栄え的にはいい感じです。 が、まずはラベルフレームから。

#!/usr/local/bin/wish
package require BWidget

LabelFrame .lf -bd 2 -relief ridge -text "辛さ" -side top
set f [.lf getframe]
foreach e {甘口 中辛 辛口} {
    pack [radiobutton $f.rad$e -text $e -value $e -variable karasa]\
  -side top -anc w
}
set karasa 中辛
pack .lf -padx 10 -pady 10
pack [button .b -text 決定 -command ok]
proc ok {} { global karasa; tk_messageBox -message "${karasa}ですね。" }
# end.

screetshot screetshot
ラベルフレーム(左)とタイトルフレーム(右)

LabelFrameウィジェットのオプションの多くは Tkのframeのそれらを継承したものです。 -text でラベルの文字列を、-side でラベルのフレームに対する位置を指定します。 (普通 -side はtop以外の指定はしないと思いますが) 注意点は、LabelFrameの上に子ウィジェットを載せたいときには、上のように、 getframeサブコマンドを使って Tkの「本物の」frameウィジェットを取り出し、その上に載せる必要があることです。

#!/usr/local/bin/wish
package require BWidget

TitleFrame .tf -text "辛さ" -side right
set f [.tf getframe]
foreach e {甘口 中辛 辛口} {
    pack [radiobutton $f.rad$e -text $e -value $e -variable karasa]\
  -side top -anc w
}
set karasa 中辛
pack .tf -padx 10 -pady 10
pack [button .b -text 決定 -command ok]
proc ok {} { global karasa; tk_messageBox -message "${karasa}ですね。" }
# end.

さて一方の TitleFrame ですが、サンプルのコードはほとんど同じですね。 TitleFrame の -side オプションは、今度は left、center、right から選ぶようになっています。これすなわち、 ラベルをフレームの上辺の枠線のどの位置に置くか、ということです。 普通の脳波帯の持ち主なら間違いなく left を指定すると思いますが、 天邪鬼精神を発揮して right を指定するとこんな表示になります。


プログレスバー

プログレスバー (ProgressBar) は、インストールなどの作業の進み具合を表す、 横向きのバーのことです。というのが固定観念というもの! BWidgetのプログレスバーには -orient というオプションがあって、 マニュアルを見た限りではここに horizontal か vertical を指定できます。つーか、 縦向きのプログレスバー??(シュールだなあ) と思ったら案の定、縦向きにできることはできますが、 バーが伸びていかないのでダメっす。 やっぱりプログレスバーは横向きで使いましょう。 BWidgetの神様もそうおっしゃっておいでですわよ。

#!/usr/local/bin/wish
package require BWidget

ProgressBar .pb -relief ridge -maximum 100 -variable pbvalue \
  -type normal -orient horizontal -width 120 -height 15 \
  -fg midnightblue -troughcolor #c0c0ff
button .b1 -text 開始 -command startTimer
button .b2 -text 停止 -command stopTimer -state disabled
set activated 0
set pbvalue 0
proc startTimer {} {
    global pbvalue activated
    set activated 1
    .b2 configure -state normal
    .b1 configure -state disabled
    if {$pbvalue >= [.pb cget -maximum]} {set pbvalue 0}
    after 100 progress
}
proc stopTimer {} {
    global activated
    set activated 0
    .b1 configure -state normal
    .b2 configure -state disabled
}
proc progress {} {
    global activated pbvalue
    if {$pbvalue < [.pb cget -maximum] && $activated} {
        incr pbvalue 5; after 100 progress
    } else {
        stopTimer
    }
}
pack .pb -side top -pady 5
pack .b2 -side right -padx 5 -pady 5
pack .b1 -side right -padx 5 -pady 5
# end.

screetshot

プログレスバーの進み具合は0から -maximum オプションで指定した数値(正の数) までとなっています。 現在の値は -variable オプションで指定した変数に直接セットできます。


ノートブック

ノートブックという名前は他のGUIツールキットでも使われたり使われなかったりするのですが、 この呼び名、どこから始まったのかしら? 見た目がファイリングノートの区切りに使う色紙に似ているところから来ていると思いますが、 QtやPowerBuilderでいうところの「タブウィジェット」 という呼び名のほうがむしろ見たまんまのそれが特徴としてもつ造形をよりよく表現しているとは言えますまいか。 これすなわち、タブと呼ばれる文字のついた出っ張りをつつくと、 そのタブがつけられたところのパネルが最上面に現れて操作できるというウィジェットです。 BWidgetのノートブックはよく出来ていて、 横幅で表示できる幅を越えた数のタブを追加しても、タブをスクロールさせることができます。

#! /usr/local/bin/wish
package require BWidget
NoteBook .nb -side top -width 300 -font snk14

array set apages {
  program "ファイルとリソース" connection "接続" \
  logging "ログと運用" misc "その他"
}
foreach e {program connection logging misc} {
    .nb insert end $e -text $apages($e)
}
set p [.nb getframe "program"]
label $p.la -text "ライブラリの検索パス:"
frame $p.fa
entry $p.fa.ea -width 40
button $p.fa.ba -text 参照...
foreach e {ea ba} {pack $p.fa.$e -side left}

label $p.lb -text "アイコンビットマップ:"
frame $p.fb
entry $p.fb.eb -width 40
button $p.fb.bb -text 参照...
foreach e {eb bb} {pack $p.fb.$e -side left}
foreach e {la fa lb fb} {pack $p.$e -side top -anc w}

pack .nb -side top -padx 10 -pady 10
pack [button .b -text 終了 -command exit] -side top -anc e -pady 5
.nb raise "program"
# end.

screetshot
ノートブック

NoteBookコマンドのオプションもおおむねTk一般のオプションから類推が効くものばかりです。 -sideというのはパネルに対するタブの位置ですが、 普通は top を指定すると思います。 それで、NoteBookウィジェットを作ったら今度は各タブとページを追加していきます。 これは

.nb insert end "general"    -text "全般"
.nb insert end "debug"      -text "デバッグ"
.nb insert end "connection" -text "接続"
.nb insert end "extension"  -text "拡張機能"

例えばこんな感じで、「ページ識別子」となる文字列と、 タブに表示する文字列を指定します。各ページにウィジェットを載せるには、 これまたgetframeサブコマンドを使う必要があります。

set frame [.nb getframe "connection"]
checkbutton $frame.c1 -text "接続ぷーリングを使用する" -variable poo
label $frame.la -text "最大同時接続数:"
entry $frame.ea -width 10

プログラム中からどれかのページが最前面に出るようにさせたい場合は、 raiseというサブコマンドを使えばOKです。
    .nb raise "connection"


フォント選択ダイアログ

BWidgetには幾つか出来合いの便利で集積的なウィジェットがあります。 この SelectFont もそのひとつで、 簡単にフォントをユーザに尋ねることができます。 下の例のように日本語のフォントを選ぶことも可能です。 但し、TrueTypeフォントでないUNIX環境では、 フォントファミリは一般に日本語専用の値ではないので、 あくまで近似的なフォントが使われるという位置づけになります。

#! /usr/local/bin/wish
package require BWidget

label .la -text ハローワールド
button .b -command {
    set fontName [showFontDialog]
    if {"$fontName" != ""} { .la configure -font $fontName }
} -text フォント...
pack .la .b -side left
proc showFontDialog {} {
    SelectFont .sf \
  -sampletext "新しいことを教えられる教師など何処にもいない
彼らは我々が常に知っていた事柄を
思い出す手助けをすることしかできない" \
  -title "Font Selection" -type dialog
}
# end.

screetshot
ニュー・センチュリー・スクールブック

BWidgetのSelectFontは、別にダイアログというわけではなく、 ウィンドウに載せるウィジェットとして作ることも可能です。 しかし一般的な使い方はやはり選択ダイアログの方だと思うので、 ここではそのようなサンプルにしました。 というか、Tkのtk_getOpenFileなどと同じく、 SelectFontというコマンドを使うとダイアログが出て来て、 ユーザが選んだフォントが文字列として返る (キャンセルすると空文字列が返る) というだけの簡単便利な使い方といえましょう。


ドラッグ&ドロップ

BWidgetを使うと、tkDNDと同じように、ドラッグ&ドロップが実現できます。 tkDNDと違って、 Win32やXウィンドウシステムのドラッグ&ドロップ機構を使っているわけではないため、 他のアプリケーションとの間でドラッグ&ドロップすることはできません。 ということで、tkDNDとは綺麗に住み分けがされています。 ただし、BWidgetウィジェット同士限定ではなく、 Tkウィジェットとの間でも行うことができるので、 用途によっては十分実用的な操作を提供できると思います。

#! /usr/local/bin/wish
package require BWidget

Label .la -text "文字を打ってキャンバスにドロップして下さい"
Entry .ea -width 40
DragSite::register .ea -dragevent 1 \
  -dragendcmd dragEnd -draginitcmd dragInit
canvas .ca -bg white -width 200 -height 100
bind .ca <Button-3> exit
DropSite::register .ca -dropcmd dropped -dropovercmd dropOver \
 -droptypes {text/plain {copy "" move "" link ""}}

# ドラッグされ始めたときに呼ばれます。
proc dragInit {source rx ry data} {
    set entrytext [$source get]
    return [list text/plain copy $entrytext]
}

# ドラッグしたまま、ドロップ可能なウィジェットの上にマウスカーソルが
# 来たら呼ばれます。
proc dropOver {target source event rx ry oper type data} {
    return 3
}

# ドロップされると呼ばれます。
proc dropped {target source rx ry oper type data} {
    set cx [expr $rx - [winfo x $target]]
    set cy [expr $ry - [winfo y $target]]
    $target create text $cx $cy -anc center -text $data -fill "#700040" \
      -font [$source cget -font]
}

# ドロップされてもされなくても、ドラッグが終わると呼ばれます。
proc dragEnd {source target oper type data result} {
    puts "DRAGEND:$source:$target:$oper:$type:$data:$result"
}

foreach e {la ea ca} { pack .$e -side top -padx 10 -pady 5 }
# end.

screetshot
上のエントリに入力した文字をキャンバスにドロップできます

最も汎用的な方法は、 DragSite::registerとDropSite::registerを使って、 ドラッグ対象とドロップ対象を指定する方法です。 上のサンプルで特に注意が必要と思われるのはDropSiteの -dropovercmd の値です。 このオプションで指定するスクリプトは通常プロシージャ (上のdropOver) で、それはDropSiteで指定したウィジェットの範囲内に、 DragSiteからドラッグしてきたマウスカーソルが入ったときに呼ばれます。 このプロシージャは、 条件に応じて0〜3のいずれかを返すように定義しなければいけません。

  • 0…DropSiteはこのドラッグからのドロップを拒否する。 同じドラッグで再度範囲内に入っても dropovercmd は二度と呼ばれない。
  • 1…DropSiteはこのドラッグからのドロップを受け付ける。 同じドラッグで再度範囲内に入っても dropovercmd は二度と呼ばれない。
  • 2…DropSiteはこのドラッグからのドロップを拒否する。 同じドラッグで再度範囲内に入ったらまた dropovercmd が実行される。
  • 3…DropSiteはこのドラッグからのドロップを受け付ける。 同じドラッグで再度範囲内に入ったらまた dropovercmd が実行される。
というわけなので、ここではドロップを受け付けるために3を返すプロシージャを定義しています。

コード中の詞はEnigma「Enigma III」(対訳:野村伸昭氏)から引用させて頂きました。

拡張レビュー分室 top
(first uploaded 2001/11/10 last updated 2004/03/14, MISUMI URANO)