Search code examples
kivycustom-widgets

Kivy, reusing a toggle button layout, but assigning different functions to the buttons


I have created a custom toggle button layout that I would like to reuse multiple times. I would like to reuse it as it contains extensive formatting. I am using generated uuid's to assign the groups so that the multiple instances don't interfere with each other.

Here my test.kv:

#:kivy 2.0.0
#:import uuid uuid

<ExampleToggle@BoxLayout>:
    uuid: uuid.uuid4()
    orientation: 'horizontal'

    ToggleButton:
        id: run
        text: 'RUN'
        group: root.uuid
        on_release: root.on_run()

    ToggleButton:
        id: stop
        text: 'STOP'
        group: root.uuid
        on_release: root.on_stop()

<TestDisplay>:
    BoxLayout:
        orientation: 'vertical'
        
        ExampleToggle:
            on_run: print('A') 
            on_stop: print('B') 

        ExampleToggle:
            on_run: print('C') 
            on_stop: print('D') 

Here is my test.py:

import kivy
kivy.require('2.0.0')

from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.widget import Widget


class ExampleToggle():
    on_run = ObjectProperty(None)
    on_stop = ObjectProperty(None)

    def on_run(self, *args):
        # Dummy function
        pass

    def on_stop(self, *args):
        # Dummy function
        pass


class TestDisplay(Widget):
    pass


class TestApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        return TestDisplay()


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

I would like to be able to create multiple copies of the ExampleToggle widget and assign different functions to the buttons. I have no issue assigning a function to the on_release event of the individual buttons, however if I do that then it is the same event for every instance of this widget. I would like to be able to assign different functions every time I reuse the widget.

I feel I am either missing something ridiculously simple, or I am going down the wrong path. I have tried multiple different methods, and spent a ton of time reading and researching... Any help would be greatly appreciated.


Solution

  • After a long break I came back to this and worked out the answer. Here is my working solution for others who are curious, or for myself in the future. I had to build the toggle layout in python and then use it in the .kv file. The key was using the 'self.dispatch()' method. I found this from reading through the kivy source code for buttons.

    test.kv:

    #:kivy 2.0.0
    #:import uuid uuid
    #:import ExampleToggle test
    
    <TestDisplay>:
        BoxLayout:
            orientation: 'vertical'
            
            ExampleToggle:
                on_run: print('A') 
                on_stop: print('B') 
    
            ExampleToggle:
                on_run: print('C') 
                on_stop: print('D') 
    

    test.py:

    from uuid import uuid4
    
    import kivy
    kivy.require('2.0.0')
    
    from kivy.app import App
    from kivy.uix.widget import Widget
    from kivy.uix.togglebutton import ToggleButton
    from kivy.uix.boxlayout import BoxLayout
    
    
    class ExampleToggle(BoxLayout):
        def __init__(self, **kwargs):
            self.register_event_type('on_run')
            self.register_event_type('on_stop')
    
            super(ExampleToggle, self).__init__(**kwargs)
    
            self.groupid = uuid4()
            self.orientation = 'horizontal'
            self.size_hint = (None, .1)
            self.width: self.height * 5
            self.state = None
    
            rbtn = ToggleButton(text='RUN',
                                group=self.groupid,
                                allow_no_selection=False)
            sbtn = ToggleButton(text='STOP',
                                group=self.groupid,
                                allow_no_selection=False)
    
            self.add_widget(rbtn)
            self.add_widget(sbtn)
    
            rbtn.bind(on_release=self.run)
            sbtn.bind(on_release=self.stop)
    
        def run(self, *args):
            if self.state != 'RUN':
                self.dispatch('on_run')
                self.state = 'RUN'
    
        def stop(self, *args):
            if self.state != 'STOP':
                self.dispatch('on_stop')
                self.state = 'STOP'
    
        def on_run(self):
            pass
    
        def on_stop(self):
            pass
    
    
    class TestDisplay(Widget):
        pass
    
    
    class TestApp(App):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
    
        def build(self):
            return TestDisplay()
    
    
    if __name__ == '__main__':
        TestApp().run()