Kivy Tutorials

Kivy Events 3: Custom Events With EventDispatcher

This article can be read in about 28 minutes.

Kivy provides built-in events, but you can also create your own events. This article explains how to create and register custom events and dispatch them.

Methods of the EventDispatcher Classes

Please refer to the following articles for more information on Kivy’s event mechanism, built-in events, and event propagation.

To create a custom event, the EventDispatcher class provides methods for registering events.

  1. register_event_type (self, event_type) : register a new custom event.
  2. unregister_event_type (self, event_type): unregister a custom event.
  3. bind (self, **kwargs) : bind an event to a specific callback.
  4. fbind (self, name, func, * args, **kwargs) : faster binding than bind().
  5. unbind (self, **kwargs) : Unbind events and callbacks when using bind().
  6. funbind (name, func, *args, **kwargs) : Unbind events and callbacks when using fbind().
  7. dispatch (self, event_type, *args, **kwargs) : trigger an event and call a listener.
  8. get_property_observers (self, name, args = False ) : retrieve the list of bound events.
  9. is_event_type (self, event_type): check if an event is registered.

Creating Custom Events

To create and execute a custom event, follow these steps

  1. Register a custom event. (use register_event_type() or __events__)
  2. Bind callback functions if necessary. ( use bind() or fbind())
  3. Trigger events. ( use dispatch())

Code Example

Kivy_events_custom_event1.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class RootWidget(BoxLayout):
    #__events__ = ('on_custom_event',) # (1) Register custom events (Static).

    def __init__(self, **kwargs):
        self.register_event_type('on_custom_event') # (1) Register a custom event (Dynamic).
        self.bind(on_custom_event=self.on_callback) # (2) Binding callback.
        super().__init__(**kwargs)

    def on_custom_event(self,*args):
        self.ids.label.text += '\n on_custom_event called'

    def on_callback(self,*args):
        self.ids.label.text += '\n on_callback called'

class KivyEventsCustomEvent1(App):
    def build(self):
        return RootWidget()

if __name__ == '__main__':
    KivyEventsCustomEvent1().run()

Kivyeventscustomevent1.kv

<RootWidget>:
    orientation: 'vertical'
    Label:
        id:label
        text: "Event is dispatched."
        font_size: 30     
    Button:
        text: "Dispatch"
        font_size: 30
        on_press:
            root.dispatch('on_custom_event') # (3) Triggering an event.

Cases Where It Is Not Necessary to Import EventDispatcher

Although register_event_type(), bind(), and dispatch() used in this example are methods of the EventDispatcher class, you do not need to import an EventDispatcher to use them.

This code uses a subclass that inherits from BoxLayout, which belongs to the Widget class. And since the Widget class inherits from the EventDispatcher class, these methods can be used without importing. In other words, if a subclass inherits from a UI widget, layout widget, or Widget class, there is no need to import it.

In the case of an App subclass as shown below, you need to define a subclass that inherits from EventDispatcher.

from kivy.event import EventDispatcher
from kivy.app import App

def CustomEvent(EventDispatcher):
    pass

def CustomEventApp(App): pass
   pass

Step 1: Registering a Custom Events

There are two ways to register a custom event.

  1. Using register_event_type()
  2. Special variable How to use __events__

The event function to be registered in this example is shown below. The *args argument can be omitted.

   def on_custom_event(self,*args):
        self.ids.label.text = '\n on_custom_event called'

How to Use register_event_type

To register a custom event with register_event_type(), the following conditions must be met

  1. Be prefixed with ‘on_’.
  2. The custom event must be defined in the class.

Failure to adhere to these conditions will result in an error.

instance.register_event_type(event_type )

self.register_event_type('on_custom_event') # (1) Register a custom event (Dynamic).

The event_type argument specifies a custom event, but only one event can be registered. If you wish to register multiple events, write multiple lines.

self.register_event_type('on_custom_event1')
self.register_event_type('on_custom_event2')
self.register_event_type('on_custom_event3')

Special Variables How to Use __events__

The way to use special variables is to specify multiple events in a list.

__events__ = ( ‘event_name1 ‘, ‘event_name2 ‘, ‘event_name3 ‘,)

The list must end with a comma.

__events__ = ('on_custom_event',) # (1) Register custom events (Static).

Difference Between the Two

Although it seems fine to use either, register_event_type() is suitable for both registering dynamic and static events. On the other hand __events__ is suitable for registering static events. When registering dynamic events, register_event_type() is the only choice.

A static event is an event that is defined from the beginning and is immutable, meaning that the process does not change after an instance of the class is created. On the other hand, a dynamic event is an event that changes after an instance of the class is created, such as the addition of an event.

Step 2: Binding a Callback Function

To bind a callback function, use bind() or fbind(). Normally, bind() is sufficient, but if you use events frequently or for performance reasons, you may choose to use fbind(). fbind() is a binding method that is more sophisticated and faster than bind(). I will skip over the usage of fbind().

How to Write bind()

The callback function in this example is as follows

   def on_callback(self,*args):
        self.ids.label.text = '\n on_callback called'

Note that omitting args results in the following error: the error ‘positional argument takes 1 but was given 2 ‘ is a TypeError that occurs when the number of arguments defined does not match the number of arguments in the caller.

  File "kivy\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
   File "kivy\\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
   File "kivy\\_event.pyx", line 1231, in kivy._event.
 TypeError: RootWidget.on_callback() takes 1 positional argument but 2 were given

The bind() is written as follows

instance_name.bind(event_name = callback_function)

self.bind(on_custom_event=self.on_callback)
When Using the KV Language

Usually, it can be bound in the kv language, but for custom events I get an error, it doesn’t seem to handle them the same way as Kivy’s built-in events.

# py file
# Register a custom event.
self.register_event_type('on_custom_event')
# kv file
# This will result in an error.
on_custom_event: root.on_callback()

Registering an overridden Kivy built-in event is not an error.

# py file
# Register overridden built-in event.
self.register_event_type('on_press')
# kv file
# This is not an error.
on_press: root.on_callback()

Step3: Triggering the Event

Use dispatch() to trigger an event.

on_press: root.dispatch('on_press', 'on_press', 'on_press')
    root.dispatch('on_custom_event') # (3) Triggering an event.

In this example, a custom event is executed when a button is pressed. This time, the event is described in this location, but it can also be described in the constructor, for example, to execute the event when the application is launched.

Manipulating Custom Events

As mentioned at the beginning of this article, the EventDispatcher class provides methods for manipulating custom events. Here is a example code that uses the following methods.

  1. unregister_event_type (self, event_type): unregister a custom event.
  2. unbind (self, **kwargs) : unassociate an event with a callback using bund().
  3. get_property_observers (self, name, args = False ) : retrieve the list of bound events.
  4. is_event_type (self, event_type): check if an event is registered.

Code Example

Kivy_events_custom_event2.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class RootWidget(BoxLayout):
    __events__ = ('on_dynamic_event', 'on_custom_event',)

    def __init__(self, **kwargs):
        self.bind(on_custom_event=self.on_callback)
        super().__init__(**kwargs)
        self.dispatch('on_dynamic_event')

    # Custom event1
    def on_custom_event(self,*args):
        self.ids.label.text += '\n on_custom_event was called'

    # Custom event2
    def on_dynamic_event(self,*args):
        self.ids.label.text += '\n on_dynamic_event was called'

    # Callback
    def on_callback(self, *args):
        self.ids.label.text += '\n on_callback was called'

    # Unregister the registered event.
    def unregister_event(self):
        if self.is_event_type('on_dynamic_event'):
            self.unregister_event_type('on_dynamic_event')

            self.ids.label.text = 'Unregistered on_dynamic_event'
            self.ids.button1.text = 'Re-register Dynamic event'
        else:
            self.register_event_type('on_re_register')
            self.dispatch('on_re_register')

    # Unbind callback function.
    def unbind_callback(self):
        if self.is_callback() == None:
            self.ids.label.text = 'Error: None returned in the list'

        if self.is_callback() == True:
            self.unbind(on_custom_event=self.on_callback)
            self.ids.label.text = 'Unbound on_callback'
            self.ids.button2.text = 'Re-bind Callback'
        elif self.is_callback() == False:
            self.register_event_type('on_re_bind')
            self.dispatch('on_re_bind')

    # Check if callback functions are bound.
    def is_callback(self):
        get_callback = self.get_property_observers('on_custom_event', args = False)
        for observer in get_callback:
            if observer.method_name == 'on_callback':
                return True
            elif observer.method_name == None:
                return None
        return False
    
    # Re-register an unregistered custom event.
    def on_re_register(self):
        self.register_event_type('on_dynamic_event')
        self.ids.label.text = 'Registered on_dynamic_event'
        self.ids.button1.text = 'Unregister Dynamic event'
        self.dispatch('on_dynamic_event')
    
    # Rebind the unbind callback function.
    def on_re_bind(self):
        self.bind(on_custom_event=self.on_callback)
        self.ids.label.text = 'Bound on_callback.'
        self.ids.button2.text = 'Unbind Callback'

class KivyEventsCustomEvent2(App):
    def build(self):
        return RootWidget()

if __name__ == '__main__':
    KivyEventsCustomEvent2().run()

kivyeventscustomevent2.kv

<RootWidget>:
    orientation: 'vertical'
    Label:
        id:label
        text: ""
        font_size: 30
    
    BoxLayout:
        Button:
            id: button1
            text: "Unregister Dynamic event"
            font_size: 30
            on_press: root.unregister_event()
                
        Button:
            id: button2
            text: "Unbind Callback"
            font_size: 30
            on_press: root.unbind_callback()

        Button:
            text: "call Custom event"
            font_size: 30
            on_press: root.dispatch('on_custom_event')

Code Explanation

This code consists of the following methods

# Custom_event1
def on_custom_event(self,*args):
# Custom event2
def on_dynamic_event(self,*args):
# Callback
def on_callback(self,*args):
# Unregister the registered event.
def unregister_event(self):
# Unbind the callback function.
def unbind_callback(self):
# Check if the callback function is bound.
def is_callback(self):
# Re-register the unbind custom event.
def on_re_register(self):
# Re-bind the unregistered callback function.
def on_re_bind(self):
  1. on_dynamic_event() is triggered when the app is launched.
  2. Button1 repeatedly unregisters and re-registers the registered event.
  3. Button2 repeatedly binds and unbinds callback functions.
  4. Button3 triggers on_custom_even().
Process flow of the application to manipulate custom events.

Unregistering a Registered Custom Event

To unregister a registered custom event, use unregister_event_type(). Also use is_event_type() to check if a custom event exists.

is_event_type()

The is_event_type() returns True if the parameter event_type is registered, False if not.

instance. is_event_type (self, event_type)

The event_type specifies the event name registered by either register_event_type() or __events__.

if self.is_event_type('on_dynamic_event'):
unregister_event_type()

The unregister_event_type() unregisters an event registered by register_event_type() or __events__.

instance. unregister_event_type (self, event_type)

The event_type specifies the event name registered by either register_event_type() or __events__.

self.unregister_event_type('on_dynamic_event')

Unbind a Callback Function

To unbind a callback, use unbind(). Also use get_property_observers() to check if it is bound.

get_property_observers()

get_property_observers() returns a list of callback functions bound to properties or events.

instance.get_property_observers (self, name, args = False)

name: Specify the property or event from which to bind.

args: bool value specifying whether to return the arguments of the bound callback function. Default is False; if False, only the callback function is returned and no arguments are returned; if True, the arguments are returned together. The argument list consists of five elements (callback, largs, kwargs, is_ref, uid ), where is_ref indicates whether the callback is a weakref (weak reference), uid is the uid if fbind() is used, and None if bind() is used. is used, and None if bind() is used.

# List returned if True.
[(<WeakMethod proxy=<__main__.RootWidget object at 0x00000164BC361240> method=None method_name=on_callback>, (), {}, 1, None)]

# List returned if Flase.
[<WeakMethod proxy=<__main__.RootWidget object at 0x00000164BC361240> method=None method_name=on_callback>]

Since the callback function name can be obtained by method_name, branch processing is performed and the flag is returned.

   # Check if the callback function is bound.
    def is_callback(self):
        get_callback = self.get_property_observers('on_custom_event', args = False)
        for observer in get_callback: if observer.method_name == False
            if observer.method_name == 'on_callback': if observer.method_name = 'on_callback', args = False
                return True
            elif observer.method_name == None: return None
                return None
        return False

The get_callback is assigned a list of get_property_observers(), so it is expanded with a for statement and returns True if it matches ‘on_callback ‘.

In this example, method_name could be obtained because a callback function with no arguments was bound, but when a callback function with arguments was bound, there were cases where None was entered for method_name or method_name could not be obtained. The details of this are summarized in the following article.

unbind()

Unbinds a callback function that has been bound to an event with bind(). If a callback function is bound to an event multiple times, the first bound callback function is unbound.

self.bind(on_custom_event=self.on_callback_1) # The first bound callback function is unbound.
self.bind(on_custom_event=self.on_callback_2)
self.bind(on_custom_event=self.on_callback_3)

If it is a callback function with no arguments bound by fbind(), unbind() can be used. If it is a callback function with arguments, unbind fails, so use funbind().

instance. unbind (self, **kwargs)

self.unbind(on_custom_event=self.on_callback)

Propagation of Custom Events

The implementation of custom event propagation is described in the following article.

Comment

Copied title and URL