Lev_name
Lev_name

Reputation: 31

How to compose widgets correctly in python kivy?

I would like to make it so that I can see the progress of the execution of the loop, but the ProgressBar only exits after the execution of this loop. How can I fix this?

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.progressbar import ProgressBar

class MyApp(App):
    def build(self):
        self.wid = FloatLayout()
        self.prog_bar = ProgressBar(max=99999, pos = [0, -150])
        self.wid.add_widget(self.prog_bar)
        self.prog_bar.value = 0
        for i in range(0, 99999):
            self.prog_bar.value = i
            print(i/99999)
        
        return self.wid
if __name__ == '__main__':
    MyApp().run()

Upvotes: 0

Views: 116

Answers (2)

Tshirtman
Tshirtman

Reputation: 5949

This is not about composition, but rather about multitasking, the thing is, if you do a locking loop like this, kivy can’t update anything until the loop is done (because nothing else happens while the function runs, no update of the UI or processing of any event). So what you want to do is allow the loop to run "concurrently" to the kivy event loop.

There are multiple ways to do this kind of things, they are all more or less suited to different situations.

kivy Clock allows scheduling a function to run later (schedule_once), possibly multiple times (schedule_interval), so you could have a more atomic function, that just increments the value of the progress bar once (not in a loop), and call this function using schedule interval. The issue is if you want to do actual work in the function, that could take significant enough time for the UI to be visibly blocked between increment of the progressbar value.

If that’s your case, because the function you are trying to run is slow, then you might want to run the function in a Thread instead, so it doesn’t block the kivy UI. You’ll be able to update the value of your progress bar from the the thread, if necessary, using the Clock to schedule a function doing just that (you need to be careful about not doing things that update the OpenGL context from a thread, as OpenGL doesn’t specify the behavior in such situation, so some graphic cards/drivers can be fine with it, but others will crash your program, so anything updating the UI from a thread must use the clock, there is a mainthread decorator provided in the clock module for that).

In some situation, you can (and thus want) avoid doing the long work beforehand, and find a way to do the minimal amount of work each time needed, and go on with the work, there are some abstractions of that idea, like RecycleView, that avoids creating widgets altogether, when you want a very long list of items to display, just creating the data beforehand, and letting it handle the creation/update of the widgets as needed. Of course, this might not apply to your situation, but it’s worth mentioning in case it is.

So in short: don’t do such loops, or do them in threads, think really hard if you actually need such a loop, and use clock events to update your UI when using threads.

Upvotes: 0

John Anderson
John Anderson

Reputation: 38857

Kivy uses the main thread of your App to update its widgets. You are running your loop on the main thread, so Kivy cannot update the ProgressBar until that loop completes. The fix is to do the loop in another thread:

class MyApp(App):
    def build(self):
        self.wid = FloatLayout()
        self.prog_bar = ProgressBar(max=99999, pos=[0, -150])
        self.wid.add_widget(self.prog_bar)
        self.prog_bar.value = 0
        threading.Thread(target=self.do_progress).start()

        return self.wid

    def do_progress(self):
        for i in range(0, 99999):
            self.prog_bar.value = i
            print(i / 99999)

Upvotes: 2

Related Questions