trouble understanding Progress Bar

2,490 views
Skip to first unread message

Bill Eaton

unread,
Apr 1, 2014, 12:04:38 AM4/1/14
to kivy-...@googlegroups.com
I have a fundamental problem understanding Progress Bars and how updating graphics is supposed to work. 

I've read many of the posts in this group about the Progress Bars. It seems the strategy is to get "out of the main loop". Or dispatch events. Or use Clock.schedule_interval. I had the most progress with Clock.schedule_interval example at https://gist.github.com/inclement/6372048 but when I tried to put generalize it, I couldn't make it work.

I'm doing some work in a loop. I want to do:
for i in range(10):
  grab_data_chunk
  update_progressbar
But invariably what happens is the progress bar is not updated until the loop is done and I exit the method.

ZenCODE

unread,
Apr 1, 2014, 4:08:29 AM4/1/14
to kivy-...@googlegroups.com
Err, don't you mean:

for i in range(10):
  grab_data_chunk()
  update_progressbar()

Neither of those statements does anything at present. But posting your full code might help the most?

Cheers

Bill Eaton

unread,
Apr 1, 2014, 12:52:33 PM4/1/14
to kivy-...@googlegroups.com
Ok. I have some working code, but it's pretty ugly. Instead of being able to iterate over a list in one tidy loop, I've spread out the loop over three methods. This is an adaptation of https://gist.github.com/inclement/6372048.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.progressbar import ProgressBar
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from time import sleep
 
class MainMenu(BoxLayout):
   
def __init__(self, **kwargs):
       
super(MainMenu, self).__init__(**kwargs)
       
self.orientation = 'vertical'
       
        btn
= Button(text="Start")
        btn
.bind(on_release=self.trigger)
       
self.add_widget(btn)
       
       
self.MyList = ('this', 'is', 'a', 'test')
       
self.i = 0
       
self.pb = ProgressBar(max = len(self.MyList), value = 0)    
       
self.add_widget(self.pb)
               
   
def trigger(self, *args):
     
self.i = 0
     
self.pb.value = 0
     
     
Clock.schedule_interval(self.heavyFunc,0.1)
   
   
def heavyFunc(self, dt):
        sleep
(0.5)
       
print(self.MyList[self.i])
       
self.i += 1
       
self.pb.value +=1
       
if self.i >= len(self.MyList):
           
Clock.unschedule(self.heavyFunc)
           
print('unscheduled')
 
class TestApp(App):
   
def build(self):
       
return MainMenu()
 
if __name__ == "__main__":
   
TestApp().run()
Honestly, I don't even know how this code even works. It seems like the schedule_interval() calls should really pile up, since they're called every 0.1 sec. There must be a more elegant way to structure this code, but I cannot figure out how. I messed with schedule_once() calls inside a loop, but they fail to update the screen until after the loop terminates.


ZenCODE

unread,
Apr 1, 2014, 5:55:34 PM4/1/14
to kivy-...@googlegroups.com
Okay, so this example is a more complex way to handle it, but allows you to generically handle lists of functions and have them accurately update a progress bar.

Here is how I would approach it. We create a ProgressAnimator class that accepts two parameters - the progressbar and the list of functions. It then calls each function sequentially, updating the progressbar accordingly. The one complication here is that the ProgressAnimator needs to be notified when the task is complete - hence the 'callback' parameter passed into each /do_task' worker.

It might be overkill for your purposes as your code works, but could be worthwhile if you will do this regularly. It separates out the progressbar animation logic into a dedicated class making it easier to re-use.

Hope it suits or helps?  ;-)

from kivy.app import App

from kivy.uix.button import Button
from kivy.uix.progressbar import ProgressBar
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from time import sleep


class TaskSupplier(object):
   
"""
    A placeholder object for the function that do the real work.
    Not really required, but I like using class to keep namespaces clean ;-)
    """

   
@staticmethod
   
def do_task(callback):
       
""" Does the heavy lifting/processing. Calls 'callback' when done """
       
print "Processing task...."
        sleep
(0.5)
        callback
()


class ProgressAnimator(object):
   
"""
    This class handles the animation of a 'ProgressBar' instance given a list
    of functions that do the real work. Each of these functions need to accept
    a single 'callback' parameter specifying the function to call when done.
    """

   
def __init__(self, progressbar, callbacks):
       
self.pb = progressbar  # Reference to the progress bar  to update
       
self.pb.max, self.pb.value = len(callbacks), 0
       
self.callbacks = callbacks  # A list of callback functions doing work
       
self.index = 0  # The index of the callback being executed
       
Clock.schedule_once(lambda dt: callbacks[0](self.task_complete), 0.1)

   
def task_complete(self):
       
""" The last called heavy worker is done. See if we have any left """
       
self.index += 1
       
self.pb.value = self.index
       
if self.index < len(self.callbacks):
           
Clock.schedule_once(
               
lambda dt: self.callbacks[self.index](self.task_complete), 0.1)



class MainMenu(BoxLayout):
   
def __init__(self, **kwargs):
       
super(MainMenu, self).__init__(**kwargs)
       
self.orientation = 'vertical'


       
# We add 2 buttons to demonstrate the generic nature of the
       
# ProgressAnimator
        btn
= Button(text="Start 3")
        btn
.bind(on_release=lambda btn: self.trigger(3))
       
self.add_widget(btn)

        btn
= Button(text="Start 6")
        btn
.bind(on_release=lambda btn: self.trigger(6))
       
self.add_widget(btn)

       
self.pb = ProgressBar()
       
self.add_widget(self.pb)

   
def trigger(self, number):
       
""" Start the list of tasks """
       
# The tasklist can be any list of functions. The only requirement is
       
# that each accepts 1 parameter - callback to call when done
        task_list
= [TaskSupplier.do_task for num in xrange(0, number)]
       
ProgressAnimator(self.pb, task_list)

Bill Eaton

unread,
Apr 1, 2014, 7:07:55 PM4/1/14
to kivy-...@googlegroups.com
Zencode,

Thank you for all of your effort. I can tell you put some time into this. It IS more complex, but still more tidy than the Mickey Mouse code I put together.

I wish there was a more direct way to accomplish this. But for now, this will work.

Thanks again.

Leon A

unread,
Oct 15, 2015, 3:09:13 PM10/15/15
to Kivy users support
Hey Zen,

I thought rather than starting a new post i'd take advantage of this one since its related.

I want to create my own custom progress bar and I've got as far as inheriting from ProgressBar and for example creating a new Canvas with a rectangle so in effect changing the
background but I'm trying to get my head around how to actually place my own bar in there to progress? I have searched all of the kivy folders and read through the code but getting no where. For example in the actual progress bar code I cannot see any reference to any graphics instructions other than ::image' .

Hope you understand, Here's what I have so far all im doing is calling the 'top' command in Ubuntu and displaying some info from the CPU as an experiment you can ignore this part ;) I am just interested to understand how to create my own bar to progress .. sorry for the pun. cheers.

#!\usr\bin\env python
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.progressbar import ProgressBar
from kivy.garden import ProgressSpinner
from commands import getoutput
from subprocess import check_output
import ansiconv

Builder.load_string('''
<myPB>:
    value: 400
    max: 500
    canvas:
        Rectangle:
            source: "kapy-bg.jpg"
            size: self.size
            pos: self.pos

<CoolWidget>:
    orientation: '
vertical'    
    Button:
        size_hint: 1, .2        
        text: '
top!'
        on_press: root.getTop()
   
    ProgressBar:
        id: pb
        max: 1000
        value: 200
       
   
    Label:
        id: top_label
        font_size: root.width / 50
        #pos_hint: {'
x': -.7, 'y': .3}
'''
)
class myPB(ProgressBar):
   
def __init__(self, **kwargs):
       
super(myPB, self).__init__(**kwargs)
       
       
print '\n\n TEST PB CREATED \n\n'
   
class Coolwidget(BoxLayout):
   
def __init__(self, **kwargs):
       
super(Coolwidget, self).__init__(**kwargs)
       
self.top_label =  self.ids['top_label']        
       
self.pb = self.ids['pb']
       
self.testpb = myPB
   
def getTop(self):
        output
= getoutput("top -p 1 -n 1 -b ")
       
#output = check_output([])
       
self.pb.value = 900        
       
print ansiconv.to_plain(output)
        output
= ansiconv.to_plain(output) # Convert output so that its usable        
       
self.add_widget(self.testpb(value=400))        
       
#self.top_label.text = output.split()
       
         
class Coolapp(App):
   
def build(self):
       
return Coolwidget()
       

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

ZenCODE

unread,
Oct 16, 2015, 8:04:44 AM10/16/15
to Kivy users support
Hey Leon :-)

Have you looked in the kv? https://github.com/kivy/kivy/blob/master/kivy/data/style.kv

Search for ProgresBar.

You can remvoe these instructions by doing:




<-ProgressBar>:
    # Do your stuff here

Would that not work for you?

Cheers

Leon A

unread,
Oct 16, 2015, 1:02:14 PM10/16/15
to Kivy users support
You Sir are a legend! thanks as always man :) cheers!!
Reply all
Reply to author
Forward
0 new messages