Search code examples
pythonpython-3.xkivykivy-language

passing objects through callbacks in kivy- a good idea for linking objects?


for a voteapp suppose in it's kv file I define a toggle button as so:

....
...
ToggleButton:
    id: toggle_visibility
    size_hint_x: 0.27
    text: "Hide"
    on_press: root.toggle_visibility(self) #<-----------

Notice the marked line above, is there anything wrong passing the widget as an argument for callback functions Instead of registering with ObjectProperty() and linking to it's id? When I do the later, I get a NoneType object returned error for an event such as press with a button for some reason, so I opted to the former.

EDIT: I have put the code for the errors that cause for the Nonetype object returned message with the traceback at the end.

The error occurs when I execute the following code:

In the main.py file:

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


class Apibar(BoxLayout):
    api_input_bar = ObjectProperty()
    toggle_visibility_button = ObjectProperty()  

    #some code here  not shown to keep minimal; only regular methods  

    def toggle_visibility(self):
        #wanting to change the text inside the text input key
        self.api_input_bar.text="your api key"

And in the .kv file the code is:

<Apibar>:
    BoxLayout:
        orientation:"vertical"
        padding: "5dp"
        api_input_bar: api_input
        toggle_visibility_button : toggle_visibility
        BoxLayout:
              orientation: "horizontal"
              size_hint_y: 0.5
              #some more rules for other widgets
              ToggleButton:
                  id: toggle_visibility
                  size_hint_x: 0.27
                  text: "Hide"
                  on_press: root.toggle_visibility()
                  #some more rules for other widgets

And the traceback at the terminal is:

File "main.py", line 43, in <module>
     pictureapp().run()
   File "/usr/lib/python3/dist-packages/kivy/app.py", line 828, in run
     runTouchApp()
   File "/usr/lib/python3/dist-packages/kivy/base.py", line 504, in runTouchApp
     EventLoop.window.mainloop()
   File "/usr/lib/python3/dist-packages/kivy/core/window/window_sdl2.py", line 663, in mainloop
     self._mainloop()
   File "/usr/lib/python3/dist-packages/kivy/core/window/window_sdl2.py", line 405, in _mainloop
     EventLoop.idle()
   File "/usr/lib/python3/dist-packages/kivy/base.py", line 342, in idle
     self.dispatch_input()
   File "/usr/lib/python3/dist-packages/kivy/base.py", line 327, in dispatch_input
     post_dispatch_input(*pop(0))
   File "/usr/lib/python3/dist-packages/kivy/base.py", line 233, in post_dispatch_input
     listener.dispatch('on_motion', etype, me)
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/core/window/__init__.py", line 1188, in on_motion
     self.dispatch('on_touch_down', me)
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/core/window/__init__.py", line 1204, in on_touch_down
     if w.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/uix/widget.py", line 457, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/uix/widget.py", line 457, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/uix/widget.py", line 457, in on_touch_down
     if child.dispatch('on_touch_down', touch):
   File "kivy/_event.pyx", line 718, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7726)
   File "/usr/lib/python3/dist-packages/kivy/uix/behaviors/button.py", line 151, in on_touch_down
     self.dispatch('on_press')
   File "kivy/_event.pyx", line 714, in kivy._event.EventDispatcher.dispatch (kivy/_event.c:7681)
   File "kivy/_event.pyx", line 1225, in kivy._event.EventObservers.dispatch (kivy/_event.c:13524)
   File "kivy/_event.pyx", line 1109, in kivy._event.EventObservers._dispatch (kivy/_event.c:12356)
   File "/usr/lib/python3/dist-packages/kivy/lang/builder.py", line 64, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "/home/xyzuser/project_folder/pythonprjs/pictureabarapp/picture.kv", line 37, in <module>
     on_press: root.toggle_visibility()
   File "main.py", line 35, in toggle_visibility
     self.api_input_bar.text="your api key"
 AttributeError: 'NoneType' object has no attribute 'text'

I did it exactly as mentioned but the code doesn't seem to work.


Solution

  • The problem is caused because you are doing the assignment in an inappropriate place, api_input_bar belongs to Apibar, so if you want to make the connection the right place is below Apibar since it is its scope:

    <Apibar>:
        # Apibar Scope
        api_input_bar: api_input
        toggle_visibility_button : toggle_visibility
        BoxLayout:
            # BoxLayout scope
            orientation:"vertical"
            padding: "5dp"
            BoxLayout:
                orientation: "horizontal"
                size_hint_y: 0.5
                #some more rules for other widgets
                ToggleButton:
                    id: toggle_visibility
                    size_hint_x: 0.27
                    text: "Hide"
                    on_press: root.toggle_visibility()
                    #some more rules for other widgets
    

    When making the connection elsewhere, a new ObjectProperty is being created but for the BoxLayout. This can be checked using the following example:

    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.properties import ObjectProperty
    from kivy.lang.builder import Builder
    
    class Apibar(BoxLayout):
        api_input_bar = ObjectProperty()
        toggle_visibility_button = ObjectProperty()  
    
        def toggle_visibility(self):
            # self.ids.box.api_input_bar is different of self.api_input_bar
            self.ids.box.api_input_bar.text="your api key"
    
    Builder.load_string('''
    <Apibar>:
        BoxLayout:
            id: box
            orientation:"vertical"
            padding: "5dp"
            api_input_bar: api_input
            toggle_visibility_button : toggle_visibility
            BoxLayout:
                orientation: "horizontal"
                size_hint_y: 0.5
                #some more rules for other widgets
                ToggleButton:
                    id: toggle_visibility
                    size_hint_x: 0.27
                    text: "Hide"
                    on_press: root.toggle_visibility()
                    #some more rules for other widgets
                TextInput:
                    id: api_input
        ''')
    
    class MyApp(App):
        def build(self):
            return Apibar()
    
    if __name__ == '__main__':
        MyApp().run()