Pack ジオメトリマネージャ

提供: tknotebook
2018年8月26日 (日) 12:04時点におけるNakamuri (トーク | 投稿記録)による版

移動: 案内検索

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

Tkinterにはウィジェットをウィンドウ上に配置するための「ジオメトリマーネジャ」があります。 ジオメトリマネージャとは、ウィジェットの位置や大きさを決定する仕掛けです。

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

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

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

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 を使ってみる

さてここまでは side=tk.TOP だけ使ってきましたが 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メソッドの side引数だけを使ってきましたが、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