Tkinter でウィジェットを動的に追加したり削除したりするアレ


この記事を書いた理由

ネットを探してもなかなか見つからなかったため。
需要あると思うんですけどね。意外と。

想定読者

エントリー(テキストボックス)ウィジェットを自由に追加したり、削除したりする、スマホとかでよく見るアレがしたいんじゃあ!
入力項目は3つまでとかダサすぎる・・・。
とか考えている人。

Tkinter って何?
という人はちょっと向いていないかもしれません。

いきなりソースコード

コメント増し増しなので頑張って読んでください。
要するに、エントリーウィジェットのインスタンスと並び順を配列と変数で管理して、良い感じに表示してるだけです。
ボタンだと背景が透明にならないのでラベルで代用。

応用すれば、エントリー(テキストボックス)以外のウィジェットでも同じことできますね。

import tkinter as tk

# Tkinter お決まりフレーズ
class App(tk.Tk) :
    def __init__(self) :
        super().__init__()

        # タイトルとウィンドウサイズ
        self.title('appendEntry')
        self.geometry('640x480')

        # エントリーウィジェットをグループ化するためのフレーム
        self.FrameWindow = tk.Frame(self)
        self.FrameWindow.grid(row=0)

        # エントリーウィジェットマネージャを初期化
        self.Entries = []        # エントリーウィジェットのインスタンス
        self.insertEntries = []  # 追加するボタンのようなラベル
        self.removeEntries = []  # 削除するボタンのようなラベル

        # こちらはインデックスマネージャ。ウィジェットの数や並び方を管理
        self.index = 0           # 最新のインデックス番号
        self.indexes = []        # インデックスの並び

        # 最初のエントリーウィジェットを作成して配置
        self.createEntry(0)

        # テキストを取得ボタン作成
        self.GetEntryTextButton = tk.Button(
            text='テキストを取得',
            command=self.GetEntryTextButton_click
        )
        self.GetEntryTextButton.grid(row=1)

    # エントリーウィジェットを追加するボタンのようなラベルをクリック
    def insertEntry_click(self, event, id) :

        # 追加する位置
        next = self.indexes.index(id) + 1
        self.index = self.index + 1

        # エントリーウィジェットを作成して配置
        self.createEntry(next)

    # エントリーウィジェットを削除するボタンのようなラベルをクリック
    def removeEntry_click(self, event, id) :

        # 削除する位置
        current = self.indexes.index(id)

        # エントリーウィジェットと追加・削除ボタンのようなラベルを削除
        self.Entries[current].destroy()
        self.insertEntries[current].destroy()
        self.removeEntries[current].destroy()

        # エントリーウィジェットマネージャから削除
        self.Entries.pop(current)
        self.insertEntries.pop(current)
        self.removeEntries.pop(current)
        self.indexes.pop(current)

        # 再配置
        self.updateEntries()

    # エントリーウィジェットを再配置
    def updateEntries(self) :

        # エントリーウィジェットマネージャを参照して再配置
        for i in range(len(self.indexes)) :
            self.Entries[i].grid(column=0, row=i)
            self.Entries[i].lift()
            self.insertEntries[i].grid(column=1, row=i)
            self.removeEntries[i].grid(column=2, row=i)

        # 1つしかないときは削除ボタンのようなラベルを表示しない
        if len(self.indexes) == 1 :
            self.removeEntries[0].grid_forget()

    # エントリーウィジェットを作成して配置
    def createEntry(self, next) :

        # 最初のエントリーウィジェットを追加
        self.Entries.insert(next, tk.Entry(self.FrameWindow, width=30))

        # エントリーウィジェットを追加するボタンのようなラベルを作成
        self.insertEntries.insert(next, tk.Label(
            self.FrameWindow,
            text='+',
            fg='#33ff33',
            font=('Arial Black', 20)
        ))

        # エントリーウィジェットを削除するボタンのようなラベルを作成(初期の段階では表示しない)
        self.removeEntries.insert(next, tk.Label(
            self.FrameWindow,
            text='−',
            fg='#ff3333',
            font=('Arial Black', 20)
        ))

        # 追加するボタンのようなラベルにクリックイベントを設定
        self.insertEntries[next].bind('<1>', lambda event, id=self.index: self.insertEntry_click(event, id))

        # 削除するボタンのようなラベルにクリックイベントを設定
        self.removeEntries[next].bind('<1>', lambda event, id=self.index: self.removeEntry_click(event, id))

        # インデックスマネージャに登録
        self.indexes.insert(next, self.index)

        # 再配置
        self.updateEntries()

    # テキストを取得するボタンを押す
    def GetEntryTextButton_click(self) :
        GetEntry =[]

        # 全てのエントリーウィジェットの内容を配列化
        for i in range(len(self.indexes)) :
            GetEntry.append(self.Entries[i].get())

        # コンソールに表示
        print(GetEntry)

# Tkinter お決まりフレーズ
if __name__ == '__main__' :
    app = App()
    app.mainloop()

動作した感じ

MacOS X 11.6
Python 3.9 で動かしてみました。多分 Win とか他の Python3 のバージョンでも動くと思いますが。

緑色の+ボタンを押すとエントリーウィジェットを直下に追加。
赤色のーボタンを押すと真横のエントリーウィジェットを削除します。
テキストを取得ボタンを押すと、実行中のコンソールに配列として表示します。

なんか、応用すれば色々できそう。

まとめ

なんか、面白いアイディアが思いついたら自由に改変して使ってください。

画面外にはみ出した場合のスクロール実装は下記をご参照ください。
【Python/Tkinter】スクロール可能なFrameを作成するクラス
自分もこれから勉強します。