どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

【tkinter】pack()を使ったWidgetの配置の方法

 

tkinterではwidget(例:Frame、Canvas)を配置するには3つの方法 grid, pack, placeがあります。

今回はpackに焦点を当ててwidgetの配置方法についてまとめていきます。

 

 

前準備

pack()の使い方について見る前に、template.pyapp.pyを用意します。この2つのプログラムを使ってpack()の使い方について見ていきます。

 

template.py

import tkinter as tk
from PIL import ImageTk


class Template(tk.Tk):
    def __init__(self, **kwargs):
        super(Template, self).__init__()
        self.title(kwargs.get("title", "tkinter"))
        self.geometry("+{}+{}".format(*kwargs.get("pos", (0, 0))))
        self.resizable(*kwargs.get("resize", (0, 0)))
        try:
            self.iconbitmap(kwargs.get("icon", None))
        except:
            if kwargs.get("icon", None) is not None:
                icon_name = kwargs.get("icon")
                icon_img = ImageTk.PhotoImage(file=icon_name)
                self.tk.call("wm", "iconphoto", self._w, icon_img)

    def run(self):
        self.mainloop()

template.pyはアプリケーションの雛形として用意しました。ここでは、アプリケーションのタイトル、起動時のアプリケーションの位置、アプリケーションの拡大拡小の設定、アプリケーションのアイコンについて設定をすることができます。設定はtemplate.pyを継承したクラスでするようにしてあります。

関連記事 - はじめてのtkinter ウィンドウを作って表示させる

 

app.py

import tkinter as tk
from template import Template


class App(Template):
    def __init__(self):
        super(App, self).__init__()
        self.createWidgets()
        self.attachWidgets()

    def createWidgets(self):
        pass

    def attachWidgets(self):
        pass


if __name__ == "__main__":
    app = App()
    app.run()

template.pyのTemplateクラスを継承しています。今回は特にアプリケーションのタイトル、位置、拡大拡小、アイコンの設定については行いません。createWidgetsWidgetの作成、attachWidgets()で作成したWidgetをウィンドウに配置していきます。

packの使い方

今回、pack()の使い方を見るに当たって2つのFrameを用意しました。frame_1にはLabel、frame_2にはButtonを配置したいと思います。

    def createWidgets(self):
        self.frame_1 = tk.Frame(self, width=100, height=100, bg="coral")
        self.frame_2 = tk.Frame(self, width=100, height=100, bg="tan")

LabelとButtonを作る前に、ひとまず2つのFrameをを生成してウィンドウに配置してみます。

    def attachWidgets(self):
        self.frame_1.pack()
        self.frame_2.pack()

createWidgets()attachWidgets()をそれぞれ変更しました。変更後のコードを実行してみます。

$ python app.py

下のようなウィンドウが起動するはずです。

f:id:pytry3g:20200102220606p:plain

特にオプションを指定しなくてもFrameが配置されていますね。

次に、LabelとButtonを生成して用意した2つのフレームに配置していきます。

    def createWidgets(self):
        self.frame_1 = tk.Frame(self, width=100, height=100, bg="coral")
        self.frame_2 = tk.Frame(self, width=100, height=100, bg="tan")
        self.label_tkinter = tk.Label(self.frame_1, width=30, text="tkinter", bg="red")
        self.label_sample = tk.Label(self.frame_1, text="サンプル", bg="silver")
        self.label_python = tk.Label(
            self.frame_1, text="python", bg="lime green", fg="blue",
            cursor="heart", font=("Helvetica", 16, "bold"), relief="groove"
        )
        self.button_sample = tk.Button(self.frame_2, text="サンプル")
        self.button_quit = tk.Button(self.frame_2, text="終了", command=self.quit)

    def attachWidgets(self):
        self.frame_1.pack()
        self.frame_2.pack()
        self.label_tkinter.pack()
        self.label_sample.pack()
        self.label_python.pack()
        self.button_sample.pack()
        self.button_quit.pack()

3つのLabelと2つのButtonを生成してpack()を使ってそれぞれFrameに配置してみました。この状態でプログラムを実行すると下のウィンドウが起動します。

f:id:pytry3g:20200102231533p:plain

pack()をオプションなしで使用すると、pack()を呼び出した順に上からwidgetが配置されていますね。

あと、オプションなしだとwidgetのサイズに合わせてFrameのサイズが変わっているようです。オプションの設定をすれば見栄えのいいものに変えることができそう。

これからpack()のオプションについて詳しく見ていきます。

関連記事① - [tkinter] Labelを使ってみる

関連記事② - [tkinter] Buttonを使ってみる

関連記事③ - [tkinter] Frameを使ってみる

side(widgetをどこから詰め込むのか決める)

sideを設定することで、widgetをparent(親ヴィジット)のどこから詰めるのか決めることができます。デフォルトではTOPになっており、上から詰める設定になっています。そのため、さきほど実行したプログラムではLabelとButtonが上から順に配置されていたわけです。

他の選択肢として、左から詰めるLEFT、右から詰めるRIGHT、下から詰めるBOTTOMがあります。

試しに、FlameにLEFTを使ってみます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack(side=tk.LEFT)
        self.label_tkinter.pack()
        self.label_sample.pack()
        self.label_python.pack()
        self.button_sample.pack()
        self.button_quit.pack()

コードを変更して実行すると、

f:id:pytry3g:20200103112414p:plain

このようなウィンドウが起動します。さきほどはframe_1が上側、frame_2が下側に配置されていましたが、左詰めの設定をしたことで、frame_1frame_2と順に左詰めになっています。

 

次に、各Frameに入れたLabelとButtonにsideの設定をしてみます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack()
        self.label_tkinter.pack()
        self.label_sample.pack(side=tk.LEFT)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack()
        self.button_quit.pack(side=tk.RIGHT)

コードを変更して、実行すると下のようになりました。

f:id:pytry3g:20200103114946p:plain

このようにsideの設定をすることでwidgetをどこから詰めるのかを決めることができました。

anchor(親ヴィジットのどこに置くか決める)

anchorwidgetを配置先の親ヴィジットのどこに置くかを決めるために使います。デフォルトではCENTERになっています。

他には、右E、左W、上N、下S、右上NE、右下SE、左上NW、左下SWがあります。

試しに、label_sampleanchorを下に設定してプログラムを実行してみます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack()
        self.label_tkinter.pack()
        self.label_sample.pack(side=tk.LEFT, anchor=tk.S)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack()
        self.button_quit.pack(side=tk.RIGHT)

下のウィンドウが起動しました。

f:id:pytry3g:20200103124042p:plain

今回はLabelのサンプルのところに変化を加えました。

sideの説明のところで最後に使ったソースコードを実行したときと少し違う点があるのがわかりますか?

f:id:pytry3g:20200103120823p:plain

sideのところだとサンプルの下に隙間がありましたが、今回anchortk.Sと設定したのでlabel_sampleの位置が下に移動して配置されていることがわかります。

このように、anchorを指定するとwidgetを親ヴィジットのどのあたりに配置するのかを決めることができます。

ipadx, ipady(内側のpadding)

ipadxipadyはそれぞれwidgetの内側の高さと横の長さを設定することができます。デフォルトでは0になっています。

試しに、button_sampleipadxipadyの値を変えてみます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack()
        self.label_tkinter.pack()
        self.label_sample.pack(side=tk.LEFT, anchor=tk.S)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack(ipadx=25, ipady=25)
        self.button_quit.pack(side=tk.RIGHT)

このように変更して実行すると下のウィンドウが起動します。

f:id:pytry3g:20200103140656p:plain

padx, pady(外側のpadding)

padxpadyはそれぞれwidgetの外側の高さと横の長さを設定することができます。デフォルトでは0になっています。

試しに、label_tkinterpadyの値を変えてみます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack()
        self.label_tkinter.pack(pady=20)
        self.label_sample.pack(side=tk.LEFT, anchor=tk.S)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack(ipadx=25, ipady=25)
        self.button_quit.pack(side=tk.RIGHT)

このように変更して実行すると下のウィンドウが起動します。

f:id:pytry3g:20200103141748p:plain

tkinterのLabelの上下にそれぞれpadyで指定した値の長さが設定されています。

padxpadyですが、実はもう少し細かい設定をすることができます。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT)
        self.frame_2.pack()
        self.label_tkinter.pack(padx=(30, 50), pady=(20, 0))
        self.label_sample.pack(side=tk.LEFT, anchor=tk.S)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack(ipadx=25, ipady=25)
        self.button_quit.pack(side=tk.RIGHT)

label_tkinterpadxpadyを上のように変更してみました。

このプログラムを実行すると、

f:id:pytry3g:20200103142432p:plain

このようになりました。

padxpadyはそれぞれpadx=(左、右)pady=(上、下)と細かく設定をすることができるんです。

fill(隙間を埋める)

fillを使うことで隙間を埋めることができます。例えば、さきほど実行したプログラムだとframe_1の上下が空いていましたよね。

f:id:pytry3g:20200103142432p:plain

こんなときに隙間を埋めることができるのがfillなんです。使い方は超簡単です!デフォルトではNONEになっているのをBOTHにするだけ。

    def attachWidgets(self):
        self.frame_1.pack(side=tk.LEFT, fill=tk.BOTH)
        self.frame_2.pack()
        self.label_tkinter.pack(padx=(30, 50), pady=(20, 0))
        self.label_sample.pack(side=tk.LEFT, anchor=tk.S)
        self.label_python.pack(side=tk.RIGHT)
        self.button_sample.pack(ipadx=25, ipady=25)
        self.button_quit.pack(side=tk.RIGHT)

隙間があるframe_1fillBOTHに設定してプログラムを実行すると、

f:id:pytry3g:20200103143451p:plain

はい、さっきまで隙間があったのにちゃんと隙間が埋まっています!!

fillは他にもXYを設定することができます。Xだと水平方向の隙間を埋める、Yだと垂直方向に隙間を埋める、BOTHだと水平方向と垂直方向の隙間を埋めることができます。

expand(拡大)

この記事の最後に参考記事を載せていますが、その参考記事を見る限りexpandはparent(親ヴィジット)を拡大したときに生まれるwidget周りの隙間をどうするかを設定すると書かれています。しかし、試してみるとwidget周りの隙間は埋めていない感じがしました。

見た感じparentを拡大したときに、widgetの位置がFalseなら固定、Trueならparentの拡大に伴ってwidgetの位置が設定通りに移動している感じがしました。

あ、ちなみに私が書いたtemplate.pyではウィンドウの拡大拡小ができない設定をしているので、app.py__init__()を以下のように変更してください。

class App(Template):
    def __init__(self):
        super(App, self).__init__(resize=(1,1))

おわり

pack()の使い方について紹介しました。

tkinterについて他にもいろいろ書いているので、ご興味があるかたはぜひ以下の記事を見てみてください。

www.pytry3g.com

 

参考記事① - The Tkinter Pack Geometry Manager

参考記事② - Python | pack() method in Tkinter - GeeksforGeeks