Search code examples
pythontimerlabelkivyupdating

How to update text of a label in Kivy, Python


I have made a simple timer program and a box layout with a label and a button in Kivy. Whenever the button is pressed, I want the timer to start and the label to display the time passed. I followed this tutorial: https://www.youtube.com/watch?v=cggCobcS3vU and then added some of my own stuff to the code, but the label only displays the initial text set in the .kv file. Any fix?

timer.py

import time
from kivy.app import App
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.properties import StringProperty, ObjectProperty




class MyApp(BoxLayout):
    def __init__(self, **kwargs):
        super(MyApp, self).__init__(**kwargs)
        self.output = ''
    def update_label(self):
        self.lbl1.text = str(self.output)
    def count(self, *varargs):

        timeLoop = True

        Sec = 0
        Min = 0
        Hrs = 0


        while timeLoop == True:
            self.update_label()
            print(str(self.output))
            Sec += 1
            time.sleep(1)
            if Sec == 60:
                Sec = 0
                Min += 1
            if Min == 60:
                Min = 0
                Hrs += 1
            if Sec <= 9 and Min <=9 and Hrs <= 9:
                self.output = '0' + str(Hrs) +'.'+ '0' + str(Min) + "." + '0' + str(Sec)
            elif Sec <= 9 and Min <=9 and Hrs >=9:
                self.output = str(Hrs) + '.'+ '0' + str(Min) + "." + '0' + str(Sec)
            elif Sec <= 9 and Min >=9 and Hrs >=9:
                self.output = str(Hrs) + '.'+ str(Min) + "." + '0' + str(Sec)
            elif Sec >= 9 and Min >=9 and Hrs >=9:
                self.output = str(Hrs) + '.'+ str(Min) + "." + str(Sec)
            elif Sec >= 9 and Min >=9 and Hrs <=9:
                self.output = '0' + str(Hrs) +'.'+ str(Min) + "." + str(Sec)
            elif Sec >= 9 and Min <= 9 and Hrs <=9:
                self.output = '0' + str(Hrs) +'.'+ '0' + str(Min) + "." + str(Sec)
            elif Sec >= 9 and Min <=9 and Hrs >= 9:
                self.output = str(Hrs) +'.'+ '0' + str(Min) + "." + str(Sec)
            elif Sec <= 9 and Min >= 9 and Hrs <=9:
                self.output =  '0' + str(Hrs) +'.'+ str(Min) + "." + '0' + str(Sec)

class MainApp(App):        
    def build(self):
        c = MyApp()

        return c
if __name__ == '__main__':
    MainApp().run()  

mainapp.kv

<MyApp>:
    lbl1: label1
    BoxLayout:
        size: root.size
        Button:
            id: button1
            text: "Change text"
            on_press: root.count()
        Label:
            id: label1
            text: "hi"

I am aware of this: How to change text of a label in the kivy language with python

and this: https://groups.google.com/forum/#!topic/kivy-users/mdqPQYBWEU8

But neither seems to work for me.


Solution

  • Blocking tasks such as time.sleep() together with an infinite loop are not GUI friendly since it lives in an event loop, so blocking tasks will not allow handling other events such as changing the size of the window, moving the window, etc.

    Kivy can create periodic tasks without using True or time.sleep(), for this Clock is used:

    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.clock import Clock
    
    
    class MyApp(BoxLayout):
        def __init__(self, **kwargs):
            super(MyApp, self).__init__(**kwargs)
            self.output = ''
    
        def update_label(self):
            self.lbl1.text = str(self.output)
    
        def count(self, *varargs):
            self.Sec = 0
            self.Min = 0
            self.Hrs = 0
            Clock.schedule_interval(self.on_timeout, 1)
    
        def on_timeout(self, *args):
            self.update_label()
            self.Sec += 1
            if self.Sec == 60:
                self.Sec = 0
                self.Min += 1
            if self.Min == 60:
                self.Min = 0
                self.Hrs += 1
            if self.Sec <= 9 and self.Min <=9 and self.Hrs <= 9:
                self.output = '0' + str(self.Hrs) +'.'+ '0' + str(self.Min) + "." + '0' + str(self.Sec)
            elif self.Sec <= 9 and self.Min <=9 and self.Hrs >=9:
                self.output = str(self.Hrs) + '.'+ '0' + str(self.Min) + "." + '0' + str(self.Sec)
            elif self.Sec <= 9 and self.Min >=9 and self.Hrs >=9:
                self.output = str(self.Hrs) + '.'+ str(self.Min) + "." + '0' + str(self.Sec)
            elif self.Sec >= 9 and self.Min >=9 and self.Hrs >=9:
                self.output = str(self.Hrs) + '.'+ str(self.Min) + "." + str(self.Sec)
            elif self.Sec >= 9 and self.Min >=9 and self.Hrs <=9:
                self.output = '0' + str(self.Hrs) +'.'+ str(self.Min) + "." + str(self.Sec)
            elif self.Sec >= 9 and self.Min <= 9 and self.Hrs <=9:
                self.output = '0' + str(self.Hrs) +'.'+ '0' + str(self.Min) + "." + str(self.Sec)
            elif self.Sec >= 9 and self.Min <=9 and self.Hrs >= 9:
                self.output = str(self.Hrs) +'.'+ '0' + str(self.Min) + "." + str(self.Sec)
            elif self.Sec <= 9 and self.Min >= 9 and self.Hrs <=9:
                self.output =  '0' + str(self.Hrs) +'.'+ str(self.Min) + "." + '0' + str(self.Sec)
    
    class MainApp(App):        
        def build(self):
            c = MyApp()
    
            return c
    if __name__ == '__main__':
        MainApp().run()  
    

    Plus:

    Instead of doing a tedious calculation to obtain the time we can reduce it using the libraries:

    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.clock import Clock
    from datetime import datetime
    
    
    class MyApp(BoxLayout):
        def count(self, *varargs):
            self.start = datetime.now()
            Clock.schedule_interval(self.on_timeout, 1)
    
        def on_timeout(self, *args):
            d = datetime.now() - self.start
            self.lbl1.text = datetime.utcfromtimestamp(d.total_seconds()).strftime("%H.%M.%S")
    
    class MainApp(App):        
        def build(self):
            c = MyApp()
    
            return c
    if __name__ == '__main__':
        MainApp().run()