Python

【Kivy】ボタンのフィードバックと重い処理を分ける方法【Python】

投稿日:2021年4月22日 更新日:

Kivyでアプリ開発をしていると、MVCのように処理と表示を分けたくなることがあると思います。
その時、ボタンをトリガーにすると、ボタンのフィードバックのタイミングが処理の終了タイミングと同じになってしまうことはないでしょうか。

今回は、ボタンのフォードバックと、重い処理とを分けて表示する方法を紹介します。

結論:Threadを使って分ける

結論から言うと、Pythonのマルチスレッド機能を使うことで分けることができます。
では、順に説明していきましょう。

まずは、完成像の共有です。
ボタンを押すことで現在時刻を取得するアプリがあり、ここに重い処理を加えます。
具体的には、時刻取得の際に何らかの重い処理としてsleepメソッドを追記しました。

マルチスレッド化する前のソースコードは下記となります。

#:kivy 1.11.1

MainScreen:
    BoxLayout:
        orientation: 'vertical'
        size: root.size

        Label:
            id: view_label
            text: '-'

        Button:
            text: 'Get Time'
            on_press: root.button_clicked()
import datetime
import time

from kivy.app import App
from kivy.uix.widget import Widget

class MainScreen(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def button_clicked(self):
        time.sleep(3)   # 重い処理
        self.ids.view_label.text = str(datetime.datetime.now())
        
class TestApp(App):
    def __init__(self, **kwargs):
        super(TestApp, self).__init__(**kwargs)
        self.title = 'Window Name'

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

実行してみると、ボタンを押してもユーザーにはフィードバックが何もなく、3秒後に突然ボタンとラベルの表示が切り替わることと思います。
これでは、ユーザビリティ的に良くないですよね。

理想としては、ボタンを押したらボタンのフィードバックがあり、処理が完了したらラベルの表示が更新されるような、ボタンと処理の動作が分かれるていることが望ましいです。

そこで、マルチスレッドを使って、ボタンと処理を分けました。
改修後のpythonファイルは以下の通りです。
(Kivyのコードには変更ありません)

import datetime
import time
import threading

from kivy.app import App
from kivy.uix.widget import Widget

from kivy.clock import Clock

class MainScreen(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    
    def update(self, dt):
        self.ids.view_label.text = self.view_label_text

    def button_clicked(self):
        thread = threading.Thread(target=self.process)
        thread.start()

    def process(self):
        time.sleep(3)
        self.view_label_text = str(datetime.datetime.now())
        Clock.schedule_once(self.update)

class TestApp(App):
    def __init__(self, **kwargs):
        super(TestApp, self).__init__(**kwargs)
        self.title = 'Window Name'

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

このように記述することで、ボタンを押下した時のフィードバックは即時に反映しつつ、3秒後に時刻が表示されることと思います。

前は、button_clicked関数内に処理をまとめていましたが、処理はprocess関数に分けました。
button_clicked関数が呼ばれた時は、process関数のスレッドを作成して開始するだけにとどめています。
こうすることによって、ボタン押下と時刻取得の処理を切り分け、メインスレッドであるbutton_clicked関数は即時に終わらせることができるようにしました。

process関数の処理完了時には、updateメソッドをClockクラスを使って呼び出しています。
ClockというKivyのメインループとなっているクラスから呼び出さないと、値が更新されないので注意しましょう。

取得した時間情報の受け渡しは、self.view_label_textで受け渡ししていますが、update関数に引数を指定する形でも問題ありません。お好みで。

補足

ボタンのFBはon_pressで呼び出したメソッドの完了時

上記実行していただけたら気づくと思うのですが、ボタンのフィードバックはkvファイルのon_pressで指定したメソッドの完了時になります。
したがって、ボタンをフィードバックを簡潔させるためには、そのメソッドを完了させる必要があります。

マルチスレッドは、その名の通り複数のスレッドで動作させます。
thread.start()を実行すると、メインスレッドはthread.start()の完了を待たずにそのまま次のステップに進みます。

こうすることによって、on_pressで呼び出したメソッドを完了することができます。

マルチスレッドを使って処理を分担することは、Kivy公式のProgramming Guideにも記載されています。
Non GUI Operations that can be deferred to a different thread.と明記されています。

Kivy公式 Programming Guideより引用

値の更新だけでは画面に表示されない

上記のソースコード、わざわざupdate()を用意しています。
一度、update()をコメントアウトして実行してみて欲しいのですが、update()がないと表示される値は更新されません。(変数としての値は更新されます。)

これも、on_pressで呼び出したメソッド終了時はプログラムが自動的に値の更新をみていると思います。
しかし、マルチスレッドで処理を分けていると、プログラム側は値の更新を拾うことができません。

したがって、update()を用意してあげて、Tread完了時に値の更新が完了したことを知らせてあげる必要があります。

-Python
-, ,

執筆者:


comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

関連記事

【Python】GUIライブラリの比較【Tkinter, PyQt, Kivy】

Python言語は、機械学習やサーバーサイドのイメージが強いと思うのですが、実はGUIアプリケーションを作成することも可能です。 今回は、Pythonで代表的なGUIツール4つを比較してみました。 目 …

【Kivy】一番簡単なKivyのインストール手順【Python】

Python用のGUIツールとして、海外では知名度が高いのがKivyです。Kivyは、マルチプラットフォーム対応ですので、Pythonでのアプリ開発の選択肢が大きく広がります。 Kivyの環境構築は、 …

【Kivy】AttributeError: ‘ROOT’ object has no attribute ‘_disabled_count’の対処法【Python】

Kivyでのアプリ開発をしていると、このようなエラーに出くわすことがあります。こちらのエラー対処法を紹介します。 目次1 結論:Widgetを定義したクラスのコンストラクタを修正2 補足2.1 発生条 …

【Python】pytestの使い方【テスト駆動開発】

今回は、テスト駆動開発の足掛けとして、pytestの使い方を紹介します。 テスト駆動開発とは、テストのことを第一に考えて実装する開発法のことです。アジャイル開発のように、設計・開発・テストを短く繰り返 …

【Kivy】Widgetの初期値をidで指定する方法【Python】

Kivyを使ったアプリ開発をやっていると、widgetの初期値について、kvファイルで指定するのではなくpythonファイルで設定したくなることがあると思います。そんな時、root widgetクラス …

yassanさんのプロフィール写真

yassan
R&Dエンジニア。
新卒で大手の社内SEになったもののスキルアップのためにITベンダーに転職。
技術調査からプログラミングまで幅広くやってます。
趣味はバイクとガジェットと遊戯王カードです。