LoginSignup
8
12

More than 5 years have passed since last update.

Python Kivy でポップアップ内プログレスバー

Last updated at Posted at 2017-04-23

はじめに

Kivyのポップアップウィジェット内に設置したプログレスバーウィジェットで関数の進捗を表示することを考えてみました.
進捗表示を行いたい関数をジェネレータとすることで,関数内からプログレスバーを更新しているように見せています.

環境

  • Python 3.5
  • Kivy 1.9.1
  • Windows 10 64bit

ポップアップウィジェットのカスタマイズ

プログレスバー,ラベル,ボタンの3部品からなる以下のようなイメージのポップアップを使用します.
2017-04-23_14h29_40.png

また,ポップアップウィジェットには以下の2つのメソッドを追加しています.
1. set_value(): プログレスバーの値とラベルを更新するメソッド
2. enable_close_button(): ポップアップを閉じるボタンを有効にするメソッド

コードは以下の通りです.

Class-PopupProgress
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.progressbar import ProgressBar
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.label import Label

class PopupProgress():
    def __init__(self, value_init=0, value_max=1, title='', size=None, 
                 auto_close=False):
        self._auto_close = auto_close
        self._box = BoxLayout(orientation='vertical')

        if size is None:
            size_hint = (1,1)
        else:
            size_hint = (None, None)

        self.popup = Popup(title=title, 
                           content=self._box, 
                           auto_dismiss=False, 
                           size_hint=size_hint, 
                           size=tuple(size)
                           )

        # progress bar
        self._pb = ProgressBar(max=value_max, value=value_init)
        self._box.add_widget(self._pb)

        # label
        self._label = Label(text='')
        self._box.add_widget(self._label)

        # close button
        self._button = Button(text='Close', height=40, size_hint_y=None, 
                        disabled=True)
        self._button.bind(on_press=self.popup.dismiss)
        self._box.add_widget(self._button)

        self.popup.open()    # ポップアップを開く


    def set_value(self, value=None, message=''):
        if not value is None:
            self._pb.value = value
        self._label.text = message
        if self._pb.value_normalized>=1:
            if self._auto_close:
                self.popup.dismiss()
            else:
                self.enable_close_button()


    def enable_close_button(self):
        self._button.disabled = False

Root Widget

Root Widget にはボタンを作成するためのコンストラクタ(__init__)以外に以下の3つのメソッドを設定しています.

  1. func_test(): 時間のかかる処理の進捗を確認したいメソッド(ジェネレータ)

    • 時間のかかる処理の進捗を確認したいメソッドはジェネレータとします .
    • 処理の進捗を確認したい場所に yield value, message を挿入し,進捗とメッセージを返すようにしています.
    • 処理の最後では value=1 を返し,閉じるボタンを有効にしています.
    • ここではプログレスバーの Max値=1 としていますが,func_test_pp()で任意の値を設定可能です.
    • ここでは時間のかかる処理を模して time.sleep(1) を設定しています.
  2. func_test_pp(): プログレスバーの値を更新するループを開始させるためのメソッド

    • ポップアップウィジェットクラスのインスタンス self.pp を生成します.
    • 上記のfunc_test()ジェネレータからイテレータオブジェクトself.gen を作成します.
    • Clock.schedule_once() を使用して 再帰メソッドpp_update() を実行し,ループを開始します.
  3. pp_update(): プログレスバーの値を更新する再帰メソッド

    • イテレータオブジェクトself.gen を一度参照した後,プログレスバーの値を更新します.
    • StopIteration またはその他のエラーが発生するまで,Clock.schedule_once() を使用して再度自身を呼び出します.
    • エラーが発生した場合,ポップアップ内のボタンを有効にしてユーザーがポップアップを閉じることが出来るようにしています.

Clock.schedule_once() は0秒だと表示が不安定であったため,差し当たり0.1秒後に実行するように設定しています.

Class-RootWidget他
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
import time

class RootWidget(BoxLayout):

    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        button = Button(text='popup!')
        button.bind(on_press=self.func_test_pp)
        self.add_widget(button)


    def pp_update(self, *args):
        try:
            value, message = next(self.gen)
            self.pp.set_value(value, message)
            Clock.schedule_once(self.pp_update, 0.1)
        except StopIteration:
            self.pp.enable_close_button()
        except Exception as e:
            self.pp.set_value(message=str(e))
            self.pp.enable_close_button()


    def func_test_pp(self, *args):
        self.pp = PopupProgress(
                title='TestPopupProgress', 
                size=(self.width/2, self.height/3)
                )
        self.gen = self.func_test()
        Clock.schedule_once(self.pp_update, 0.1)


    def func_test(self):
        yield 0, 'Preparing...'    # yield を使用して現在の進捗とコメントを返す(はじまり)

        # 進捗確認を行いたい時間のかかる処理
        allitems = 5
        for i in range(allitems):
            time.sleep(1) # 何か時間のかかる処理
            yield (i+1)/allitems, '{:d}/{:d}'.format(i+1, allitems)    # yield を使用して現在の進捗とコメントを返す

        yield 1, 'Complete.'    # yield を使用して現在の進捗とコメントを返す(おわり)


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


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

まとめ

上記をまとめると以下の通りとなります.
2017-04-23_14h33_04_.gif

test_popup_progress.py
from kivy.app import App

from kivy.clock import Clock

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.progressbar import ProgressBar
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.label import Label

import time

class PopupProgress():
    def __init__(self, value_init=0, value_max=1, title='', size=None, 
                 auto_close=False):
        self._auto_close = auto_close
        self._box = BoxLayout(orientation='vertical')

        if size is None:
            size_hint = (1,1)
        else:
            size_hint = (None, None)

        self.popup = Popup(title=title, 
                           content=self._box, 
                           auto_dismiss=False, 
                           size_hint=size_hint, 
                           size=tuple(size)
                           )

        # progress bar
        self._pb = ProgressBar(max=value_max, value = value_init)
        self._box.add_widget(self._pb)

        # label
        self._label = Label(text='')
        self._box.add_widget(self._label)

        # close button
        self._button = Button(text='Close', height=40, size_hint_y=None, 
                        disabled=True)
        self._button.bind(on_press=self.popup.dismiss)
        self._box.add_widget(self._button)

        self.popup.open()


    def set_value(self, value=None, message=''):
        if not value is None:
            self._pb.value = value
        self._label.text = message
        if self._pb.value_normalized>=1:
            if self._auto_close:
                self.popup.dismiss()
            else:
                self.enable_close_button()


    def enable_close_button(self):
        self._button.disabled = False

class RootWidget(BoxLayout):

    def __init__(self, **kwargs):
        super(RootWidget, self).__init__(**kwargs)
        button = Button(text='popup!')
        button.bind(on_press=self.func_test_pp)
        self.add_widget(button)


    def pp_update(self, *args):
        try:
            value, message = next(self.gen)
            self.pp.set_value(value, message)
            Clock.schedule_once(self.pp_update, 0.1)
        except StopIteration:
            self.pp.enable_close_button()
        except Exception as e:
            self.pp.set_value(message=str(e))
            self.pp.enable_close_button()


    def func_test_pp(self, *args):
        self.pp = PopupProgress(
                title='TestPopupProgress', 
                size=(self.width/2, self.height/3)
                )
        self.gen = self.func_test()
        Clock.schedule_once(self.pp_update, 0.1)


    def func_test(self):
        yield 0, 'Preparing...'

        allitems = 5
        for i in range(allitems):
            time.sleep(1)
            yield (i+1)/allitems, '{:d}/{:d}'.format(i+1, allitems)

        yield 1, 'Complete.'


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


if __name__ == '__main__':
    TestPopupProgressApp().run()
8
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
12