こんにちは、Youtaです。
Tkinterではウィジェットの配置方法が\(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
はウィジェットをどの方向に引き伸ばすかを決めます。
これにより、ウィジェットを水平方向や垂直方向に拡大させられます。
以下が指定できます。
文字列 | 引き伸ばす方向 |
---|---|
tkiner.X または "x" | 水平方向(左右) |
tkiner.Y または "y" | 垂直方向(上下) |
tkiner.BOTH または "both" | 水平・垂直方向(上下左右) |
tkiner.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\)つが指定できます。
文字列 | コンテナ内の配置位置 |
---|---|
tkiner.TOP または "top" | 上(デフォルト) |
tkiner.BOTTOM または "bottom" | 下 |
tkiner.LEFT または "left" | 左 |
tkiner.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.TOP
ortk.BOTTOM
→tk.LEFT
ortk.RIGHT
(上下の配置→左右の配置)tk.LEFT
ortk.RIGHT
→tk.TOP
ortk.BOTTOM
(左右の配置→上下の配置)
anchor
は、ウィジェットが占める領域内でウィジェットを寄せる方向を指定します。
コンパスの方位に対応する文字列で指定します。
文字列 | 寄せる方向 |
---|---|
tkiner.N または "n" | 上 |
tkiner.E または "e" | 右 |
tkiner.S または "s" | 下 |
tkiner.W または "w" | 左 |
tkiner.NE または "ne" | 右上 |
tkiner.SE または "se" | 右下 |
tkiner.SW または "sw" | 左下 |
tkiner.NW または "nw" | 左上 |
tkiner.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
メソッド関連のメソッドこの記事が皆様のお役に立てたら幸いです。
ご覧いただきありがとうございました。