【Tkinter】packメソッドを解説!

Tkinterのpackメソッド

こんにちは、Youtaです。

Tkinterではウィジェットの配置方法が\(3\)つあります。

今回焦点を当てるのはpackメソッドです。

Tkinterでウィジェットを配置する3つの方法

本記事は、packメソッド及びその関連メソッドをすべて解説します。

誰でも必ず理解できるように、豊富な説明と図解を盛り込みました。

早速、packメソッドを見ていきましょう!

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メソッドの引数

beforeafter

before・afterの図解

beforeafterはウィジェットの配置順序を制御します。

beforeは指定のウィジェットの前に、afterは後に配置します。

beforeafterで、ウィジェットの配置順序をコントロールする例です。

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()

デフォルトでは、label1label2label3label4の順に配置されます。

しかし、beforelabel3label2の前に、afterlabel4label1の後に配置されています。

beforeでlabel3はlabel2の前に、afterでlabel4はlabel1の後に配置される

expand

expandの図解

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()

最初は上から順にウィジェットが積まれています。

expandをボタンに適用する前の状態

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

expandをボタンに適用し、ボタンの占める領域が拡大した状態

fill

fillの図解

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の方向に伸びています。

fillでラベルをさまざまな方向に引き伸ばした状態

side

sideの図解

sideはウィジェットをどの方向から詰めるかを指定します。

下記の\(4\)つが指定できます。

文字列コンテナ内の配置位置
tkiner.TOP または "top"上(デフォルト)
tkiner.BOTTOM または "bottom"
tkiner.LEFT または "left"
tkiner.RIGHT または "right"
sideの引数

tk.TOPtk.BOTTOMを指定すると、ウィジェットは横方向に縄張りを主張します。

side=tk.TOP・side=tk.BOTTOMの場合、ウィジェットは水平方向に領域を確保

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

side=tk.LEFT・side=tk.RIGHTの場合、ウィジェットは垂直方向に領域を確保

上記の仕様のため、方向に規則性を持たせると綺麗に配置されます。

\(4\)つのラベルのsidetk.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()

普段は上からですが、左から積まれます。

sideに"left"を指定して、4つのラベルを左から配置した状態

逆に、方向をでたらめに指定するのはお勧めしません。

ウィジェットの数が増えると、見栄えが悪くなるからです。

具体例を見ればすぐに分かります。

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()

実家の私の部屋のようです。

8つのラベルのsideをでたらめに指定したため見栄えが悪い状態
すずか
すずか

なんでこうなったん?

先生
先生

sideの仕様を思い出しましょう。

まず、label1label4までを上から時計回りに配置させます。

ある方向に可能な限り領域を占めようとする点にもご注目ください。

見栄えが悪い配置のメカニズム1

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

見栄えが悪い配置のメカニズム2

以上が醜悪な配置のメカニズムです。

sideを使う際は、下記のような規則を持たせると良いでしょう。

sideを指定するコツ
  • \(1\)つの方向しか使わない
  • tk.TOP or tk.BOTTOMtk.LEFT or tk.RIGHT
    (上下の配置→左右の配置)
  • tk.LEFT or tk.RIGHTtk.TOP or tk.BOTTOM
    (左右の配置→上下の配置)

anchor

anchorの図解

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コンテナ内でのウィジェットの配置位置を制御します。

anchorsideが作る空間内でのウィジェットの配置位置を調整します。

sideとanchorの違い

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()

各ウィジェットが指定の方向に寄りました。

anchorで4つのラベルがバラバラな方向に配置される状態

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

anchorで4つのラベルがバラバラな方向に配置される例を示す図に、ウィジェットの領域を図示した図

padxpady

padx・padyの図解

padxpadyは、ウィジェットの外側の余白(単位: px)を指定します。

CSSのmarginのようなもので、周囲のウィジェットとの間に隙間を入れます。

基本は整数での指定ですが、タプルもOKです。

より詳細な位置に余白が入ります。

child.pack(
    padx=(a, b),  # 水平方向の余白: aは左側の余白、bは右側の余白
    pady=(c, d),  # 垂直方向の余白: cは上側の余白、dは下側の余白
)
先生
先生

後述のipadxipadyはタプルを渡せません。

padxpadyでウィジェット間に余白を入れる場合と入れない場合の違いを見ます。

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の余白が入りました。

padx・padyで外部に余白を入れたウィジェットと、入れていないウィジェットを比較

わかりやすいように、余白部分を図示しておきます。

padx・padyによって入れられる余白を図示した図

ipadxipady

ipadx・ipadyの図解

ipadxipadyは、ウィジェットの内側の余白(単位: px)を指定します。

CSSのpaddingのようなもので、ウィジェットが占める領域を大きくします。

ipadxipadyで内部に余白を入れる場合と入れない場合の違いを見てみましょう。

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拡大しました。

ipadx・ipadyで内部に余白を入れたウィジェットと、入れていないウィジェットを比較

in_

in_の図解

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()

最初、labellabel_frameの外に配置されます。

in_を適用する前

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

in_を適用して、LabelFrame内にlabelを移動させる

オプションの設定を確認

pack_infoの図解

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が見えています。

pack_forgetメソッドを適用する前

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

pack_forgetメソッドを適用して、配置されていたlabelを消す

フレームのサイズ調整を制御

pack_propagateメソッドは、親ウィジェット(フレーム)の自動サイズ調整を制御します。

pack_propagateメソッドの構文です。

親ウィジェット.pack_propagate(frag=_noarg_)

引数のflagが\(1\)(True)で自動調整が有効に、\(0\)(False)で無効になります。

すずか
すずか

自動サイズ調整?

先生
先生

フレームが持つ機能です。

詳細を説明しますね。

まず、packgridメソッドでフレーム内にウィジェットを配置する場合を考えます。

実は、フレームのサイズは内部のウィジェットに合わせて調整されてしまうのです。

試しに、サイズを指定したフレーム内にラベルを配置してみます。

フレームには背景色を付け、色が見えるかも確認しましょう。

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)

...

フレームのサイズをwidthheightで指定した通りにできました。

pack_propagate(0)でフレームの自動サイズ調整を無効化し、フレームの背景色が見える状態
先生
先生

再び自動調整にするには、引数に\(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メソッド関連のメソッド

この記事が皆様のお役に立てたら幸いです。

ご覧いただきありがとうございました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA