Search code examples
pythonkivykivy-language

Binding Kivy ObjectProperty to a child widget doesn't seem to work outside of root widget


Trying to follow this guide : https://kivy.org/docs/guide/lang.html#accessing-widgets-defined-inside-kv-lang-in-your-python-code

I am attempting to access a widget using an id definition. This works well inside the root widget but it doesn't seem to work outside of it. As an example here's a bare minimum code representing my issue :

GUI.kv file :

<PlotBox@BoxLayout>:
graph2:graph2_id
BoxLayout:
    id:graph2_id

<RootWidget@BoxLayout>:
    graph:graph_id
    BoxLayout:
        id:graph_id
    PlotBox:

python file :

#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty

class PlotBox(BoxLayout):
    graph2 = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(PlotBox,self).__init__(**kwargs)
        self.graph2.add_widget(Button(text="This doesn't work"))

class RootWidget(BoxLayout):
    graph = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(RootWidget,self).__init__(**kwargs)
        self.graph.add_widget(Button(text='This works'))

class GUIApp(App):
    def build(self):
        self.root = RootWidget()
        return self.root

if __name__ == "__main__":
    GUIApp().run()

I get the error :

AttributeError: 'NoneType' object has no attribute 'add_widget'

On the RootWidget, it works even if I don't use graph = ObjectProperty(None). On my other widget, it's like the id doesn't get created.


Solution

  • According to the docs:

    The @ character is used to separate your class name from the classes you want to subclass. [...]

    From what is concluded that it is an equivalent way to do inheritance in the .kv similar to python so you should only select one of those ways. That causes PlotBox from .py to never be invoked.

    Another error, according to the docs, I do not know if it's your error but the .kv must be gui.kv, with lowercase.

    children are not loaded directly after executing the parent's constructor so adding it in the constructor can generate problems, a recommendation and a very common practice in kivy is to use Clock.

    All the above I have implemented in the following codes:

    gui.kv

    <PlotBox>:
        graph2:graph2_id
        BoxLayout:
            id:graph2_id
    
    <RootWidget>:
        graph:graph_id
        BoxLayout:
            id:graph_id
        PlotBox:
    

    main.py

    #kivy imports
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.button import Button
    from kivy.properties import ObjectProperty
    from kivy.clock import Clock
    
    class PlotBox(BoxLayout):
        graph2 = ObjectProperty(None)
        def __init__(self,**kwargs):
            super(PlotBox,self).__init__(**kwargs)
            Clock.schedule_once(lambda dt: self.graph2.add_widget(Button(text="This now works")))
    
    class RootWidget(BoxLayout):
        graph = ObjectProperty(None)
        def __init__(self,**kwargs):
            super(RootWidget,self).__init__(**kwargs)
            self.graph.add_widget(Button(text='This works'))
    
    class GUIApp(App):
        def build(self):
            root = RootWidget()
            return root
    
    if __name__ == "__main__":
        GUIApp().run()
    

    Output:

    enter image description here