kivyを使ってGUIプログラミング ~その2 プログレスバー編~

#はじめに
前回の導入編では、その名の通りKivyについての説明と環境構築やサンプルプログラムの実行を行いました。
今回は、何か処理を行なっている時のプログレスバーに関して紹介したいと思います。
タイトル詐欺みたいな感じのちょっとニッチな記事になりそうですがご容赦ください。
次回からもこんな感じの記事になると思いますw

#プログレスバーとは
皆様ご存知とは思いますが、[wikipedia](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%AC%E3%82%B9%E3%83%90%E3%83%BC)から概要を引用いたします。
>プログレスバー（英: Progress Bar）とは、長時間かかるタスクの進捗状況がどの程度完了したのかを視覚的・直感的に表示するもので、グラフィカルユーザインタフェースの要素（ウィジェット）の一つである。しばしば、ダウンロードやファイル転送のようにパーセント形式で表示される際に使われる。プログレスメーター （英: Progress Meters）とも呼ばれる。

こんなやつですね。(こちらのイメージも[wikipedia](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%AC%E3%82%B9%E3%83%90%E3%83%BC)から)
![](https://upload.wikimedia.org/wikipedia/commons/2/2d/Pbar.gif)


kivyで何か時間のかかる動作を実装した時に、プログレスバーが欲しいなと思ったところ、
[Kivyのリファレンス](https://pyky.github.io/kivy-doc-ja/api-kivy.uix.progressbar.html?highlight=progress#module-kivy.uix.progressbar)には、ほとんど解説がなく、ネットをさまようことになりました。。。


#kivyでのプログレスバーの使い方
こんな感じで動きます。

![pb1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421817/e2414438-0eb9-b963-08ce-a1a6702a69ed.gif)

ソースは以下の通りです。

```python
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.progressbar import ProgressBar

class ProgressTest(BoxLayout):
    def __init__(self, **kwargs):
        super(ProgressTest, self).__init__(**kwargs)
        #プログレスバーのウィジェット
        self.pb = ProgressBar()
        self.add_widget(self.pb)
        
        #処理開始ボタンのウィジェット 
        button_pb = Button(text='progress')
        # ボタンに処理を紐づける
        button_pb.bind(on_press=self.test_progress)
        self.add_widget(button_pb)

    #一定時間ごとに処理を繰り返す
    def pb_clock(self,dt):
        #プログレスバーの最大値になった時、クロックを停止する
        if self.pb.value == 100:
            return False
        #プログレスバーの値を増やす
        self.pb.value += 1

    def test_progress(self, *args):
        self.pb.value = 0
        #クロック始動
        Clock.schedule_interval(self.pb_clock, 1/60)

class TestProgress(App):
    def build(self):
        return ProgressTest()


if __name__ == '__main__':
    TestProgress().run()
```

肝に当たるのが、Clockの処理です。プログレスバーのvalueにfor文で値を更新しても、value自体の値は更新されても、画面のプログレスバーはforの処理が終わるまで変化がありません。
そこで、kivyの時間ごとに逐次的に呼び出され画面描画を行なってくれる、Clockを使います。

使い方として、上記のソースを記述しましたが、実際にプログレスバーが使われているところを考えると、ポップアップか何かに組み込まれていることが多いような気がします。

また、上記プログラムでは、行いたい処理をClock処理内に書く必要があり、
既存のクラスの繰り返し処理の進捗状況を可視化するには、for文をClock用に書き直す必要があると思います。

そこで、ポップアップ上でさらに外部クラスからの処理の進捗状況を可視化できるようカスタマイズしていきます。

#作りたいもの
今回はポップアップ上でプログレスバーを2本同時に動作させる処理を実装したいと思います。
イメージは下図のような感じです。

![イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421817/835eb2ff-5951-0e08-ecc4-452bf9cdff69.png)


別のクラスの関数をサブの処理として、サブの処理が終わるごとにメインの
プログレスバーが進むよう処理を想定しています。

使い方の章でも触れたように、別のクラスでfor文のちょっと長めの処理を行うと画面が暗くなり操作が効かなくなります。
[公式](https://pyky.github.io/kivy-doc-ja/guide/events.html#main-loop)にもこんな感じで記述があります。

２本目のプログラレスバーを動かそうとすると、処理自体は動いていても画面描画は受け付けておらず、GUIだけでは動作しているかわからない状態になっています。

そこで、無理やり画面描画をさせるために、Pythonのthreadingモジュールを用いて、処理を行いつつ、画面描画も行えるようにしています。
(正攻法なのかはわかりませんが、問題なく動作します。)

#ソースコード
``` python:sample.py
from kivy.uix.progressbar import ProgressBar
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button

import time
import threading

#重い処理を想定したクラス
from NagaiSyori import longProc


class ProgressPop(BoxLayout):
    def __init__(self, **kwargs):
        super(ProgressPop, self).__init__(**kwargs)
        #ポップアップを出すボタン
        button_pb = Button(text='thread pop!!')
        button_pb.bind(on_press=self.open_pupup)
        self.add_widget(button_pb)
        #self.tmp = 0
        #外部の重い処理をするクラス
        self.sub_process = longProc()
        #スレッド処理を終了するフラグ
        self.thread_flag = False

    #ポップアップの画面とその処理
    def open_pupup(self, btn):
        #ポップアップ画面の基盤
        content = BoxLayout(orientation='vertical')
        self.pb = ProgressBar()
        content.add_widget(self.pb)
        self.sub_pb= ProgressBar()
        content.add_widget(self.sub_pb)
        self._close = Button(text="close",
                             on_press=self.dissmiss)
        content.add_widget(self._close)
        #ポップアップのウィジェット
        self._pup = Popup(title="Popup Test",
                          content=content,
                          size_hint=(0.5,0.5))
        #ポップアップを開く
        self._pup.open()

        #スレッド処理の準備
        self.thread_flag = True
        self.ev = threading.Event()

        #スレッド処理開始
        self.thed = threading.Thread(target=self.test_progress)
        self.thed.start()

    #ポップアップを閉じる時の処理
    def dissmiss(self, *args):
        self.ev.clear()
        self.thread_flag = False
        self._pup.dismiss()

    #メインのプログレスバーの値を更新
    def main_clock(self, *args):
        #プログレスバーの値が最大になった場合にクロックを停止する
        if self.tmp >= 100:
            Clock.unschedule(self.main_clock)
        self.pb.value = self.tmp

    #サブのプログレスバーの値を更新
    def sub_clock(self, *args):
        # プログレスバーの値が最大になった場合にクロックを停止する
        if self.sub_process.num >= 100:
            Clock.unschedule(self.sub_clock)
        self.sub_pb.value = self.sub_process.num

    #処理
    def test_progress(self):
        #プログレスバーに値を渡すための変数
        self.tmp = 0
        #プログレスバーに値を更新するクロック始動
        Clock.schedule_interval(self.main_clock, 1 / 60)
        for i in range(20):
            #すべの処理が終わっていなければ
            if self.thread_flag:
                time.sleep(1/60)
                #サブのプログレスバーの値を更新するクロック
                Clock.schedule_interval(self.sub_clock, 1 / 60)
                #重い処理
                self.sub_process.process()
                self.tmp = i*5 + 5
                print(self.tmp)
        #処理が全て終わった時のスレッド終了処理
        self.ev.clear()
        self.thread_flag = False

class TestPopupProgressApp(App):
    def build(self):
        return ProgressPop()


if __name__ == '__main__':
    TestPopupProgressApp().run()
```

``` python:NagaiSyori.py
import time
class longProc:
    def __init__(self):
        #プログレスバーに値を受け渡すための変数
        self.num = 0

    #重い処理
    def process(self):
        self.num = 0
        for i in range(100):
            time.sleep(1/600)
            self.num = i + 1
```

![pb2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/421817/37266f2f-7eb4-bdda-e70b-7231757800f0.gif)

実行するとこんな感じで動くと思います。

スレッド処理を加えたことにより、Clock内で繰り返し処理を書かずに済みます。
またに外部のクラスの処理状況も可視化することができるようになりました！！！


#参考記事
大変参考になりました。本当にありがとうございます。

[Python Kivy でポップアップ内プログレスバー](https://qiita.com/SKYS/items/5393ac30e6fc81f83f2c)

[Python | Progress Bar widget in kivy - GeeksforGeeks](https://www.geeksforgeeks.org/python-progress-bar-widget-in-kivy/)