Search code examples
pythonpython-3.xlayoutkivyprogrammatically-created

Python Kivy How to inject widgets made with Python to layout in kv file


I have written a python kivy application for displaying measurements. While searching stuff on kivy I stumbled on the the kivy-md (Material Design for kivy) project. I find the UI very good looking. That is why I want to inject my app into a screen of the kivy-md project.

My project is inside a folder kivy-playground which contains the kivymd files in a folder kivymd, the main.py (for starting the kivy md application), the main.kv file (for the layout of the kivy md application) and a playground.py which contains the kivy application for displaying measurements. I am using Python 3.7 with the latest version of kivy.

Goal: I want to inject the Application view from playground.py into the main application which is started by the main.py such that the view of the playground.py is displayed on the screen page2 (see main.kv) of the main application. I am totally lost how this can be achieved and I would be happy if someone could show me on this toy example how this can be achieved. It is not necessarily important that the playground.py stays as it is. If the problem can be solved by small changes in the playground.py file then this would be also a valid solution.

I tried to cook up a minimal working example. Here are the files

# main.py
# -*- coding: utf-8 -*-

import os

from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivymd.theming import ThemeManager


class MainApp(App):

    theme_cls = ThemeManager()

    def __init__(self, **kwargs):
        super(MainApp, self).__init__(**kwargs)
        Window.bind(on_close=self.on_stop)

    def build(self):
        main_widget = Builder.load_file(
            os.path.join(os.path.dirname(__file__), "./main.kv")
        )
        self.theme_cls.theme_style = 'Dark'
        return main_widget

    def close_app(self, *largs):
        super(MainApp, self).stop(*largs)

    def on_pause(self):
        return True


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

here is the main.kv

# main.kv
#:kivy 1.10.1
#:import Toolbar kivymd.toolbar.Toolbar
#:import MDNavigationDrawer kivymd.navigationdrawer.MDNavigationDrawer
#:import NavigationLayout kivymd.navigationdrawer.NavigationLayout
#:import NavigationDrawerToolbar kivymd.navigationdrawer.NavigationDrawerToolbar

NavigationLayout:
    id: nav_layout
    MDNavigationDrawer:
        id: nav_drawer
        NavigationDrawerToolbar:
        NavigationDrawerIconButton:
            icon: 'checkbox-blank-circle'
            text: "Page1"
            on_release: app.root.ids.scr_mngr.current = 'page1'
        NavigationDrawerIconButton:
            icon: 'checkbox-blank-circle'
            text: "Page2"
            on_release: app.root.ids.scr_mngr.current = 'page2'
    BoxLayout:
        orientation: 'vertical'
        halign: "center"
        Toolbar:
            id: toolbar
            md_bg_color: app.theme_cls.primary_color
            background_palette: 'Primary'
            background_hue: '500'
            right_action_items: [['dots-vertical', lambda x: app.root.toggle_nav_drawer()]]
        ScreenManager:
            id: scr_mngr
            Screen:
                name: 'page1'
                Label:
                    text: "page1"
            Screen:
                name: 'page2'
                Label:
                    text: "Page 2"

and here is the playground.py which I want to inject into the screen page2 of the main application.

from kivy.app import App
from kivy.uix.label import Label


class Playground(App):

    def build(self):
        hello_world_label = Label(text="Hello World!")
        return hello_world_label


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

Solution

  • If you can change Playground.py to something like this:

    from kivy.app import App
    from kivy.uix.label import Label
    
    def getPlaygroundRoot():
        hello_world_label = Label(text="Hello World!")
        return hello_world_label
    
    
    class PlayGround(FloatLayout):
        def __init__(self, **kwargs):
            super(PlayGround, self).__init__(**kwargs)
            self.add_widget(getPlaygroundRoot())
    
    class Playground(App):
    
        def build(self):
            return getPlaygroundRoot()
    
    
    if __name__ == "__main__":
        Playground().run()
    

    Then, in your main.py, you can add:

    from playGround import getPlaygroundRoot
    

    And then use add_widget(getPlaygroundRoot()) to add the Playground root to some container in the MainApp.

    Or, if you want to use Playground in your .kv file, you can add #:import playground playGround to your .kv file, and then add:

            Screen:
                name: 'page2'
                Label:
                    text: "Page 2"
                    pos_hint: {'center_x': 0.5, 'y': 0.8}
                    size_hint: (1.0, 0.2)
                PlayGround:
                    pos_hint: {'center_x': 0.5, 'y': 0.1}
                    size_hint: (1.0, 0.2)
    

    to add it to your page2.