1

In my Kivy app, I have a function that takes a long time to complete. I made a popup to inform the user the function is running. I want to have a gif animation so that the user knows the app did not crash. In testing, the gif played as expected in the popup, until I add the long running function, then only a stationary image is displayed. Everything else works as expected (e.g. the popup closes at the end of the function).

tl;dr

How can I make a gif continue to play in my kivy app while a function is being executed?

Code Summary

I largely followed the code provide by Dirty Penguin's answer in Building a simple progress bar or loading animation in Kivy?:

from kivy.app import App
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from kivy.clock import Clock

import time, threading

class RunningPopup(GridLayout):
    fpop = None

    def set_pop(self, pwin):
        self.fpop = pwin

    def close(self):
        self.fpop.dismiss()

class ExampleApp(App):
    def show_popup(self):
        self.pop_up = RunningPopup()
        self.pop_up.open()

    def process_button_click(self):
        # Open the pop up
        self.show_popup()

        # the original code suggested by dirty penguin
        # mythread = threading.Thread(target=self.really_long_function)
        # mythread.start()

        # I've had better luck with the Clock.schedule_once
        Clock.schedule_once(self.really_long_function)

    def really_long_function(self):
        thistime = time.time() 
        while thistime + 5 > time.time(): # 5 seconds
            time.sleep(1)

        # Once the long running task is done, close the pop up.
        self.pop_up.dismiss()

if __name__ == "__main__":
    ExampleApp().run()

My KV file:

<RunningPopup>:
    rows: 3

    Label:
        size_hint_y: 0.2
        text: 'Experiments are running ... '
        bold: True
        color: hex('#0DB14B')

    Label:
        size_hint_y: 0.2
        text: 'Please be patient, this may take time.'
        color: hex('#0DB14B')

    Image:
        size_hint_y: 0.6
        id: loading_animation_gif
        height: dp(200)
        source: './graphics/loading.gif'
        center_x: self.parent.center_x
        center_y: self.parent.center_y
        allow_stretch: True
        size_hint_y: None
        anim_delay: 0.05
        mipmap: True

Tried

  • Using threading - this does not run in parallel, the long function runs and then the popup flashes open and closed
  • Using kivy.clock schedule_once - this appears to work the best in that the popup opens and closes as expected / during the long running function
  • Using a Zip file of images instead of a gif file (as suggested here: Gif Animation not playing in Kivy App)

Related

mdoc-2011
  • 2,747
  • 4
  • 21
  • 43
  • Kivy, like any other GUI framework, has a loop to process events. If you block that loop by running something else, the GUI grinds to a halt. – Mark Ransom Jun 23 '21 at 02:27

1 Answers1

2

Using the Thread will work. Here is a modified version of your code that uses threading. I had to make a few changes just to get your code to run:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup

import time, threading

kv = '''
#:import hex kivy.utils.get_color_from_hex
Button:
    text: 'doit'
    on_release: app.process_button_click()
    
<RunningPopup>:
    GridLayout:
        rows: 3
    
        Label:
            size_hint_y: 0.2
            text: 'Experiments are running ... '
            bold: True
            color: hex('#0DB14B')
    
        Label:
            size_hint_y: 0.2
            text: 'Please be patient, this may take time.'
            color: hex('#0DB14B')
    
        Image:
            size_hint_y: 0.6
            id: loading_animation_gif
            height: dp(200)
            source: 'elephant.gif'  # my gif file
            center_x: self.parent.center_x
            center_y: self.parent.center_y
            allow_stretch: True
            size_hint_y: None
            anim_delay: 0.05
            mipmap: True
'''

class RunningPopup(Popup):
    fpop = None

    def set_pop(self, pwin):
        self.fpop = pwin

    def close(self):
        self.fpop.dismiss()

class ExampleApp(App):
    def build(self):
        return Builder.load_string(kv)

    def show_popup(self):
        self.pop_up = RunningPopup()
        self.pop_up.open()

    def process_button_click(self):
        # Open the pop up
        self.show_popup()

        # the original code suggested by dirty penguin
        mythread = threading.Thread(target=self.really_long_function)
        mythread.start()

        # I've had better luck with the Clock.schedule_once
        # Clock.schedule_once(self.really_long_function)

    def really_long_function(self, *args):
        thistime = time.time()
        while thistime + 5 > time.time(): # 5 seconds
            time.sleep(1)

        # Once the long running task is done, close the pop up.
        self.pop_up.dismiss()

if __name__ == "__main__":
    ExampleApp().run()
John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • This solution worked. Thank you very much - I have not worked a great deal with threading in python before - I had attempted threading, but did not use the correct syntax. – mdoc-2011 Jun 23 '21 at 02:34