「Pack ジオメトリマネージャ」の版間の差分

提供: tknotebook
移動: 案内検索
(Frameの大きさを変えてみる)
(fillを使ってみる)
194行: 194行:
 
さてここまでは packメソッドでボタンの位置決めを行いましたが、ボタンの形はボタン自身に任せてきました。しかし、packメソッドのfill引数を使うと、張り付き先の形に合うようにウィジェットを変形できます。
 
さてここまでは packメソッドでボタンの位置決めを行いましたが、ボタンの形はボタン自身に任せてきました。しかし、packメソッドのfill引数を使うと、張り付き先の形に合うようにウィジェットを変形できます。
  
fill引数と。は'''ウィジェットが packメソッドで確保したエリアに合わせてい、ウィジェットの大きさを伸長させる'''パラメータです。
+
fill引数とは'''ウィジェットが packメソッドで確保したエリアに合わせて、ウィジェットの大きさを伸長させる'''パラメータです。
  
 
早速使用例をお見せしましょう。
 
早速使用例をお見せしましょう。

2018年8月27日 (月) 06:57時点における版

メインページ>コンピュータの部屋#Python>Tkinter Tips

Tkinterにはウィジェットを別のウィジェット(親ウィジェット)上に配置するための「ジオメトリマーネジャ」というものを持っています。 ジオメトリマネージャとは、ウィジェットの位置や大きさを決定する仕掛けです。

TkInterには Pack, Grid, Place の3種類のジオメトリマネージャがありますが、この記事では Pack ジオメトリマネージャを紹介します。

Pack ジオメトリマネージャとは?

Pack ジオメトリマネージャとは、ウィジェットをPack(「積めて」)配置してゆくタイプのジオメトリマネージャです。所謂エラスティックなGUIデザインを実現してくれます。 言葉で説明するのは難しいので、早速図とコードをたくさん使った説明に入りましょう。

packメソッドを使ってみる

Packジオメトリマネージャの主役となるのは、全てのウィジェットに用意され散る pack というメソッドです。

最初は packメソッド最も簡単なコードを紹介しましょう。ウィンドウ全面に貼られた FrameウィジェットにButtonを貼り付けてみます。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack()


root.mainloop()

これを実行すると、こんな感じになります。

Pack frame 01.png

このコードでは packメソッドの動きが分かりやすくなるように、ちょっとしたおまじないのコードを使っています。

frame.pack_propagate(False)

tk.Button(frame, text="A").pack()

の影響が Frameウィジェットに及ぶのを防ぐためのコードです。このコードがないと、 Frameのサイズは Buttonウィジェットを表示するのに必要な最小限のサイズに自動的に縮んでしまいます。

frame.pack_propagate(False)

としておくと、Frameウィジェットの大きさは コンストラクタに渡した width, height パラメータの値のままに保たれます。

ここで何が起きているかを簡単に説明しておきましょう。

tk.Button(frame, text="A").pack()

を実行すると、Buttonウィジェットは frameウィジェットの上端に張り付きます。

Pack 図面01.png

図の黄色いところは「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」です。 これは、Buttonウィジェットのサイズとは異なることに注意してください。

「Packメソッドによって ButtonウィジェットがFrameウィジェット上に確保したエリア」はFrameオブジェクトの最上端に、Buttonウィジェットのテキストを表示するのに必要なだけの高さが確保され、横方向はFrameの横方向全体が確保されます。Buttonは確保されたエリアの「中央」に表示されます。

packメソッドを2つ使ってみる

これまで

tk.Button(frame, text="A").pack()

というコードでButtonウィジェットを配置してきましたが、実はこれは

tk.Button(frame, text="A").pack(side=tk.TOP)

と同じです。side=tk.TOP は親ウィジェットの上端にウィジェットを配置することを意味します。 ではこれを2回連続して使ったらどうなるでしょうか?

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.TOP)

root.mainloop()

を実行すると、下図のように縦積みになって表示されます。

Pack frame 02.png

これはつまり、Buttonウィジェットが下図のように自身を表示するエリアを確保するからです。

Pack 図面02.png

つまり2番目のButtonウェイジェットは1番目のボタンウィジェットで確保されたエリアを除いた部分のFrameウィジェットのエリアの最上端を確保するのです。

side=tk.LEFT を使ってみる

さて、さらに size=tk.Left を使ってみましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)

root.mainloop()


このコードを実行すると、下図のように、今度は BボタンがFrameウィジェットの左側に貼り付いて表示されます。

Pack frame 03.png

BボタンはFrameウィジェットの左側に張り付いてますが、少し縦中央より下に表示されていることにお気づきでしょうか? このずれは以下の図を見ればわかります。

Pack 図面03.png

BボタンはFrameウィジェットのAボタンが占めていない残りの部分の左側に張り付きます。 side=tk.LEFT で左へ張り付く場合、ボタンの占めるエリアは、幅はボタンのキャプションを表示するのに十分なだけの幅になりますが、 縦はFrameウィジェットのAボタンが占めていない残りの部分の高さと同じになります。つまり高さはとれるだけ目いっぱいになるのです。

Bボタンはこうして取られたエリアの中央に置かれまず。だからFrameウィジェットの中央より若干下になるのです。

tk.Bottom, tk.Right も使ってみる

そろそろ慣れてきたでしょうから、tkBottomと tk.Right も使ってみましょう。 もうすでに察しはついていると思いますが、tk.Bottom はウィジェットをに、tk.Right はウィジェットを右に張り付かせる指定です。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM)
tk.Button(frame, text="D").pack(side=tk.RIGHT)

root.mainloop()

このコードを実行すると、既に予想されていると思いますが、下の図のようになります。

Pack frame 04.png

もうわかっているとは思いますが、念のため、ボタンの確保したエリアとボタンの位置を図で示しておきましょう。

Pack 図面04-1.png

Frameウィジェットを必要最小限の面積にしてみる(pack()の既定の動作)

さてここまではボタンウィジェットの動きをわかりやすくするため、Frameウィジェットのサイズを強制的に 200 x 200 にしてきましたが、 pack()メソッドの本来の動作では Frameウィジェットは、ボタンを表示する最小限の大きさに自動縮小します。上で示した4個のボタンの場合で試してみましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green", )
#frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP)
tk.Button(frame, text="B").pack(side=tk.LEFT)
tk.Button(frame, text="C").pack(side=tk.BOTTOM)
tk.Button(frame, text="D").pack(side=tk.RIGHT)

root.mainloop()


このコードでは Frameの自動縮小を抑えるためのおまじない frame.pack_propagate(False) を削除しています。 またわかりやすいように Frameウィジェットの背景色を green(緑) にしてみました。結果は以下のようになります。

Pack frame 05.png

この動作を一口で説明するのは大変難しいです。

Frameの大きさが固定の場合は、ボタンは tk.XXX の指定に従って Frame の四隅に張り付くだけでした。 Frameの大きさは十分広く、ボタンを表示するだけの十分なエリアがありました。

実はボタンにはボタンを表示するための必要最小限のサイズがあります。ボタンの場合、既定ではボタンのテキスト(キャプション)を表示するのに必要な大きさのことです。

Packジオメトリマネージャは Frame の大きさを色々変えてみてボタンを貼り付け、全てのボタンのテキストが全て正常に表示できるような最小の大きさを計算し、その大きさに Frameウィジェットのサイズを変更するのです。正確なアルゴリズムはよくわかりませんが(^^; 直感的には大変わかりやすい動きです。

fillを使ってみる

さてここまでは packメソッドでボタンの位置決めを行いましたが、ボタンの形はボタン自身に任せてきました。しかし、packメソッドのfill引数を使うと、張り付き先の形に合うようにウィジェットを変形できます。

fill引数とはウィジェットが packメソッドで確保したエリアに合わせて、ウィジェットの大きさを伸長させるパラメータです。

早速使用例をお見せしましょう。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200)
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

実行結果がこれ

Pack frame 06.png

図を見れば明らかですが, fill=tk.X は、ウィジェット用に「確保されたエリア」の横方向(X方向)いっぱいにウィジェットを引き延ばします。 同様に fill=tk.Y は、方向が縦(Y方向)になるだけで動きは同じです。 Cボタンで fill=tk.Y が効かないのは、もちろん、packがcボタンのエリアをテキストの高さの分しか確保しないからです。

fill=tk.BOTH は常に、ウィジェットのために確保されたエリアいっぱいにウィジェットを引き延ばします。

Frameを自動縮小するようにするとこんな風になります。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
#frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

Pack frame 07.png

Frameの大きさを変えてみる

ここまではプログラムを書いて実行して様子を見ていただけでしたが、今度はFrameウィジェットの大きさを実行時に変えてみましょう。

下のプログラムは今まで使ってきたFrameウィジェットに4個のボタンを表示するだけのプログラムですが(分かりやすいようにFrameウィジェットの背景を緑に変えてありますが)ウィンドウのサイズをマウスを使って変えてやると、下の図ように9なります。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack()
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()


Pack frame 08.png     Pack frame 09.png

まだ説明してはいませんが、Frameウィジェットは side=tk.TOP で トップレベルウィンドウに貼り付けてあるので、 トップレベルウィンドウがリサイズするとこのような動きになります。

しかし、ここではトップレベルウィンドウのリサイズに従って、Frameウィジェットを同じ大きさに追従させたいので、コードを若干手直しします。

import tkinter as tk

root = tk.Tk()

frame = tk.Frame(root, width=200, height=200, bg="green")
frame.pack_propagate(False)
frame.pack(fill=tk.BOTH, expand=True)
tk.Button(frame, text="A").pack(side=tk.TOP, fill=tk.X)
tk.Button(frame, text="B").pack(side=tk.LEFT, fill=tk.Y)
tk.Button(frame, text="C").pack(side=tk.BOTTOM, fill=tk.Y)
tk.Button(frame, text="D").pack(side=tk.RIGHT, fill=tk.BOTH)

root.mainloop()

Pack frame 10.png     Pack frame 11.png

frame.pack(fill=tk.BOTH, expand=True)

の tk.BOTH の意味は既にお分かりだとは思いますが、expand=True の意味はまだ説明していません。とりあえずここでは、Frameウィジェットがトップレベルウィンドウのリサイズに追従して動くために必要なものとお考え下さい。トップレベルウィンドウとFrameウィジェットが一緒にリサイズするようになります。


さて、Frameウィジェットのサイズが実行時に変わると何が起きているのでしょうか?

図から明らかなように、各ボタンの「占めるエリア」は、Frameウィジェットがリサイズされると改めて取り直されます。そしてそれに従ってボタンウィジェットが配置しなおされます。 Frameウィジェットのリサイズに伴ってこの処理は瞬時に実行されるので、利用者にはボタンが連続して移動し、伸び縮みしているかのように見えます。

つまり、ウィジェットをPackメソッドで置いたときの配置(積み込み)の指示が、貼り付けられたコンテナウィジェットがリサイズしてもリアルタイムに保たれていると考えることもできます。こうすることで、各ウィジェットは、ウィンドウがリサイズすると適宜ルールに従ってダイナミックに再配置されることになります。これはなかなか便利です。