Kivy Tutorials

Kivy Events 4: on_press and Custom Event Propagation

This article can be read in about 18 minutes.

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

Copied title and URL