テキストウィジェットはキャンバスウィジェットと同様に、テキストだけではなくほかのウィジェットも表示することができます。これを「埋め込みウィンドウ」といいます。埋め込みウィンドウ用のメソッドを次に示します。
windowCreate メソッド で使用するオプションを下表に示します。
-window | ウィジェット名 |
-create | ウィジェットを生成するコマンドを指定(-window を指定しない場合のみ有効) |
-align | 上下方向の揃え指定 (baseline, top, bottom, center) |
-stretch | 上下方向の引き延ばし |
-padx,-pady | スペースの指定 |
それでは、テキストにイメージを挿入できるように、前回作成したプログラムを改造してみましょう。ラベルにイメージを貼り付けて、そのラベルをテキストウィジェットに埋め込みます。画像ファイルはメニュー Image で選択し、カーソル位置にその画像を挿入します。
メニュー Image の設定は簡単です。次の 1 行を追加するだけです。
my $m6 = $m->command(-label => 'Image', -under => 0, -command => \&insert_image);
ラベル (画像) を挿入する関数 insert_image は次のようになります。
リスト : ラベル (画像) の挿入 # ファイルセレクト my $fs = $top->FileSelect( -directory => '.', -filter => '*\.(txt|pl)'); my $fs_image = $top->FileSelect(-directory => '.', -filter => '*\.(gif|ppm)'); # 画像を選んで挿入する sub insert_image { my $image_name = $fs_image->Show(); if ($image_name) { my $image = $top->Photo(-file => $image_name); my $label = $top->Label(-image => $image, -relief => 'raised', -borderwidth => 4); $t0->windowCreate('insert', -window => $label, -align => 'baseline'); } }
ファイル選択用のダイアログのほかに、画像選択用のダイアログを用意します。$fs がファイル選択用ダイアログで、$fs_image が画像選択用ダイアログです。Photo メソッドでイメージを生成して、それを Label メソッドでラベルに貼り付けます。そして、そのラベルを windowCreate メソッドでテキストウィジェットに表示します。表示位置はマーク insert で指定できるので簡単です。
ウィジェットを挿入したあとで、テキストを編集することはもちろん可能です。ウィジェットの前に文字を挿入すればウィジェットは後ろへ移動しますし、文字を削除すれば前の方へ移動します。挿入したウィジェットは、文字と同じ操作で削除することができます。
ちなみに、テキストウィジェットに画像を表示するだけならば、画像専用のメソッドを使った方が簡単です。これを「埋め込み画像」といいます。埋め込み画像用のメソッドを次に示します。
imageCreate メソッド で使用するオプションを下表に示します。
-image | 表示する画像 |
-name | 画像を参照するための名前を付ける |
-align | 上下方向の揃え指定(baseline, top, bottom, center) |
-padx,-pady | スペースの指定 |
画像を挿入する関数 insert_image は、次のようになります。
リスト : 画像の挿入 sub insert_image { my $image_name = $fs_image->Show(); if ($image_name) { my $image = $top->Photo(-file => $image_name); $t0->imageCreate('insert', -image => $image, -align => 'baseline'); } }
Photo メソッドでイメージを生成して、それを imageCreate メソッドでテキストウィジェットに表示します。このように、ラベルウィジェットを生成しない分だけプログラムは簡単になります。
ところで、このままでは作成したテキストをセーブすることができません。テキストのほかに、タグやウィジェットなどの情報を別ファイルに書き出せば、設定したフォント、色、画像を保存することができます。これは、dump メソッドを使うことで実現できます。dump メソッドはウィジェット内の情報を出力するので、それを別ファイルに書き出せばいいわけです。
まあ、どうせ保存するならば HTML 形式に変換した方がよいかもしれません。今回は dump コマンドの説明は割愛しますが、興味のある方はヘルプを参照して、セーブ機能を追加してみてください。
リスト : Text Viewer (画像表示バージョン) use strict; use warnings; use utf8; use Tk; use Tk::FileSelect; # グローバル変数 my $font = 'Terminal'; my $size = 10; my $color = 'black'; # メインウィンドウ my $top = MainWindow->new(); $top->optionAdd('*font' => 'Terminal 10'); $top->title('Text Viewer'); # テキストウィジェット my $t0 = $top->Scrolled('Text', -scrollbars => 'se', -wrap => 'none', -font => ['Terminal', 10] )->pack(-expand => 1, -fill => 'both'); # ファイルセレクト my $fs = $top->FileSelect( -directory => '.', -filter => '*\.(txt|pl)'); my $fs_image = $top->FileSelect(-directory => '.', -filter => '*\.(gif|ppm)'); # タグの設定 foreach my $f ('Terminal', '游明朝', '游ゴシック'){ foreach my $s (10, 16, 24, 48) { $t0->tagConfigure("$f $s", -font => [$f, $s]); } } foreach my $c ('black', 'red', 'blue', 'green', 'yellow') { $t0->tagConfigure($c, -foreground => $c); } # 属性の変更 sub change_prop { my @r = $t0->tagRanges('sel'); if (@r > 0) { $t0->tagAdd("$font $size", $r[0], $r[1]); $t0->tagAdd($color, $r[0], $r[1]); } } # タグのクリア sub clear_tag { foreach my $n ($t0->tagNames('insert')){ my @r = $t0->tagPrevrange($n, 'insert'); $t0->tagRemove($n, $r[0], $r[1]); } } # ファイルを選んで表示する sub load_file { my $filename = $fs->Show(); if ($filename) { # 前のドキュメントを消去 $t0->delete('1.0', 'end'); # ファイルのリード open my $in, "<:utf8", "$filename" or die "Can't open $filename\n"; while (<$in>) { $t0->insert( 'end', $_ ); } close($in); $t0->focusForce(); } } # 画像を選んで挿入する sub insert_image { my $image_name = $fs_image->Show(); if ($image_name) { my $image = $top->Photo(-file => $image_name); my $label = $top->Label(-image => $image, -relief => 'raised', -borderwidth => 4); $t0->windowCreate('insert', -window => $label, -align => 'baseline'); } } # メニューの設定 my $m = $top->Menu(-type => 'menubar'); $top->configure(-menu => $m); my $m1 = $m->cascade(-label => 'File', -under => 0, -tearoff => 0); my $m2 = $m->cascade(-label => 'Font', -under => 0, -tearoff => 0); my $m3 = $m->cascade(-label => 'Size', -under => 0, -tearoff => 0); my $m4 = $m->cascade(-label => 'Color', -under => 0, -tearoff => 0); my $m5 = $m->command(-label => 'Clear', -under => 0, -command => \&clear_tag); my $m6 = $m->command(-label => 'Image', -under => 0, -command => \&insert_image); $m1->command(-label => 'Open', -under => 0, -command => \&load_file); $m1->separator; $m1->command(-label => 'Exit', -under => 0, -command => \&exit); # Font の設定 foreach my $i ('Terminal', '游明朝', '游ゴシック') { $m2->radiobutton(-label => $i, -variable => \$font, -value => $i); } # Size の設定 foreach my $i (10, 16, 24, 48) { $m3->radiobutton(-label => $i, -variable => \$size, -value => $i); } # Color の設定 foreach my $i ('black', 'red', 'blue', 'green', 'yellow') { $m4->radiobutton(-label => $i, -variable => \$color, -value => $i); } # バインドの設定 $t0->bind("<Control-Button-1>", \&change_prop); MainLoop();
ラベルフレームウィジェットはフレームウィジェットとラベルウィジェットを合わせたものです。見た目は枠付きのフレームで、枠の上に見出しラベルが表示されます。
ラベルフレームウィジェットは Labelframe メソッドで生成します。主なオプションを下表に示します。
-text | 見出しラベルに表示されるテキストを指定する |
-relief | 形状 (flat, raised, sunken, groove, ridge) を指定する |
-labelanchor | 見出しラベルの位置 (nw, n, ne, e, se, s, sw, w) を指定する |
-labelwidget | 見出しラベルのかわりにウィジェットを指定する |
見出しラベルに表示するテキストは -text で指定します。見出しラベルの位置は -labelanchor で指定します。デフォルトは nw (左上) です。形状は -relief で指定します。デフォルトは groove です。見出しラベルの部分はテキストだけではなく、オプション -lablewidget でウィジェットを指定することができます。
それでは、簡単な使用例を示しましょう。次のリストを見てください。
リスト : ラベルフレーム (1) use strict; use warnings; use Tk; my $top = MainWindow->new(); foreach my $r ('nw', 'n', 'ne', 'sw', 's', 'se') { my $f = $top->Labelframe(-text => 'label', -width => 80, -height => 60); $f->configure(-labelanchor => $r); $f->pack(-padx => 5, -pady => 5, -side => 'left'); } MainLoop();
-lableanchor を nw, n, ne, sw, s, se に変えてラベルフレームを表示ます。なお、M.Hiroi が使用している Tk モジュール (ver 804.034) では、Labelframe メソッドで -lableanchor を指定すると、-width, -height による指定が効かなくなります。このため、-labelanchor は configure メソッドで指定しました。pack() のオプション -padx と -pady は、ウィジェットの周囲に設定する余白の長さを指定します。これが大きいとウィジェットの間隔が開くことになります。それでは実行結果を示します。
ラベルフレーム(1)
見出しラベルの位置を変更しただけですが、見た目は随分とかわりますね。
次は -relief を変更してみましょう。5 つの形状をすべて表示します。
リスト : ラベルフレーム (2) use strict; use warnings; use Tk; my $top = MainWindow->new(); foreach my $r ('raised', 'sunken', 'flat', 'groove', 'ridge') { $top->Labelframe(-text => 'label', -width => 80, -height => 60, -relief => $r, -borderwidth => 4, -bg => 'gray')->pack(-padx => 5, -pady => 5, -side=> 'left'); } MainLoop();
実行結果は次のようになります。
ラベルフレーム(2)
枠の幅はフレームウィジェットと同様にオプション -borderwidth で変更することができます。たとえば -relief が raised の場合、枠の上辺と左辺を背景よりも明るい色で、枠の下辺と右辺を暗い色で描くことにより、フレームがウィンドウから出っ張っているように見えます。-borderwidth の値を増やすと、この幅が増えるのでフレームはより出っ張って見えるようになります。興味のある方は試してみてください。
今度はラベルフレームにラジオボタンとチェックボタンを配置してみましょう。次のリストを見てください。
リスト : ラベルフレーム (3) use strict; use warnings; use Tk; my $top = MainWindow->new(); my $v = 0; my $f0 = $top->Labelframe(-text => 'Group1'); my $f1 = $top->Labelframe(-text => 'Group2'); foreach my $x (0, 1, 2) { $f0->Radiobutton(-text => "radiobutton $x", -value => $x, -variable => \$v)->pack(); $f1->Checkbutton(-text => "checkbutton $x")->pack(); } $f0->pack(-padx => 5, -pady => 5, -side => 'left'); $f1->pack(-padx => 5, -pady => 5, -side => 'left'); MainLoop();
ラベルフレーム f0 にはラジオボタンを 3 個、f1 にはチェックボタンを 3 個配置します。ウィジェットの配置方法はフレームウィジェットと同じです。これで、ラベルフレームにウィジェットを配置することができます。
ラベルフレーム(3)
最後に、-labelwidget オプションを使った例を示しましょう。見出しラベルの部分をチェックボタンにして、ラジオボタンの状態を制御することにします。ラジオボタンを使用するときはチェックボタンをオンにします。逆に、チェックボタンがオフのときはラジオボタンを使うことはできません。オプション -state を disabled にするとウィジェットを無効にすることができます。プログラムは次のようになります。
リスト : ラベルフレーム (4) use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); # 値を格納するオブジェクト my $flag = 0; my $v = 0; # ラジオボタンを格納 my @buttons = (); # ボタンの状態を変更 sub change_state { my $new_state; if ($flag) { $new_state = 'normal'; } else { $new_state = 'disabled'; } foreach $b (@buttons) { $b->configure(-state => $new_state); } } # チェックボタン my $cb = $top->Checkbutton(-text => 'use button', -variable => \$flag, -command => \&change_state); # ラベルフレーム my $f = $top->Labelframe(); $f->configure(-labelwidget => $cb); # ラジオボタン foreach my $x (0, 1, 2) { my $b = $f->Radiobutton(-text => "radiobutton $x", -value => $x, -variable => \$v, -state => 'disabled'); $b->pack(); $buttons[$x] = $b; } # フレームの配置 $f->pack(-padx => 5, -pady => 5); MainLoop();
最初にチェックボタン $cb を作ります。Checkbutton メソッドはメインウィンドウを表すオブジェクト $top から呼び出します。そして、ラベルフレームを生成する Labelframe メソッドのオプション -labelwidget に $cb を指定します。$cb は pack で配置する必要はありません。ラベルフレーム $f を pack で配置するとき、ラベルフレームといっしょにチェックボタンも表示されます。
なお、-labelwidget の指定を Labelframe メソッドで行うと、チェックボタンの配置が下にずれてしまいます。このため、configure メソッドで -labelwidget を指定しています。
チェックボタンの状態は $flag に格納します。$flag の値は 0 に初期化しているので、チェックボタンはオフの状態になります。これに合わせて、ラジオボタンはすべて disabled の状態に初期化します。ボタンの状態は関数 change_stat で変更します。change_state は $flag の値によってラジオボタンの状態を変更します。あとは、Checkbutton の -command に \&change_state をセットすれば OK です。
実行結果は次のようになります。
disabled
normal
このように、ラベルフレームを用いることで使いやすい GUI を構築することができます。
ペインドウィンドウ (panedwindow) は、一つのウィンドウを複数の領域に分割する場合に便利なウィジェットです。分割した領域を pane (ペイン) [*1] といい、ペインとペインの間には sash (サッシュ、サッシ) と呼ばれる線が入ります。この線をマウスでドラッグすることで、ペインの大きさを変更することができます。ペインドウィンドウは、ペイン・ウィンドウとかペイン式(型)ウィンドウと呼ばれる場合もあります。
ペインドウィンドウは、いろいろなアプリケーションでよく使われている形式です。たとえば Windows のエクスプローラーは、左側のペインにディレクトリ、右側のペインにファイル名が表示される 2 ペイン形式のウィンドウです。フレームを利用できるブラウザを使えば、ウィンドウを複数の領域に分割して異なる Web ページを表示することができます。また、エディタ xyzzy や Emacs もウィンドウを分割することができます。
このように、一つの画面を複数の領域に分割して内容を表示する方式を「タイリング」[*2] といいます。これに対し、Windows では複数のウィンドウを重ねて表示することができます。この方式を「オーバーラッピング」といいます。ペインドウィンドウはアプリケーションの画面で「タイリング」を実現するウィジェットと考えることができます。
ペインドウインドウは Panedwindow メソッドで生成します。主なオプションを下表に示します。
-orient | ペインを配置する方向 (horizontal : 水平, vertical : 垂直) を指定する |
-sashwidth | サッシの幅を指定する |
-sashrelief | サッシの形状 (flat, raised, sunken, groove, ridge) を指定する |
-showhandle | 真 : サッシにハンドルを付ける 偽 : サッシにハンドルを付けない |
-handlesize | ハンドルの大きさを指定する |
-orient のデフォルト値は horizontal です。ウィンドウは左右に分割され、サッシは縦線になります。vertical を指定するとウィンドウは上下に分割され、サッシは横線になります。サッシの幅は -sashwidth で指定します。サッシの形状は -relief で指定します。デフォルトは ridge になります。-showhandle を真に設定すると、サッシに四角形のハンドルを付けることができます。ハンドルの大きさは -handlesize で指定します。
ペインにウィジェットを配置するにはメソッド add を使います。ペインドウィンドウは複数のウィジェットを配置することができます。n 個のウィジェットを配置すると、ウィンドウは n 分割されます。-orient が horizontal の場合、ウィジェットは左側から順番に配置され、vertical の場合は上から順番に配置されます。
それでは簡単な例を示しましょう。次のリストを見てください。
リスト : ペインドウィンドウ (1) use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); # ペインドウィンドウの生成 my $pw = $top->Panedwindow(-sashwidth => 4); $pw->pack(-expand => 1, -fill => 'both'); # ラベルの生成 my $a = $pw->Label(-text => "panedwindow\ntest1", -bg => 'white'); my $b = $pw->Label(-text => "panedwindow\ntest2", -bg => 'yellow'); # ペインドウィンドウに追加 $pw->add($a); $pw->add($b); MainLoop();
最初に Panedwindow メソッドでペインドウィンドウ $pw を作ります。pack で $pw を配置するとき、オプション -expand => 1, -fill => 'both' を指定すると、ウィンドウのリサイズに対応することができます。次に、$pw に配置するラベルを作ります。ラベルは $pw の子ウィジェットになるので、$pw から Label を呼び出します。最後に、メソッド add でラベルをペインドウィンドウに配置します。
それでは実行結果を示します。
(1) (2)
(3)
(1) が実行した直後の状態です。(2) がサッシを右へ移動した状態で、(3) がサッシを左へ移動した状態です。
ウィンドウを縦方向に分割する場合は orient に vertical を指定します。次のリストを見てください。
リスト : ペインドウィンドウ (2) use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); # ペインドウィンドウの生成 my $pw = $top->Panedwindow(-orient => 'vertical', -sashwidth => 4); $pw->pack(-expand => 1, -fill => 'both'); # ラベルの生成 my $a = $pw->Label(-text => "panedwindow\ntest1", -bg => 'white'); my $b = $pw->Label(-text => "panedwindow\ntest2", -bg => 'yellow'); my $c = $pw->Label(-text => "panedwindow\ntest3", -bg => 'cyan'); # ペインドウィンドウに追加 $pw->add($a); $pw->add($b); $pw->add($c); MainLoop();
実行結果は次のようになります。
(1)
(2)
(3)
(1) が実行した直後の状態です。(2) は上のサッシュを上に移動した状態で、(3) は下のサッシュを下に移動した状態です。
ペインに配置できるウィジェットは一つだけです。複数のウィジェットを配置する場合は、フレームウィジェットにまとめてからペインウィンドウに配置します。簡単な例題として、複数のチェックボタンを使って、ラベルの表示をオン・オフするプログラムを作ってみましょう。次のリストを見てください。
リスト : ペインドウィンドウ (3) use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); my @la = (); my @var = (1, 1, 1, 1); # ペインドウィンドウの生成 my $pw = $top->Panedwindow(-orient => 'vertical', -sashwidth => 4); $pw->pack(-expand => 1, -fill => 'both'); # フレーム my $f = $pw->Frame(); $pw->add($f); # ラベルの表示切り替え sub change_label { my $n = shift; if ($var[$n]) { $pw->add($la[$n]); } else { $pw->forget($la[$n]); } } # チェックボタン foreach my $x (0, 1, 2, 3) { $f->Checkbutton(-text => "display label $x", -variable => \$var[$x], -command => [\&change_label, $x])->pack(); } # ラベル my @colors = ('white', 'yellow', 'cyan', 'pink'); foreach my $x (0, 1, 2, 3) { my $a = $pw->Label(-text => "panedwindow\ntest$x", -bg => $colors[$x]); $la[$x] = $a; $pw->add($a); } MainLoop();
簡単な例題ということで、チェックボタンはフレームに入れてペインウィンドウに配置します。フレームはペインウィンドウの子ウィジェットになるので $pw から Frame を呼び出します。あとはフレーム $f にチェックボタンを配置します。チェックボタンの状態は配列 @var に格納します。@var は 1 に初期化しているので、最初はすべてオンの状態です。
関数 change_label はチェックボタンの状態を調べて、オンのときはペインドウィンドウにラベルを配置し、オフのときはペインドウィンドウからラベルを削除します。メソッド forget を使うとペインウィンドウからウィジェットを削除することができます。削除したウィジェットはウィジェットコマンド add で再度ペインウィンドウに配置することができます。最後に、ラベルを 4 つ作成して、フレームとラベルをペインウィンドウに配置します。
実行結果は次のようになります。
(1)
(2)
(3)
(1) が実行した直後の状態です。(2) はラベル 0 と 1 を削除した状態です。(3) は (2) の後にラベル 1 を表示した状態です。add() はウィジェットを上から順番に追加していくだけなので、削除したラベル 1 を add で再度配置すると一番下に表示されます。
ところで、ペインドウィンドウの中にペインドウィンドウを配置することもできます。次のリストを見てください。
リスト : ペインドウィンドウ (4) use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); # ペインドウィンドウ 1 my $pw1 = $top->Panedwindow(); $pw1->pack(-expand => 1, -fill => 'both'); # ラベル 1 my $a =$pw1-> Label(-text => "panedwindow\ntest1", -bg => 'yellow'); # ペインドウィンドウ 2 my $pw2 = $top->Panedwindow(-orient => 'vertical'); # ラベル 2, 3 my $b = $pw2->Label(-text => "panedwindow\ntest2", -bg => 'cyan'); my $c = $pw2->Label(-text => "panedwindow\ntest3", -bg => 'pink'); # ペインドウィンドウに配置 $pw1->add($a); $pw1->add($pw2); $pw2->add($b); $pw2->add($c); MainLoop();
最初にペインドウィンドウ 1 を作ります。左側のペインにはラベル 1 を配置し、右側のペインにはペインドウィンドウ 2 を配置します。ここで、ペインドウィンドウ 2 の orient を vertical に設定すると、アプリケーションでよく見かける 3 ペイン型のウィンドウになります。実行結果は次のようになります。
(1)
(2)
(3)
(1) が実行した直後の状態です。(2) は縦線のサッシを左へ移動した状態です。ペインドウィンドウ 2 の領域が大きくなっていますね。このあと、横線のサッシを上に移動した状態が (3) です。このように、ペインドウィンドウを入れ子にすることで、アプリケーションでよく用いられる 3 ペイン型のウィンドウを簡単に作成することができます。
スピンボックス (spinbox) はエントリー (entry) ウィジェットを拡張したウィジェットです。スピンボックスはエントリーの右側に上下のボタンが付いていて、エントリーで値を直接入力するだけではなく、上下のボタンを押すことで値を選択することができます。
スピンボックスは Spinbox メソッドで生成します。主なオプションを下表に示します。
-from => n, -to => m, -increment => i | 選択できる数値の範囲を n から m までに設定する ボタンを押したときの増分値は i になる i が浮動小数点数の場合、表示も浮動小数点数になる |
-format => 書式文字列 | 選択する数値が浮動小数点数の場合、表示方法を書式文字列で指定する 書式は書式文字列の変換指示子 %f と同じ |
-values => 配列 | 選択する項目を配列で設定する |
-state => 状態 | スピンボックスの状態 (normal, readonly, disabled) を設定する |
数値はオプション -from, -to, -increment で簡単に設定することができます。数値が浮動小数点数の場合、オプション format で表示方法を設定することができます。数値以外の場合はオプション -values を使います。項目を配列に格納して -values にセットします。-state を readonly にすると、エントリーによる入力が禁止され、ボタンだけで値を選択することになります。
それでは簡単な使用例を示しましょう。次のリストを見てください。
リスト : スピンボックス use strict; use warnings; use Tk; my $top = MainWindow->new(); $top->optionAdd('*font' => ['', 12]); my $s1 = $top->Spinbox(-from => 1, -to => 10, -increment => 1, -width => 10); my $s2 = $top->Spinbox(-from => 1, -to => 5, -increment => 0.5, -format => '%05.2f', -width => 10); my $s3 = $top->Spinbox(-values => ['apple', 'banana', 'cherry', 'grape', 'orange'], -state => 'readonly', -width => 10); $s1->pack(-padx => 5, -pady => 5); $s2->pack(-padx => 5, -pady => 5); $s3->pack(-padx => 5, -pady => 5); MainLoop();
スピンボックスを 3 つ作ります。$s1 は 1 から 10 までの数値で、-increment は 1 です。$s2 は 1 から 5 までの数値ですが、-increment は 0.5 なので浮動小数点数で表示されます。-format の指定が %05.2f なので、表示は 01.50 のようになります。$s3 は apple, banana, cherry, grape, orange から選択します。-state に readonly を指定したので、エントリーから入力することはできません。
それでは実行結果を示します。
(1) (2)
(3)
(1) は実行した直後の状態です。(2) はアップボタンを 1 回押した状態です。もう一回アップボタンを押すと (3) の状態になります。