Search code examples
pythonuser-interfacekivy

How to use elements created in a kivy file to create another element in another kivy file?


I'm creating a button in StackLayout in file info_options.kv. Now I want to call a function in class InfoOptionsPanel whenever this button is pressed. I'm able to call this funtion when I'm replacing return MainPanel() with return InfoOptionsPanel() i.e. when I'm not using the layout created by MainPanel, but when I'm using MainPanel class for gui then button gives below, also note that there is no problem in GUI even when using MainPanel for GUI.

error thrown

File "C:\Users\..\AppData\Local\Programs\Python\Python312\Lib\site-packages\kivy\lang\builder.py", line 60, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "K:\path\to\project\Info_options.kv", line 5, in <module>
     on_press: self.parent.on_press()
 ^^^^^^^^^^^^^^^^
 AttributeError: 'InfoOptionsPanel' object has no attribute 'on_press'

info.kv

<InfoPanel@BoxLayout>
    orientation: 'vertical'
    size_hint_y: 0.8
    pos_hint: {"top": 1}
    Label:
        id: info_label
        text:"Click button"
        font_size:50

info_options.kv

<InfoOptionsPanel@StackLayout>:
    orientation: 'lr-tb'
    Button:
        text: 'Button'
        on_press: self.parent.on_press()
        size_hint :.7, 0.1

main_screen.kv

<MainPanel@BoxLayout>:
    orientation: 'vertical'
    
    AnchorLayout:
        anchor_x: 'left'
        anchor_y: 'top'
        InfoPanel:
            size: 100, 100

    AnchorLayout:
        anchor_x: 'right'
        anchor_y: 'bottom'
        InfoOptionsPanel:
            size: 100, 100

main.py


from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
import os

kivy_design_files = ["Info_options", "Info", "main_screen",]
for kv_file in kivy_design_files:
    Builder.load_file(os.path.join(os.path.abspath(os.getcwd()), kv_file + ".kv"))
    
class InfoPanel(BoxLayout):
    pass

class InfoOptionsPanel(StackLayout):
    def on_press(self):
        print("Button pressed\n"*55)

class MainPanel(BoxLayout):
    pass

class MyApp(App):
    def build(self):
        return MainPanel()

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

Solution

  • You don't need to inherit from Kivy's layout classes in both main.py and kivy files. Either remove inheritance from main.py or from your kivy files.

    For main.py,

    class InfoPanel():  # don't inherit from BoxLayout
        pass
    
    class InfoOptionsPanel():  # don't inherit from StackLayout
        def on_press(self):
            print("Button pressed\n"*55)
    
    class MainPanel():  # don't inherit from BoxLayout
        pass
    

    Or for kivy files (do the same in each),

    <InfoOptionsPanel>:  # removed @StackLayout
        orientation: 'lr-tb'
        Button:
            text: 'Button'
            on_press: root.on_press()
            size_hint :.7, 0.1
    

    I would suggest you to remove inheritance from main.py and keep python code nice and clean. Kivy files are meant to deal with UI, so take advantage of that.

    Also, use root.on_press() instead of self.parent.on_press(). Both will work regardless, but root clarifies that you're referring to the root widget. self.parent would refer to some other widget if you add multiple parent widgets or layouts to Button in the future.