Python - Kivy: Label does not update during a function excution - python-3.x

I saw that there are other topics related to this one but I could make this simple project to work.
I need a label to be updated with the function status while it is running just to let the user know that the program still working while the function is processing, but the label is only updated when the function is completed
Here are the files
my.kv
WindowManager:
ChangeLabel:
<ChangeLabel>:
BoxLayout:
orientation: 'vertical'
padding: 200, 20
Label:
id: labeltxt
text: 'Text to change'
font_size: 30
height: 50
Button:
text:'Run Function'
size_hint_y: None
height: 30
on_release:
root.changeLabel()
Label:
test.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang.builder import Builder
from kivy.core.window import Window
import time
from kivy.clock import Clock
class ChangeLabel(Screen):
def changeLabel(self):
Clock.schedule_once(self.test)
def test(self, dt):
self.ids.labeltxt.text = 'Function is running...'#I wanna this in lablel while function is running
time.sleep(2)# function to run
self.ids.labeltxt.text = 'Completed'
class WindowManager (ScreenManager):
pass
kv = Builder.load_file('my.kv')
class MyApp(App):
def build(self):
return kv
MyApp().run()

I did this solution, now it is working the way I want, but not sure if this is the best way.
test.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang.builder import Builder
from kivy.core.window import Window
import time
from kivy.clock import Clock
class ChangeLabel(Screen):
i = 0
def changeLabel(self):
Clock.schedule_interval(self.test, 0.05)
def test(self, dt):
if self.i == 0:
self.ids.labeltxt.text = 'Function is running...'
self.i = 1
else:
time.sleep(2)# function to run
self.ids.labeltxt.text = 'Completed'
self.i = 0
Clock.unschedule(self.test)
class WindowManager (ScreenManager):
pass
kv = Builder.load_file('my.kv')
class MyApp(App):
def build(self):
return kv
MyApp().run()

Related

Kivy: Error while trying to capture options from MDDropDownMenu

I am new to kivy and am trying to add a DropDown list and trying to capture the options being clicked in a variable, but I seem to run into many errors while doing so.
This is the error that I am getting:
This is my main.py file:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.uix.menu import MDDropdownMenu
from kivymd.uix.behaviors import RectangularElevationBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.config import Config
from kivymd.app import MDApp
from kivy.lang import Builder
Config.set('graphics', 'resizable', False)
class CustomToolbar(ThemableBehavior, RectangularElevationBehavior, MDBoxLayout,):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.md_bg_color = self.theme_cls.primary_color
class MainApp(MDApp):
def __init__(self, **kwargs):
self.VARIABLE = ""
super().__init__(**kwargs)
self.screen = Builder.load_file("main.kv")
menu_items = [{"viewclass": "MDMenuItem","text": "Label1"},{"viewclass":
"MDMenuItem","text": "Label2"}]
self.menu = MDDropdownMenu(
caller=self.screen.ids.drop_item,
items=self.menu_items,
position="center",
width_mult=3,
)
self.menu.bind(on_release=self.menu_callback)
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def menu_callback(self, instance_menu, instance_menu_item):
instance_menu.dismiss()
def build(self):
return self.screen
MainApp().run()
This is my .kv file:
<ScreenManagement>:
id: scr_mngr
MainAppScreen:
name: 'main'
id: main
<CustomToolbar>:
#some widgets here
BoxLayout:
#some widgets here
<MDMenuItem>:
on_release: app.root.change_variable(self.text)
Screen:
MDRaisedButton:
id: drop_item
text: "W"
pos_hint: {"center_x": .9, "center_y": .68}
on_release: app.menu.open()
I am stuck at this for 2 days, please help. Thanks in advance :D
I think your code will work if you build the GUI in the build() method and use Clock.schedule_once() to setup the menu, like this:
class MainApp(MDApp):
def change_variable(self, value):
print("\nvalue=", value)
self.VARIABLE = value
print("\tself.VARIABLE=", self.VARIABLE)
def menu_callback(self, instance_menu, instance_menu_item):
instance_menu.dismiss()
def build(self):
self.VARIABLE = ""
self.screen = Builder.load_file("main.kv")
Clock.schedule_once(self.setup_menu)
return self.screen
def setup_menu(self, dt):
menu_items = [{"viewclass": "MDMenuItem","text": "Label1"},{"viewclass":
"MDMenuItem","text": "Label2"}]
self.menu = MDDropdownMenu(
caller=self.screen.ids.drop_item,
items=menu_items,
position="center",
width_mult=3
)
self.menu.bind(on_release=self.menu_callback)

Cannot display arduino data into kivy window

Im am trying to display my arduino data into my kivy window under label but it says an error " ValueError: Label.text accept only str". Anyone able to help?
Below is my python kivy file
test.py file
import serial
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
class MainWindow(Screen):
def tm(self):
while (1):
with serial.Serial('COM4', 9600) as ser:
x = ser.readline()
return str(x)
ser.close()
class WindowManager(ScreenManager):
pass
hi = Builder.load_file("test.kv")
class MyMainApp(App):
def build(self):
return hi
if __name__ == "__main__":
MyMainApp().run()
test.kv file
WindowManager:
MainWindow:
<MainWindow>:
Label:
size: 75, 50
size_hint: None, None
pos_hint: {'right': 1, 'bottom': 1}
background_color: (1.0, 0.0, 0.0, 1.0)
text: root.x
I see a few problems in your example.
Be careful naming your variables. x for example, is almost always a NumericProperty of the widget already.
When you reference an attribute in python, use self, or the widgets x in your case wil not change.
When you have a loop in kivy, you will block the application. So you either use Clock or threading. I will suggest threading instead of Clock when using serial, because readline() will also block the app if waiting for an input.
You never actually call tm()
So, heres what I would change in MainWindow class, and a few more imports.
import serial
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.properties import StringProperty
from kivy.clock import mainthread
import threading
KV = """
WindowManager:
MainWindow:
<MainWindow>:
Label:
size: 75, 50
size_hint: None, None
pos_hint: {'right': 1, 'bottom': 1}
background_color: (1.0, 0.0, 0.0, 1.0)
text: root.label_text
"""
class MainWindow(Screen):
label_text = StringProperty("")
def __init__(self, **kwargs):
super(MainWindow, self).__init__(**kwargs)
threading.Thread(target=self.tm).start()
def tm(self):
while (1):
with serial.Serial('COM4', 9600) as ser:
value = ser.readline()
ser.close()
self.update_label(value.decode("ascii").strip())
#mainthread
def update_label(self, value):
self.label_text = value
class WindowManager(ScreenManager):
pass
class MyMainApp(App):
def build(self):
return Builder.load_string(KV)
if __name__ == "__main__":
MyMainApp().run()
Be sure to use mainthread when changing any ui element in another thread. In this case when you change a stringproperty that will change the display.

dynamic binding of functions not working in kivy

I wanted to make a dynamic class of a group of widget so whenever I add them to my main app, I had to only bring in the changes from somewhere(e.g. a python file, class etc, here I've done them in explicit lists), but binding the widget with properties like "on_text" which respond to events didn't really work, other properties like 'text', 'hint_text' worked perfectly but on_text doesn't really budge. I can't figure out the cause for this because I've checked the correct object being passed along the functions, below is my code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
print(cur_properties[attr])
curwidget.bind(**(cur_properties[attr]))
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'on_text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
And, in the kivy file:
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: 'testlable'
TextInput:
id: mytext
hint_text: 'some test'
What is the cause? And how can I fix it?
There are 2 ways to make a binding of a property:
*.py
object.bind(property=callback)
*.kv
object:
on_property: callback
So in the case of making the connection in python you should not use on_text, but text.
To verify the change, the Label can not be edited from the GUI, so I will use the TextInput to write on the Label:
*.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class Dynamic_TextInput(BoxLayout):
def __init__(self,changes=None, **kwargs):
super().__init__(**kwargs)
self.widgets = {'Mylabel':self.ids.mylabel,
'Myinput': self.ids.mytext}
self.Change(changes)
def Change(self, changes=None, **kwargs):
if changes:
for change in changes:
curwidget = self.widgets[change[0]]
cur_properties = change[1]
for attr in cur_properties.keys():
if attr=='bind':
print("The cur properties are: ")
curwidget.bind(**(cur_properties[attr]))
print(cur_properties[attr])
else:
setattr(curwidget, attr, cur_properties[attr])
class mainwidget(BoxLayout):
myobj1 = ObjectProperty()
myobj2 = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.orientation='vertical'
change1=[('Mylabel', {'text':'firstchange',
'bind':{'text':lambda *_: print('something')}
}),
('Myinput', {'hint_text': 'Changed hint text'})
]
self.add_widget(Dynamic_TextInput(changes=change1))
self.add_widget(Dynamic_TextInput())
class MainApp(App):
def build(self):
return mainwidget()
if __name__ == '__main__':
MainApp().run()
*.kv
#:kivy 1.10.0
<Dynamic_TextInput>:
myobj1: mylabel
myobj2: mytext
orientation: 'horizontal'
Label:
id: mylabel
text: mytext.text # <----
TextInput:
id: mytext
hint_text: 'some test'

Newbie at Kivy trying to create stopwatch

I'm attempting to create a stopwatch in Kivy. I've completed a skeleton on Sublime Text 3 (as shown in the code below). When I run the code on Sublime Text, a window opens, but Python crashes in 4.1s.
Here is the code in question:
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.lang import Builder
import time
class CrudeTimerGrid(GridLayout):
# Initialise timer with input start time
def __init__(self,start_time):
time = NumericProperty(start_time)
def tick(self):
if self.time > 0:
self.time -= 1
else:
pass
def start(self):
Clock.schedule_interval(self.tick,1)
def pause(self):
Clock.unschedule()
# incomplete code
def reset(self):
pass
class CrudeTimerApp(App):
def build(self):
# Testing timer by initialising timer with 20 seconds
return CrudeTimerGrid(20)
Builder.load_string('''
<CrudeTimerGrid>
id: timer
rows: 2
# insert formatting here
BoxLayout:
Label:
text: timer.time
BoxLayout:
Button:
text: "Start"
on_press: timer.start()
Button:
text: "Pause"
on_press: timer.pause()
Button:
text: "Reset"
on_press: timer.reset()
''')
CrudeTimerApp().run()
Newbie at StackOverflow too, so please let me know if any other info is needed. Thanks for your help!
Main problem is here:
def __init__(self,start_time):
time = NumericProperty(start_time)
You should define Kivy properties at class level, please read this. Code'll stop crash if you change it like this:
class CrudeTimerGrid(GridLayout):
time = NumericProperty(0)
There're also few other changes you should do to make it finally work, here's full code version:
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.lang import Builder
import time
class CrudeTimerGrid(GridLayout):
time = NumericProperty(0)
def tick(self, *_):
if self.time > 0:
self.time -= 1
else:
pass
def start(self, *_):
self.cb = Clock.schedule_interval(self.tick,1)
def pause(self):
Clock.unschedule(self.cb)
# incomplete code
def reset(self, *_):
pass
class CrudeTimerApp(App):
def build(self):
# Testing timer by initialising timer with 20 seconds
return CrudeTimerGrid(time=20)
Builder.load_string('''
<CrudeTimerGrid>
id: timer
rows: 2
# insert formatting here
BoxLayout:
Label:
text: str(timer.time)
BoxLayout:
Button:
text: "Start"
on_press: timer.start()
Button:
text: "Pause"
on_press: timer.pause()
Button:
text: "Reset"
on_press: timer.reset()
''')
CrudeTimerApp().run()
Upd:
Any idea why 2 arguments are input into tick in the first place?
tick passed to Clock.schedule_interval to be called. This function schedules it's callback with additional parameter (as many other functions in Kivy also do). You can read a bit more about it in documentation.
You defined self.cb in order to reference it in Clock.unschedule,
correct?
You can schedule many different functions with different intervals calling Clock.schedule_interval multiple times. But how can Clock.unschedule know which concrete of them to unschedule? In order to make Clock.unschedule know what to unschedule you should pass to it value returned by Clock.schedule_interval. Here's documentation section where this mechanism described.

return a BoxLayout inside another one in Kivy?

I'm trying to make a simple application in Python 3.5 and kivy that starts with a simple screen and when you click on it, goes to another one which shows 3 lists that let you chose the data:
The Python file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.listview import ListItemButton
from kivy.properties import ListProperty
from dataTopy import rlists
# Transition des ecrans:
class MainScreen(Screen):
pass
class AnotherScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
presentation = Builder.load_file("ex44.kv")
#
class FirstListItemButton(ListItemButton):
pass
class SecondListItemButton(ListItemButton):
pass
class ThirdListItemButton(ListItemButton):
pass
class Ex44(BoxLayout):
d1 = ListProperty([str(i) for i in range(1990,2014)] )
d2 = ListProperty(['']*100)
d3 = ListProperty(['']*100)
def change(self,c):
try: self.d2,self.d3 = rlists(int(c.text))
except:
import os
CurDir = os.getcwd()
print('Can not find data in ' + CurDir)
def change1(self,c):
print('M => '+c.text)
def change2(self,c):
print('F => '+c.text)
class Ex44App(App):
def build(self):
return presentation
if __name__ == '__main__':
Ex44App().run()
The kivy file:
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import ex44 ex44
ScreenManagement:
transition: FadeTransition()
MainScreen:
AnotherScreen:
<MainScreen>:
name: "main"
Button:
on_release: app.root.current = "other"
text: "Next Screen"
font_size: 50
<AnotherScreen>:
name: "other"
BoxLayout:
Ex44
Button:
color: 0,1,0,1
font_size: 25
size_hint: 0.3,0.2
text: "Back Home"
on_release: app.root.current = "main"
pos_hint: {"right":1, "top":1}
<FirstListItemButton>:
on_press: app.root.change(*args)
<SecondListItemButton>:
on_press: app.root.change1(*args)
<ThirdListItemButton>:
on_press: app.root.change2(*args)
<Ex44>:
ListView:
adapter:
ListAdapter(data=root.d1,
selection_mode='single',
cls=ex44.FirstListItemButton)
ListView:
adapter:
ListAdapter(data=root.d2,
selection_mode='single',
cls=ex44.SecondListItemButton)
ListView:
adapter:
ListAdapter(data=root.d3,
selection_mode='single',
cls=ex44.ThirdListItemButton)
When I try to run the app, it tells me: "Unknown class "
It is weird because the class Ex44 works alone but not when I'm trying to add it into the main application logic.
I've tried to return a widget instead of a BoxLayout for the class, to return Ex44 alone in the kivy file, etc. but I always get the same error in return.
Is it possible to return a BoxLayout inside another one in Kivy?
You are building the kv file too soon (before the class is defined). move the Builder.from_file call to the build method
...
def build(self):
return Builder.load_file("ex44.kv")

Resources