こんにちは、Youtaです。
Tkinterではウィジェットの配置方法がpack, grid, placeの\(3\)つあります。
今回焦点を当てるのはpackメソッドです。

本記事は、packメソッド及びその関連メソッドをすべて解説します。
誰でも必ず理解できるように、豊富な説明と図解を盛り込みました。
早速、packメソッドを見ていきましょう!
Contents
packメソッド
packメソッドはコンテナ内にウィジェットを順次配置します。
メソッドが呼ばれた順に配置するため、その名の通り「詰め込む」イメージです。
Tkinterの\(3\)つある配置方法のうち、特に複雑なレイアウトを求めない場合に便利です。
私はテスト的に表示する際に使っています。
これが、当ブログのTkinter関連の記事でも
サンプルコードにpackメソッドが多く使われている理由です。
packメソッドの構文です。
child.pack(option=value, ...)次章では、上記のoptionに指定できるものをご紹介します。
オプション
packメソッドには\(11\)個のオプション引数があります。
| 変数名 | データ型 | 説明 |
|---|---|---|
before | ウィジェット | 指定されたウィジェットの前に配置 |
after | ウィジェット | 指定されたウィジェットの後に配置 |
expand | ブール 整数 | ウィジェットがコンテナ内で余ったスペースを占有するかどうか |
fill | 文字列 | ウィジェットを引き伸ばす方向 |
side | 文字列 | コンテナ内でウィジェットを詰める方向 |
anchor | 文字列 | ウィジェットが占める領域において、ウィジェットを寄せる方向 |
ipadx | 整数 | ウィジェット内側の\(x\)方向の余白 |
ipady | 整数 | ウィジェット内側の\(y\)方向の余白 |
padx | 整数 タプル | ウィジェット外側の\(x\)方向の余白 |
pady | 整数 タプル | ウィジェット外側の\(y\)方向の余白 |
in_ | ウィジェット | 親ウィジェット(コンテナ)を指定 |
packメソッドの引数
before・afterはウィジェットの配置順序を制御します。
beforeは指定のウィジェットの前に、afterは後に配置します。
before・afterで、ウィジェットの配置順序をコントロールする例です。
import tkinter as tk
root = tk.Tk()
# label1の作成と配置
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack()
# label2の作成と配置
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack()
# label3の作成と配置
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack(before=label2) # label3をlabel2の前に配置
# label4の作成と配置
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(after=label1) # label4をlabel1の後に配置
root.mainloop()デフォルトでは、label1・label2・label3・label4の順に配置されます。
しかし、beforeでlabel3はlabel2の前に、afterでlabel4はlabel1の後に配置されています。


expandは、ウィジェットがコンテナ内の余った領域を占めるかを決めます。
デフォルトはexpand=0(False)で、スペースを占有しません。
expand=1(True)にすると、ウィジェットは余分なスペースを取り込みます。
expandにより、画面内の余分な領域がどう使われるのかを示す例です。
ボタンを押すと、そのボタンにのみexpandを適用します。
import tkinter as tk
# ボタンの拡張・縮小を切り替える関数
def toggle():
global is_expanded
if is_expanded:
contract() # 拡張状態なら縮小
else:
expand() # 縮小状態なら拡張
is_expanded = not is_expanded # 状態を反転
# ボタンを拡張する関数
def expand():
button.pack(expand=1)
# ボタンを縮小する関数
def contract():
button.pack(expand=0)
# 初期状態の設定
global is_expanded
is_expanded = False # 最初は縮小状態
root = tk.Tk()
root.geometry("300x200") # ウィンドウのサイズを設定
# ラベル1の作成と配置
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack()
# トグルボタンの作成と配置
button = tk.Button(root, text="Button", command=toggle)
button.pack()
# ラベル2の作成と配置
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack()
root.mainloop()最初は上から順にウィジェットが積まれています。

ボタンを押すと、ボタンは下の領域を独り占めします。


fillはウィジェットをどの方向に引き伸ばすかを決めます。
これにより、ウィジェットを水平方向や垂直方向に拡大させられます。
以下が指定できます。
| 文字列 | 引き伸ばす方向 |
|---|---|
tkinter.X または "x" | 水平方向(左右) |
tkinter.Y または "y" | 垂直方向(上下) |
tkinter.BOTH または "both" | 水平・垂直方向(上下左右) |
tkinter.NONE または "none" | 引き伸ばさない(デフォルト)。 |
fillの引数fillでウィジェットを各方向に引き伸ばす例です。
結果が見やすいようにexpandも使います。
import tkinter as tk
root = tk.Tk()
root.geometry("300x200") # ウィンドウのサイズを設定
# ラベル1の作成と配置
label1 = tk.Label(root, text="x", bg="lightblue")
label1.pack(fill="x", expand=1) # 横方向に広がり、余白を均等に分ける
# ラベル2の作成と配置
label2 = tk.Label(root, text="y", bg="lightgreen")
label2.pack(fill="y", expand=1) # 縦方向に広がり、余白を均等に分ける
# ラベル3の作成と配置
label3 = tk.Label(root, text="both", bg="lightpink")
label3.pack(fill="both", expand=1) # 両方向に広がり、余白を均等に分ける
# ラベル4の作成と配置
label4 = tk.Label(root, text="none", bg="lightyellow")
label4.pack(fill="none", expand=1) # どの方向にも広がらないが、余白を均等に分ける
root.mainloop()各ラベルがfillの方向に伸びています。


sideはウィジェットをどの方向から詰めるかを指定します。
下記の\(4\)つが指定できます。
| 文字列 | コンテナ内の配置位置 |
|---|---|
tkinter.TOP または "top" | 上(デフォルト) |
tkinter.BOTTOM または "bottom" | 下 |
tkinter.LEFT または "left" | 左 |
tkinter.RIGHT または "right" | 右 |
sideの引数tk.TOP・ tk.BOTTOMを指定すると、ウィジェットは横方向に縄張りを主張します。

tk.LEFT・ tk.RIGHTを指定すると、ウィジェットは縦方向に縄張りを主張します。

上記の仕様のため、方向に規則性を持たせると綺麗に配置されます。
\(4\)つのラベルのsideをtk.LEFTにして配置する例です。
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
# ラベル1の作成と配置
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack(side="left") # side="left"で左側から配置
# ラベル2の作成と配置
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack(side="left") # side="left"で左側から配置
# ラベル3の作成と配置
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack(side="left") # side="left"で左側から配置
# ラベル4の作成と配置
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(side="left") # side="left"で左側から配置
root.mainloop()普段は上からですが、左から積まれます。

逆に、方向をでたらめに指定するのはお勧めしません。
ウィジェットの数が増えると、見栄えが悪くなるからです。
具体例を見ればすぐに分かります。
import tkinter as tk
root = tk.Tk()
# ラベル1の作成と配置
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack(side="top") # side="top"で上側から配置
# ラベル2の作成と配置
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack(side="right") # side="right"で右側から配置
# ラベル3の作成と配置
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack(side="bottom") # side="bottom"で下側から配置
# ラベル4の作成と配置
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(side="left") # side="left"で左側から配置
# ラベル5の作成と配置
label5 = tk.Label(root, text="Label5", bg="lightblue")
label5.pack(side="right") # side="right"で右側から配置
# ラベル6の作成と配置
label6 = tk.Label(root, text="Label6", bg="lightgreen")
label6.pack(side="top") # side="top"で上側から配置
# ラベル7の作成と配置
label7 = tk.Label(root, text="Label7", bg="lightpink")
label7.pack(side="left") # side="left"で左側から配置
# ラベル8の作成と配置
label8 = tk.Label(root, text="Label8", bg="lightyellow")
label8.pack(side="bottom") # side="bottom"で下側から配置
root.mainloop()実家の私の部屋のようです。


なんでこうなったん?

sideの仕様を思い出しましょう。
まず、label1〜label4までを上から時計回りに配置させます。
ある方向に可能な限り領域を占めようとする点にもご注目ください。

次は、label5〜label8までを右から反時計回りに配置させます。

以上が醜悪な配置のメカニズムです。
sideを使う際は、下記のような規則を持たせると良いでしょう。
- \(1\)つの方向しか使わない
tk.TOPortk.BOTTOM→tk.LEFTortk.RIGHT
(上下の配置→左右の配置)tk.LEFTortk.RIGHT→tk.TOPortk.BOTTOM
(左右の配置→上下の配置)

anchorは、ウィジェットが占める領域内でウィジェットを寄せる方向を指定します。
コンパスの方位に対応する文字列で指定します。
| 文字列 | 寄せる方向 |
|---|---|
tkinter.N または "n" | 上 |
tkinter.E または "e" | 右 |
tkinter.S または "s" | 下 |
tkinter.W または "w" | 左 |
tkinter.NE または "ne" | 右上 |
tkinter.SE または "se" | 右下 |
tkinter.SW または "sw" | 左下 |
tkinter.NW または "nw" | 左上 |
tkinter.CENTER または "center" | 中央 |
anchorの引数
さっきのsideと何が違うん?

sideで大まかに位置を決め、anchorで微調整するイメージです。
sideはコンテナ内でのウィジェットの配置位置を制御します。
anchorはsideが作る空間内でのウィジェットの配置位置を調整します。

anchorでウィジェットを色々な方角に寄せる例です。
結果がわかりやすいようにsideも一緒に使っています。
import tkinter as tk
root = tk.Tk()
root.geometry("300x200")
# ラベル1の作成と配置
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack(anchor="e", side="top") # anchor="e"は東(右)方向
# ラベル2の作成と配置
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack(anchor="w", side="bottom") # anchor="w"は西(左)方向
# ラベル3の作成と配置
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack(anchor="n", side="left") # anchor="n"は北(上)方向
# ラベル4の作成と配置
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(anchor="s", side="right") # anchor="s"は南(下)方向
root.mainloop()各ウィジェットが指定の方向に寄りました。

わかりやすいように、ウィジェットの領域を図示しておきます。


padx・padyは、ウィジェットの外側の余白(単位: px)を指定します。
CSSのmarginのようなもので、周囲のウィジェットとの間に隙間を入れます。
基本は整数での指定ですが、タプルもOKです。
より詳細な位置に余白が入ります。
child.pack(
padx=(a, b), # 水平方向の余白: aは左側の余白、bは右側の余白
pady=(c, d), # 垂直方向の余白: cは上側の余白、dは下側の余白
)
後述のipadx・ipadyはタプルを渡せません。
padx・padyでウィジェット間に余白を入れる場合と入れない場合の違いを見ます。
import tkinter as tk
root = tk.Tk()
# ラベル1: 余白なし
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack(side="left")
# ラベル2: padxあり
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack(side="left", padx=20)
# ラベル3: 余白なし
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack()
# ラベル4: padyあり
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(pady=20)
root.mainloop()label2の左右・label4の上下に\(20\)pxの余白が入りました。


ipadx・ipadyは、ウィジェットの内側の余白(単位: px)を指定します。
CSSのpaddingのようなもので、ウィジェットが占める領域を大きくします。
ipadx・ipadyで内部に余白を入れる場合と入れない場合の違いを見てみましょう。
import tkinter as tk
root = tk.Tk()
# ラベル1: 余白なし
label1 = tk.Label(root, text="Label1", bg="lightblue")
label1.pack()
# ラベル2: ipadxあり
label2 = tk.Label(root, text="Label2", bg="lightgreen")
label2.pack(ipadx=20)
# ラベル3: ipadyあり
label3 = tk.Label(root, text="Label3", bg="lightpink")
label3.pack(ipady=20)
# ラベル4: ipadx・ipadyあり
label4 = tk.Label(root, text="Label4", bg="lightyellow")
label4.pack(ipadx=20, ipady=20)
root.mainloop()label2を左右に、label3を上下に、label4を上下左右に\(20\)px拡大しました。


in_は、ウィジェットを配置するコンテナ(親ウィジェット)を指定します。
通常、ウィジェットはコンストラクタの第一引数に指定した親に配置されます。
import tkinter as tk
root = tk.Tk()
frame = tk.Frame()
# 親はroot
label = tk.Label(root)
# root(親)にlabelを配置
label.pack()しかし、in_を使うと後から配置先となる親を変更できるのです。
import tkinter as tk
root = tk.Tk()
frame = tk.Frame()
# 親はroot
label = tk.Label(root)
# frame(新しい親)にlabelを配置
label.pack(in_=frame)in_を利用して、ウィジェットの配置位置を切り替える例です。
import tkinter as tk
# ウィジェットの状態を切り替える関数
def toggle():
global is_in_frame
# 現在の状態に応じてadd_label_to_frame()またはremove_label_from_frame()を呼び出す
if is_in_frame:
remove_label_from_frame()
else:
add_label_to_frame()
is_in_frame = not is_in_frame
# labelをlabel_frame内に配置する関数
def add_label_to_frame():
label.pack(in_=label_frame)
# labelをroot内に配置する関数
def remove_label_from_frame():
label.pack(in_=root)
is_in_frame = False
root = tk.Tk()
# ラベルフレームを作成し、配置する
label_frame = tk.LabelFrame(root, text="LabelFrame", width=100, height=100)
label_frame.pack(padx=10, pady=10)
# 切り替えボタンをラベルフレームに配置し、toggle関数を紐づける
button = tk.Button(label_frame, text="Button", command=toggle)
button.pack(padx=5, pady=5)
# メインのラベルを作成し、rootに配置する
label = tk.Label(root, text="Label", bg="lightblue")
label.pack(padx=5, pady=5, fill="x")
root.mainloop()最初、labelはlabel_frameの外に配置されます。

ボタンを押すと、labelは新しい親label_frameの中に移動します。

オプションの設定を確認

pack_infoメソッドはpackメソッドのオプションの設定を取得します。
pack_infoメソッドの構文です。
子ウィジェット.pack_info()具体例を示しましょう。
pack_infoメソッドを使うにあたり、下記の状態を前提とします。
import tkinter as tk
root = tk.Tk()
label = tk.Label(root)
label.pack()packメソッドの設定を出力してみます。
print(label.pack_info())
# {'in': <tkinter.Tk object .>, 'anchor': 'center', 'expand': 0, 'fill': 'none', 'ipadx': 0, 'ipady': 0, 'padx': 0, 'pady': 0, 'side': 'top'}ウィジェットを非表示

pack_forgetメソッドはpackメソッドで配置したウィジェットを非表示にします。
pack_forgetメソッドの構文です。
子ウィジェット.pack_forget()
非表示にしたら戻せないの?

再度packメソッドを使うと再表示されます。
ただし注意点があります。
pack_forgetメソッドは、packメソッドのオプション設定を無効化してしまいます。
したがって、再表示の際には再度オプションを設定する必要があります。
# 配置
子ウィジェット.pack(padx=10, pady=10, before=button, anchor="w")
# 非表示
子ウィジェット.pack_forget()
# 再表示
子ウィジェット.pack(padx=10, pady=10, before=button, anchor="w")pack_forgetメソッドで、ウィジェットの表示・非表示を切り替える例です。
import tkinter as tk
# ウィジェットの状態を切り替える関数
def toggle():
global is_visible
# 現在の状態に応じてshow()またはhide()を呼び出す
if is_visible:
hide()
else:
show()
is_visible = not is_visible
# labelを表示する関数
def show():
label.pack(padx=10, pady=10, before=button, anchor="w")
# labelを非表示にする関数
def hide():
label.pack_forget()
is_visible = True
root = tk.Tk()
label = tk.Label(root, text="Label", bg="lightblue")
label.pack(padx=10, pady=10, anchor="w")
# 切り替えボタンを配置し、toggle関数を紐づける
button = tk.Button(root, text="Button", command=toggle)
button.pack()
root.mainloop()最初はlabelが見えています。

ボタンを押すとlabelが消えます。

フレームのサイズ調整を制御
pack_propagateメソッドは、親ウィジェット(フレーム)の自動サイズ調整を制御します。
pack_propagateメソッドの構文です。
親ウィジェット.pack_propagate(frag=_noarg_)引数のflagが\(1\)(True)で自動調整が有効に、\(0\)(False)で無効になります。

自動サイズ調整?

フレームが持つ機能です。
詳細を説明しますね。
まず、packやgridメソッドでフレーム内にウィジェットを配置する場合を考えます。
実は、フレームのサイズは内部のウィジェットに合わせて調整されてしまうのです。
試しに、サイズを指定したフレーム内にラベルを配置してみます。
フレームには背景色を付け、色が見えるかも確認しましょう。
import tkinter as tk
root = tk.Tk()
frame = tk.Frame(root, width=300, height=200, bg="lightblue")
frame.pack()
label = tk.Label(frame, text="Label")
label.pack()
root.mainloop()フレームの背景色が全然見えません。
つまり、フレームが内部のラベルの大きさに変更されたのです。


勝手に変わるの嫌だね。

そこで登場するのがpack_propagateメソッド!
pack_propagateメソッドに\(0\)(False)を渡すと、フレームのサイズを固定します。
先ほどのコードにpack_propagateメソッドを織り交ぜてみましょう。
...
frame = tk.Frame(root, width=300, height=200, bg="lightblue")
frame.pack()
frame.pack_propagate(0)
...フレームのサイズをwidthやheightで指定した通りにできました。


再び自動調整にするには、引数に\(1\)(True)を渡します。
全ウィジェットを取得
pack_slavesメソッドは、親ウィジェット内の全ウィジェットを取得します。
pack_slavesメソッドの構文です。
parent.pack_slaves()pack_slavesメソッドを実際に使っているところをお見せしましょう。
まず、下記の状態を前提とします。
import tkinter as tk
root = tk.Tk()
tk.Label(root).pack()
tk.Label(root).pack()
tk.Button(root).pack()
tk.Button(root).pack()ここでpack_slavesメソッドを試します。
print(root.pack_slaves())
# [<tkinter.Label object .!label>, <tkinter.Label object .!label2>, <tkinter.Button object .!button>, <tkinter.Button object .!button2>]オブジェクト本体が取れるので、configメソッド等でオプションの変更も可能です。
まとめ
Tkinterのpackメソッドをご紹介しました。
最後に軽く復習してから締めましょう。
packメソッドは格子状にウィジェットを配置します。
そのオプション引数は計\(10\)個ありました。
| 変数名 | データ型 | 説明 |
|---|---|---|
before | ウィジェット | 指定されたウィジェットの前に配置 |
after | ウィジェット | 指定されたウィジェットの後に配置 |
expand | ブール 整数 | ウィジェットがコンテナ内で余ったスペースを占有するかどうか |
fill | 文字列 | ウィジェットを引き伸ばす方向 |
side | 文字列 | コンテナ内でウィジェットを詰める方向 |
anchor | 文字列 | ウィジェットが占める領域において、ウィジェットを寄せる方向 |
ipadx | 整数 | ウィジェット内側の\(x\)方向の余白 |
ipady | 整数 | ウィジェット内側の\(y\)方向の余白 |
padx | 整数 タプル | ウィジェット外側の\(x\)方向の余白 |
pady | 整数 タプル | ウィジェット外側の\(y\)方向の余白 |
in_ | ウィジェット | 親ウィジェット(コンテナ)を指定 |
packメソッドの引数またpackメソッドに関連して、下記のような機能を提供するメソッドもありました。
| メソッド名 | 説明 |
|---|---|
pack_info | オプションの設定を確認する。 |
pack_forget | ウィジェットを非表示にする。 オプションは無効化する。 |
pack_propagate | フレームのサイズ調整を制御する。 |
pack_slaves | コンテナ内の全ウィジェットを取得する。 |
packメソッド関連のメソッドこの記事が皆様のお役に立てたら幸いです。
ご覧いただきありがとうございました。

