[Python][tkinter] forループ数に応じたプログレスバーのシンプルな表示方法

作ったプログラムの備忘録

## はじめに
for文でフォルダ内にあるファイル数分だけ処理をまわすときに、tkinterに進捗状況を表示したかったので`ttk.Progressbar`の動かし方を調べたところ、思った挙動がなかなか得られませんでした。

たとえば以下のようなサイトにあるコードなども転記してみたりしましたが、いまいち関数で外に出している意味やafter関数が重なったif文など腑に落ちない状態でした。

https://teratail.com/questions/316975

というわけで、狙いの挙動を得るのに必要なコードについて検討したところ、シンプルな表記ができたのでメモとして記載します。

## 実行環境
Windows 10 Pro 64bit
Python 3.9.13

## プログレスバー表示に必要なコード

<details><summary>実際に動く最小コード</summary><div>

```Python:実際に動く最小コード
import time
import tkinter as tk
from tkinter import ttk

root = tk.Tk()
root.title("プログレスバー")

# プログレスバー配置
progbar = ttk.Progressbar(root, length=400, mode="determinate", maximum=1)
progbar.pack()

# for文を回すリスト
prog_list = list('abcdefghijklmnopqrstuvwxyz')

# ループ処理
for i in range(len(prog_list)):
    time.sleep(0.05)
    # プログレスバー表示変更
    progbar.configure(value=(i+1)/len(prog_list))
    progbar.update()

root.mainloop()
```

</div></details>


```Python:プログレスバー表示に関する部分の抜粋
# プログレスバー配置
progbar = ttk.Progressbar(root, length=400, mode="determinate", maximum=1)
progbar.pack()

# for文を回すリスト
prog_list = list('abcdefghijklmnopqrstuvwxyz')

# ループ処理
for i in range(len(prog_list)):
    # -- ここに処理を記載 # 例：time.sleep(0.05)
    # プログレスバー表示変更
    progbar.configure(value=(i+1)/len(prog_list))
    progbar.update()
```

* 最初の2行はプログレスバーの生成と配置で、次にfor文を回すリストを用意します。
* この際にプログレスバーの最大値設定は`maximum=1`として以降の設定は割合計算としました。
* そのため、for文の最後に`progbar.configure(value=(i+1)/len(prog_list))` と `progbar.update()`を入れればプログレスバーが進んでいきます。

* ネット上で確認できるコードだと`after関数`を使っていたり、`variable`で指定した変数を変更している例が多いようなのですが、for文中に入れるとうまく動きませんでした。
* プログレスバーはfor文で回している数の分だけ進めるのであれば、これが一番シンプルな気がします。

## 作例（ソースコード）
* enumerateでfor文を回せばリストの要素も利用できますし、実際のプログラムではわかりやすいかなと思います。

* 以下のコードでは2重ループを2本のプログレスバーで表示させています。

![animation.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3079481/fe860aa4-d82c-0c33-6fdb-924f653e399a.gif)

```Python:
import time
import tkinter as tk
from tkinter import ttk

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master.geometry('420x160')
        self.master.title('sample')
        self.master.grid_rowconfigure((0, 1, 2), weight=1)
        self.master.grid_columnconfigure((0, 1), weight=1)

        self.label_1 = ttk.Label(self.master, text='')
        self.label_2 = ttk.Label(self.master, text='')
        self.progbar1 = ttk.Progressbar(self.master, length=400, mode="determinate", maximum=1)
        self.progbar2 = ttk.Progressbar(self.master, length=400, mode="determinate", maximum=1)
        self.button1 = ttk.Button(self.master, text='実行', width=20, command=self.loop_function)
        self.button2 = ttk.Button(self.master, text='リセット', width=20, command=self.reset_bar)
        self.label_1.grid(row=0, column=0, padx=10, pady=10)
        self.label_2.grid(row=0, column=1, padx=10, pady=10)
        self.progbar1.grid(row=1, column=0, columnspan=2, padx=10, pady=(0, 5))
        self.progbar2.grid(row=2, column=0, columnspan=2, padx=10, pady=(0, 10))
        self.button1.grid(row=3, column=0, padx=10, pady=(0, 10))
        self.button2.grid(row=3, column=1, padx=10, pady=(0, 20))

        # ループ処理をまわすリスト
        self.prog_list1 = list('abcdefghijklmnopqrstuvwxyz')
        self.prog_list2 = list('あいうえお')

    # ループ処理
    def loop_function(self):
        # ループ処理
        for i, item2 in enumerate(self.prog_list2):
            for j, item1 in enumerate(self.prog_list1):
                time.sleep(0.15)
                # ラベル表示変更
                self.label_1.configure(text=f'{item2}-{item1}')
                self.label_2.configure(text=f'{j+1}/{len(self.prog_list1)}')
                # プログレスバー表示変更
                self.progbar1.configure(value=(j+1)/len(self.prog_list1))
                self.progbar1.update()
            # プログレスバー表示変更
            self.progbar2.configure(value=(i+1)/len(self.prog_list2))
            self.progbar2.update()

    # リセット処理
    def reset_bar(self):
        # ラベル表示リセット
        self.label_1.configure(text='')
        self.label_2.configure(text='')
        # プログレスバー表示リセット
        self.progbar1.configure(value=0)
        self.progbar1.update()
        self.progbar2.configure(value=0)
        self.progbar2.update()


if __name__ == "__main__":
    root = tk.Tk()
    app = Application(master = root)
    app.mainloop()

```