un_active button until function finished in kivy - python

I am using kivy for UI. there is a Time_consuming function and when it runs, kivy ui will goes in black, so I used Threading.
I want to disable buttons until Time_consuming function finishes, and then enable button again. I have been used something like below:
from threading import Thread
from kivy.clock import Clock
from functools import partial
def run():
self.run_button.disabled=True
self.back_button.disabled=True
t=Thread(target=Time_consuming(), args=())
t.start()
Clock.schedule_interval(partial(disable, t.isAlive()), 8)
def disable(t, what):
print(t)
if not t:
self.run_button.disabled=False
self.back_button.disabled=False
but this dose not work, t.isAlive() in disable() even when Time_consuming() finishes, is True. where is the problem ?
question2: another problem is, Clock.schedule_interval will continue to run for ever. how can stop it when function finished?

I see you already answered your question. I was also building an example app to help you, while you were answering. I think it still can be of value to you and therefore I am posting it. One button shows you threading and the other one scheduling once. Happy coding :).
from kivy.app import App
from kivy.base import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
import time
import threading
Builder.load_string("""
<rootwi>:
label_to_be_changed1: label_to_be_changed1
label_to_be_changed2: label_to_be_changed2
button1: button1
button2: button2
orientation: 'vertical'
Button:
id: button1
text:'Button1 - Threading'
on_press: root.change_Label_text1()
Button:
id: button2
text: 'Button2 - schedule'
on_press: root.change_Label_text2()
Label:
id: label_to_be_changed1
Label:
id: label_to_be_changed2
""")
class rootwi(BoxLayout):
label_to_be_changed1 = ObjectProperty()
label_to_be_changed2 = ObjectProperty()
button1 = ObjectProperty()
button2 = ObjectProperty()
def change_Label_text1(self):
self.button1.disabled = True
threading.Thread(target=self.timeconsuming).start()
def timeconsuming(self):
#do your stuff
time.sleep(5)
self.label_to_be_changed1.text = 'thread has ended'
self.button1.disabled = False
def change_Label_text2(self):
self.button2.disabled = True
Clock.schedule_once(self.change_Label_text2_callback, 4)
def change_Label_text2_callback(self, *largs):
self.label_to_be_changed2.text = 'schedule has ended'
self.button2.disabled = False
class MyApp(App):
def build(self):
return rootwi()
if __name__ == '__main__':
MyApp().run()

I have found that:
question1: pass t instead of t.isAlive().this :
Clock.schedule_interval(partial(disable, t.isAlive()), 8)
changed to :
Clock.schedule_interval(partial(disable, t), 8)
question2: If the disable() returns False, the schedule will be canceled and won’t repeat.
def run_model():
self.run_button.disabled=True
self.back_button.disabled=True
t=Thread(target=run, args=())
t.start()
Clock.schedule_interval(partial(disable, t), 8)
def disable(t, what):
if not t.isAlive():
self.run_button.disabled=False
self.back_button.disabled=False
return False

Related

Problem with python threding and kivy add_widget method

Here is my a simple code, which uses Python socket and threading. Here the thread cannot able to run a function of adding widget List dynamically.
Note: The thread is able to successful run all function except the function for add_widget.
Thanks in Advance.
from kivymd.app import MDApp
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivymd.uix.textfield import MDTextField, MDTextFieldRound
from kivymd.uix.button import MDFillRoundFlatButton, MDIconButton,MDRectangleFlatButton
import sys
import socket
from kivy.uix.scrollview import ScrollView
from kivymd.uix.list import OneLineListItem, IRightBody
import threading
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.clock import mainthread
kv="""
ScreenManager
LoginPage:
MsgPlatform:
<LoginPage>:
name:'login'
MDTextField:
id:ip
hint_text:'Enter ip-Addresh'
pos_hint:{'center_x':0.5,'center_y':0.7}
size_hint_x:0.3
MDTextField:
id:port
hint_text:'Enter port'
pos_hint:{'center_x':0.5,'center_y':0.6}
size_hint_x:0.3
MDFillRoundFlatButton:
text:'Join'
pos_hint:{'center_x':0.5,'center_y':0.5}
on_release:
root.login()
root.manager.transition.direction='left'
root.manager.transition.duration= 0.2
root.manager.current='msgplatform'
<MsgPlatform>:
name:'msgplatform'
ScrollView:
MDList:
id:container
ScrollView:
MDList:
id:cont
MDIconButton:
icon:'send.jpg'
pos_hint:{'center_x':0.95,'center_y':0.05}
size_hint:0.04,0.03
on_release:
root.send_msg()
root.clear_msg()
"""
s=socket.socket()
data=''
class recvThread(object):
def __init__(self):
thread = threading.Thread(target=self.recv, args=())
thread.daemon = True
thread.start()
def recv(self):
while True:
global data
data=str(s.recv(5000),'utf-8')
if str(data)!='':
MsgPlatform().recvMsg()
class LoginPage(Screen):
def login(self):
host=str(self.ids.ip.text)
port=int(self.ids.port.text)
s.connect((host,port))
class MsgPlatform(Screen):
def on_pre_enter(self):
recvThread()
def recvMsg(self):
cont=self.ids.container
global data
print(data)
c=cont.add_widget(OneLineListItem(text=str(data)))
sm=ScreenManager()
sm.add_widget(LoginPage(name='login'))
sm.add_widget(MsgPlatform(name="msgplatform"))
class MainApp(MDApp):
def build(self):
script=Builder.load_string(kv)
return script
if __name__=='__main__':
MainApp().run()
Here is Thread:
class recvThread(object):
def __init__(self):
thread = threading.Thread(target=self.recv, args=())
thread.daemon = True
thread.start()
def recv(self):
while True:
global data
data=str(s.recv(5000),'utf-8')
if str(data)!='':
MsgPlatform().recvMsg()
Here is the thread implementation function.
def recvMsg(self):
cont=self.ids.container
global data
print(data)
c=cont.add_widget(OneLineListItem(text=str(data)))

Python/Kivy passing variables

Struggling to pass a variable to kivy window. I have read similar threads all over the place but none of the fixes seem to work for me. Im sure this is simple to someone who knows their way around tiny, unfortunately I don't.
main.py
import kivy
from kivy.uix.togglebutton import ToggleButton
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.app import App
kivy.require('1.10.0')
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
print("launching")
def __init__(self):
super(Controller, self).__init__()
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(dt):
if b.get_light(1, 'on')== True:
#print("down") # When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
else:
#print("up")# When this line is commented out I get an continuous accurate update on the status of the light, showing that its working.
return 'down' # This is the part I want passed to the state criteria in the ivy window
class ActionApp(App):
def build(self):
Clock.schedule_interval(Controller.update, 1.0 / 60.0)
return Controller()
myApp = ActionApp()
myApp.run()
action.kv
<Controller>:
cols: 4
rows: 3
spacing: 10
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: Controller.update # This is the part that is throwing up the error.
The error:
11: #on_release: root.KitchenSpot1(False)
12: #state1 = app.update.h
>> 13: state: Controller.update
14:
15:
...
NameError: name 'Controller' is not defined
Thanks in advance to anyone that can help me.
Make update an instance method and use a StringProperty to update state property in your kv:
main.py:
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.gridlayout import GridLayout
from kivy.uix.togglebutton import ToggleButton
from phue import Bridge
import nest
b = Bridge('xxx.xxx.x.xxx')
b.connect()
b.get_api()
lights = b.lights
class Controller(GridLayout):
state = StringProperty('normal') # <<<<<<<<<<<<
def __init__(self, **kwargs):
super(Controller, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 60.0)
def KitchenSpot1(self,state):
lights[0].name
lights[0].on = state
def update(self, dt):
if b.get_light(1, 'on'):
self.state = 'down' # <<<<<<<<<<<<
else:
self.state = 'normal' # <<<<<<<<<<<<
class ActionApp(App):
def build(self):
return Controller()
if __name__ == "__main__":
myApp = ActionApp()
myApp.run()
action.kv:
<Controller>:
cols: 4
rows: 3
spacing: 10
state: "normal" # <<<<<<<<<<<<
ToggleButton:
id: KitchenSpot1Toggle
text: "Kitchen Spot 1"
on_press: root.KitchenSpot1(True)
#on_release: root.KitchenSpot1(False)
#state1 = app.update.h
state: root.state # <<<<<<<<<<<<
Here is a more generic simplified answer from the kivy documentation, look for the section called "Keyword arguments and init()" because there are some other ways to do it as well.
The following code passes myvar to the build() method of MyApp. It does this by over-riding the init() of the Kivy App class by a new init() that calls App.init() and then continues with whatever extra initialisation you want. You can then store variables in the MyApp class instances and use them in build().
from kivy.app import App
from kivy.uix.label import Label
myvar = 'Hello Kivy'
class MyApp(App):
def __init__(self, myvar, **kwargs):
super(MyApp, self).__init__(**kwargs)
self.myvar = myvar
def build(self):
widget = Label(text=self.myvar)
return widget
if __name__ == '__main__':
MyApp(myvar).run()

Python How to influence running program

Button Start alarm starts ringing of alarm. I want to stop ringing by button Stop alarm. I don't know to influece running program. How must I repair function stop_alarm?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
import winsound
class ControlPanel(BoxLayout):
def __init__(self, **kwargs):
# make sure we aren't overriding any important functionality
super(ControlPanel, self).__init__(**kwargs)
self.alarm_status = True
self.orientation = "vertical"
butOn = Button(text = "Start alarm: ", on_release = self. start_alarm)
butStop = Button(text = "Stop alarm: ", on_release = self.stop_alarm)
self.add_widget(butOn)
self.add_widget(butStop)
def start_alarm(self, obj):
while self.alarm_status == True:
winsound.PlaySound("alarm.wav", winsound.SND_FILENAME)
def stop_alarm(self, obj):
self.alarm_status = False
class LifeApp(App):
def build(self):
return ControlPanel()
if __name__ == '__main__':
LifeApp().run()
The main problem with your code is that you have a while loop on your main GUI thread. So what you need to do is to run the start_alarm on a different thread than your main GUI thread for the GUI thread to be responsive.
As for not being able to play alarm next time, you didn't set the alarm_status flag to True again.Once you have that you can start and stop the sound as many times you want.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
import time
import winsound
import threading
class ControlPanel(BoxLayout):
def __init__(self, **kwargs):
# make sure we aren't overriding any important functionality
super(ControlPanel, self).__init__(**kwargs)
self.alarm_status = True
self.orientation = "vertical"
butOn = Button(text = "Start alarm: ", on_release = self.start_thread)
butStop = Button(text = "Stop alarm: ", on_release = self.stop_alarm)
self.add_widget(butOn)
self.add_widget(butStop)
def start_alarm(self, obj):
while self.alarm_status == True:
winsound.PlaySound("alarm.wav", winsound.SND_FILENAME)
#Set Alarm_Status True so that next time it works
self.alarm_status = True
def stop_alarm(self, obj):
self.alarm_status = False
#Function to run your start_alarm on a different thread
def start_thread(self,obj):
t1 = threading.Thread(target=self.start_alarm,args=(obj,))
t1.start()
class LifeApp(App):
def build(self):
return ControlPanel()
if __name__ == '__main__':
LifeApp().run()
Hope this helps!

Update labels in a separate worker (Process instance)

I do have several screens. One of them (DataScreen) contains 8 labels which should show the current sensor values. Sensors are read by a separate process (which is started from the MainScreen). The process itself is an instance of multiprocessing.Process.
I can get a reference to the labels by sensor_labels = self.manager.get_screen('data').l
However, I cannot figure out how to change them within the subprocess. I can change them from any function which is not a separate process, simply by doing something like:
for item in sensor_labels:
item.text = 'Update'
Unfortunately, it seems to be more difficult to pass the reference of the sensor_labels to the worker. If I pass them as argument both processes (kivy and the worker) seem to share the same object (the id is the same). However, if I change label.text = 'New Text' nothing changes in Kivy.
Why is the id of both objects the same, but the text is not changed ?
And how can I share a Kivy label object with another process ?
Here is my working minimal example
#! /usr/bin/env python
""" Reading sensor data
"""
from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'multi')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.stacklayout import StackLayout
from multiprocessing import Process, Queue, Array
# all other modules
import time
import numpy as np
from multiprocessing import Lock
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.n_probes = 8
#staticmethod
def read_sensors(qu_rx, sensor_labels, lock):
while True:
if not qu_rx.empty():
message = qu_rx.get()
if message == 'STOP':
print('Worker: received poison pill')
break
data = np.random.random()
print('ID of labels in worker: {}'.format(id(sensor_labels)))
print('Text of labels in worker:')
lock.acquire()
for label in sensor_labels:
label.text = '{0:2f}'.format(data)
print(label.text)
lock.release()
time.sleep(5)
def run_worker(self, *args, **kwargs):
self.qu_tx_worker = Queue()
lock = Lock()
# this is a reference to the labels in the DataScreen class
self.sensor_labels = self.manager.get_screen('data').l
self.worker = Process(target=self.read_sensors,
args=(self.qu_tx_worker, self.sensor_labels, lock))
self.worker.daemon = True
self.worker.start()
def stop_worker(self, *args, **kwargs):
self.qu_tx_worker.put('STOP')
print('Send poison pill')
self.worker.join()
print('All worker dead')
print('ID of labels in Kivy: {}'.format(id(self.sensor_labels)))
print('Label text in Kivy:')
for label in self.sensor_labels:
print(label.text)
class DataScreen(Screen):
def __init__(self, **kwargs):
layout = StackLayout()
super(DataScreen, self).__init__(**kwargs)
self.n_probes = 8
self.label_text = []
for i in range(self.n_probes):
self.label_text.append(StringProperty())
self.label_text[i] = str(i)
self.l = []
for i in range(self.n_probes):
self.l.append(Label(id='l_{}'.format(i),
text='Start {}'.format(i),
font_size='60sp',
height=20,
width=20,
size_hint=(0.5, 0.2)))
self.ids.stack.add_widget(self.l[i])
def change_text(self):
for item in self.l:
item.text = 'Update'
Builder.load_file('phapp.kv')
class MyApp(App):
"""
The settings App is the main app of the pHBot application.
It is initiated by kivy and contains the functions defining the main interface.
"""
def build(self):
"""
This function initializes the app interface and has to be called "build(self)".
It returns the user interface defined by the Builder.
"""
sm = ScreenManager()
sm.add_widget(MainScreen())
sm.add_widget(DataScreen())
# returns the user interface defined by the Builder
return sm
if __name__ == '__main__':
MyApp().run()
And the .kv file:
<MainScreen>:
name: 'main'
BoxLayout:
orientation: 'vertical'
Button:
text: 'Start Application'
font_size: 40
on_release: root.run_worker()
Button:
text: 'Stop Application'
font_size: 40
on_release: root.stop_worker()
Button:
text: 'Go to data'
font_size: 40
on_release: app.root.current = 'data'
Button:
text: 'Exit'
font_size: 40
on_release: app.stop()
<DataScreen>:
name: 'data'
StackLayout:
id: stack
orientation: 'lr-tb'
BoxLayout:
Button:
size_hint: (0.5, 0.1)
text: 'Update'
font_size: 30
on_release: root.change_text()
Button:
size_hint: (0.5, 0.1)
text: 'Back to main menu'
font_size: 30
on_release: app.root.current = 'main'
It looks like you might misunderstand how multiprocessing works.
When you start a new Process with the multiprocessing library it creates a new process and pickles all the code needed to run the target function. Any updates you make to the labels passed are happening in the worker process and will NOT reflect in the UI process.
To get around this you have to use one of these methods to exchange data between the worker and UI processes: https://docs.python.org/2/library/multiprocessing.html#exchanging-objects-between-processes. Since you already have a queue you can do something like this:
Put your read_sensors into worker.py passing a tx and rx Queue where tx is used to send to the UI and rx is used to read from the UI.
#! /usr/bin/env python
""" Reading sensor data
"""
import time
import numpy as np
def read_sensors(rx,tx, n):
while True:
if not rx.empty():
message = rx.get()
if message == 'STOP':
print('Worker: received poison pill')
break
#: Sensor value for each label
data = [np.random.random() for i in range(n)]
#: Formatted data
new_labels = ['{0:2f}'.format(x) for x in data]
print('Text of labels in worker: {}'.format(new_labels))
#lock.acquire() # Queue is already safe, no need to lock
#: Put the formatted label in the tx queue
tx.put(new_labels)
# lock.release() # Queue is already safe, no need to unlock
time.sleep(5)
Then in your app use the Clock to call an update handler to check the tx Queue for updates periodically. When quitting, the UI can tell the worker to stop by putting a message in the rx queue.
#! /usr/bin/env python
""" Reading sensor data
"""
from kivy.config import Config
from kivy.clock import Clock
Config.set('kivy', 'keyboard_mode', 'multi')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty, NumericProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.stacklayout import StackLayout
from multiprocessing import Process, Queue
#: Separate worker file so a separate app is not opened
import worker
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.n_probes = 8
#: Hold the update event
self._event = None
def read_worker(self,dt):
""" Read the data from the worker process queue"""
#: Get the data from the worker (if given) without blocking
if self.tx.empty():
return # No data, try again later
#: The worker put data in the queue, update the labels
new_labels = self.tx.get()
for label,text in zip(self.sensor_labels,new_labels):
label.text = text
def run_worker(self, *args, **kwargs):
self.rx = Queue() #: Queue to send data to worker process
self.tx = Queue() #: Queue to recv from worker process
self.sensor_labels = self.manager.get_screen('data').l
self.worker = Process(target=worker.read_sensors,
args=(self.rx,self.tx,self.n_probes))
self.worker.daemon = True
self.worker.start()
# Check the tx queue for updates every 0.5 seconds
self._event = Clock.schedule_interval(self.read_worker, 0.5)
def stop_worker(self, *args, **kwargs):
self.rx.put('STOP')
print('Send poison pill')
self.worker.join()
print('All worker dead')
#: Stop update loop
if self._event:
self._event.cancel()
print('ID of labels in Kivy: {}'.format(id(self.sensor_labels)))
print('Label text in Kivy:')
for label in self.sensor_labels:
print(label.text)
class DataScreen(Screen):
def __init__(self, **kwargs):
layout = StackLayout()
super(DataScreen, self).__init__(**kwargs)
self.n_probes = 8
self.label_text = []
for i in range(self.n_probes):
self.label_text.append(StringProperty())
self.label_text[i] = str(i)
self.l = []
for i in range(self.n_probes):
self.l.append(Label(id='l_{}'.format(i),
text='Start {}'.format(i),
font_size='60sp',
height=20,
width=20,
size_hint=(0.5, 0.2)))
self.ids.stack.add_widget(self.l[i])
def change_text(self):
for item in self.l:
item.text = 'Update'
Builder.load_file('phapp.kv')
class MyApp(App):
"""
The settings App is the main app of the pHBot application.
It is initiated by kivy and contains the functions defining the main interface.
"""
def build(self):
"""
This function initializes the app interface and has to be called "build(self)".
It returns the user interface defined by the Builder.
"""
sm = ScreenManager()
sm.add_widget(MainScreen())
sm.add_widget(DataScreen())
# returns the user interface defined by the Builder
return sm
if __name__ == '__main__':
MyApp().run()
Also the multiprocessing.Queue class is already 'process' safe, you don't need to use a lock around it. If you have a separate process for each sensor you can use the same idea just more queues.
Kivy doesn't provide IPC, and the GUI elements should be updated only in the main thread. To implements IPC you could use OSC to facilitate that, see this. If you move reading the sensors inside threads, then read this and this if you haven't done so yet.

Change screens with python logic (Kivy Screen manager)

I can't find the syntax for setting on_press in python code to change the screen anywhere. I keep getting errors for anything like Button(text = 'hi', on_press = self.current = 'start_menu. Here's the code and it works as is.
class LoadMenu(Screen):
def __init__(self, **kwargs):
super(LoadMenu, self).__init__(**kwargs)
Clock.schedule_once(self.update)
def update(self, dt):
L = [x for x in range(len(os.listdir('saves')))]
for x in L:
x = self.add_widget(Button(text = os.listdir('saves')[x]))
I haven't positioned the buttons so they just are on top of each other but I can fix that later. What I need to happen is for each button to change to the play screen on press so that will be the same for each button but I also need each one to load the Shelve file they a referencing.(I know I'll need another function for that) Can I have an on_press trigger two events at once, and again how do I set it in the python code?
Consider the following programme:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import StringProperty
dirlist = ['aaa', 'bbb', 'ccc', 'ddd']
class MyButton(Button):
prop = StringProperty('')
def on_press(self):
print "Class-defined on_press handler (I'm {})".format(self.text)
def other_on_press_handler(sender):
print "other_on_press_handler, from {}".format(sender.text)
def some_func(text):
print "yeah: " + text
class LoadMenu(Screen):
def __init__(self, **kwargs):
super(LoadMenu, self).__init__(**kwargs)
Clock.schedule_once(self.update)
def on_press_handler(self, sender):
print "on_press_handler, from {}".format(sender.text)
self.parent.current = 'sc2'
def yet_another_on_press_handler(self, sender):
print "yet_another_on_press_handler, from {}".format(sender.text)
self.parent.current = 'sc2'
def update(self, dt):
for x in range(len(dirlist)):
my_b = Button(text = dirlist[x], on_press=self.on_press_handler)
self.parent.ids.button_container.add_widget(my_b)
if x > 1:
my_b.bind(on_press=other_on_press_handler)
if x == 3:
my_b.bind(on_press=lambda sender: some_func("Whoa, lambda was here ({})".format(sender.text)))
for x in range(len(dirlist)):
my_b = MyButton(text = 'my '+ dirlist[x], prop="{} {}".format(dirlist[x], x))
self.parent.ids.button_container.add_widget(my_b)
my_b.bind(on_press=self.yet_another_on_press_handler)
root = Builder.load_string("""
ScreenManager:
LoadMenu:
name: 'sc1'
GridLayout:
cols: 4
id: button_container
Screen:
name: 'sc2'
BoxLayout:
Button:
text: "Go back"
on_press: root.current = 'sc1'
""")
class MyApp(App):
def build(self):
return root
if __name__ == '__main__':
a = MyApp()
a.run()
Let's start by looking at the update method in LoadMenu: In the first loop, a bunch of buttons is generated, and each receives an on_press callback at creation. The last two buttons in the loop get bound to another callback, and the last example shows how to use a lambda expression to generate a callback.
In the second for loop, we instantiate object of class MyButton, a child of Button. Note that we also define an on_press handler in the class definition; this gets called in addition to other functions we may have bound.
But really, this is actually all pretty nicely explained in the kivy Events and Properties docs.

Categories