Some of Kivy’s built-in events have propagation mechanisms, but on_press() and on_release() do not. Custom events can also propagate, but the propagation mechanism itself must be completely self-written. This article presents example code for these propagations.
Propagation of on_press()
For more information on how Kivy’s events work, how propagation works, and how to create custom events, please see the following article.
Here is example code for propagation of on_press(). on_press() and on_release() do not have a propagation mechanism, so they do not work like on_touch_*(). However, they do function as button events, so they can be propagated by calling the event in each subclass. However, you must control the order in which events are called and propagated.
Since it is not easy to implement propagation with on_press() or on_release(), it is recommended to use on_touch_*() as an alternative if you do not care about these.
Code Example
kivy_events_propagation4.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class ParentWidget1(BoxLayout):
def on_press(self,flg):
if not flg == True:
print(f'Pressed Parent 1')
class ParentWidget2(BoxLayout):
def on_press(self, flg):
print(f'Pressed Parent 2')
self.parent.ids.button1.on_press() # Manually calling button1.on_press.
self.parent.on_press(True)
class ChildButton(Button):
def on_press(self):
print(f'Pressed Child: {self.text}')
self.parent.on_press(False)
class KivyEventsPropagation4(App):
def build(self):
return ParentWidget1()
if __name__ == '__main__':
KivyEventsPropagation4().run()
kivyeventspropagation4.kv
<ParentWidget1>:
orientation: 'vertical'
ChildButton:
id: button1
text: 'Child 1 button'
font_size: 30
ParentWidget2:
ChildButton:
text: 'Child 2 button'
font_size: 30
Code Explanation
By defining on_press() in the subclass that inherits from each widget, on_press() is called when the button is pressed. This is the same behavior as on_touch_*().
This example has the following structure.
<ParentWidget1>
+--- BoxLayout # Parent1
+---+--- Button1 # Child1
+---+--- BoxLayout # Parent1
+---+---+--- Button2 # Child2
When this code is executed, the following log will be output to the console.
# When Button1 is pressed.
Pressed Child: Child 1 button
Pressed Parent 1
# When Button 2 is pressed.
Pressed Child: Child 2 button
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Parent 1
In the original bubbling, all widgets should be called when Button1 is pressed, but in this example, the order is not controlled, so it is like bubbling. If you want to change the order in which they are called, you need to add a process to control the order.
Calling the Parent’s on_press()
self.parent.on_press(False)
The above code calls on_press() of the parent. The flag is passed as an argument. If this is not done, Parent1 will be called twice when Button2 is pressed, as shown below.
Pressed Child: Child 2 button
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Parent 1
Pressed Parent 1
In on_touch_*(), we could stop the propagation by setting return True, but in on_press() and on_release(), you need to control it yourself.
In this example, the flag indicates whether or not Parent1 has been called, and True means that Parent1 has been called; on_press() for Parent1 will not process if True. This prevents Parent1 from being called twice.
def on_press(self,flg):
if not flg == True:
print(f'Pressed Parent 1')
Ensure that the Widget Propagates Correctly
Within Parent2‘s on_press(), Child1‘s on_press() is called.
self.parent.ids.button1.on_press()
If this line is not written, propagation will not work correctly as shown below.
# When Button1 is pressed.
Pressed Child: Child 1 button
Pressed Parent 1
# When Button 2 is pressed.
Pressed Child: Child 2 button
Pressed Parent 2
Propagation of Custom Events
The following is a example code for propagation of custom events. Custom events do not have a propagation mechanism, so you need to implement it completely yourself. To do this, register the custom event with register_event_type() in the EventDispatcher class and trigger the event with dispatch() to control propagation.
Code Example
kivy_events_propagation5.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
class ParentWidget1(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type('on_click')
def on_click(self):
print(f'Pressed Parent 1')
def trigger_click(self, phase, btn):
self.dispatch("on_click")
self.propagation_controller(phase, btn)
def propagation_controller(self, phase, btn):
parent2 = self.ids.get('parent2')
button1 = self.ids.get('button1')
button2 = self.ids.get('button2')
if phase == 'capture':
if btn == 'btn1':
if button1:
button1.dispatch("on_click")
elif btn == 'btn2':
if parent2:
parent2.dispatch("on_click")
if button1:
button1.dispatch("on_click")
if button2:
button2.dispatch("on_click")
elif phase == 'bubble':
if btn == 'btn1':
if parent2:
parent2.dispatch("on_click")
if button2:
button2.dispatch("on_click")
if button1:
button1.dispatch("on_click")
elif btn == 'btn2':
if button2:
button2.dispatch("on_click")
class ParentWidget2(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type('on_click')
def on_click(self):
print(f'Pressed Parent 2')
class ChildButton(Button):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.register_event_type('on_click')
def on_click(self):
print(f'Pressed Child: {self.text}')
class KivyEventsPropagation5(App):
def build(self):
return ParentWidget1()
if __name__ == '__main__':
KivyEventsPropagation5().run()
kivyeventspropagation5.kv
<ParentWidget1>:
id: parent1
orientation: 'vertical'
ChildButton:
id: button1
text: 'Child 1 button'
font_size: 30
on_press:
#root.trigger_click('capture', 'btn1')
root.trigger_click('bubble', 'btn1')
ParentWidget2:
id: parent2
ChildButton:
id: button2
text: 'Child 2 button'
font_size: 30
on_press:
#root.trigger_click('capture', 'btn2')
root.trigger_click('bubble', 'btn2')
Code Explanation
This example reproduces the bubbling and capturing functions in the same way as on_touch_*(). The propagation_controller class is defined to control propagation, and events are called manually. The order of this description can be freely rearranged by changing the order of the events.
Result of Code Execution
You can select either the Bubble phase or the Capture phase. When the code is executed, the console will display the following.
# In case of Bubble phase.
# When Button 1 is pressed.
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 2 button
Pressed Child: Child 1 button
# When Button 2 is pressed.
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 2 button
# In the Capture phase.
# When Button1 is pressed.
Pressed Parent 1
Pressed Child: Child 1 button
# When Button2 is pressed.
Pressed Parent 1
Pressed Parent 2
Pressed Child: Child 1 button
Pressed Child: Child 2 button
Registering Custom Events
In the constructor of each subclass, register_event_type() registers a custom event on_click() for that class.
def __init__(self, **kwargs):
super(). __init__(**kwargs)
self.register_event_type('on_click')
def on_click(self):
print(f'Pressed Parent 1')
Dispatch of Custom Events
The trigger_click() is a callback function to be called from kv. it dispatches the events of on_click(). It also calls the propagation_controller method to control propagation.
def trigger_click(self, phase, btn):
self.dispatch("on_click")
self.propagation_controller(phase, btn)
In the kv file, trigger_click() is bound to on_press() and the arguments phase and btn are passed. The argument “phase” can select either “capture” or “bubble”. The button name is passed to propagation_controller() to identify the button pressed. btn1 is Button1 and btn2 is Button2.
on_press:
#root.trigger_click('capture', 'btn1')
root.trigger_click('bubble', 'btn1')
propagation_controller() to Manage Propagation
The propagation_controller() manages propagation.
In the following, get() is used to retrieve the widget object.
parent2 = self.ids.get('parent2')
button1 = self.ids.get('button1')
button2 = self.ids.get('button2')
In the following, the branching process is performed to determine whether the capture or bubble passed from the phase argument is capture or bubble. In addition, the process is branched depending on the button passed from the btn argument.
if phase == 'capture':
pass
elif phase == 'bubble': pass
pass
if btn == 'btn1': pass
pass
elif btn == 'btn2': pass
pass
You can change the order in which events are fired by calling dispatch() individually.
if parent2: if parent2.dispatch()
parent2.dispatch("on_click")
if button1: if button1.
button1.dispatch("on_click")
if button2: button2.dispatch("on_click")
button2.dispatch("on_click")
Comment