pythonnestedkivyscreen

Kivy Screen Manager in nested Python Code is displaying a blank screen


I have created a Kivy app which uses python to control complex dynamic layout generation. I'm trying to create a set Menu bar on the left which controls the content shown in a scrollview on the right.

The way I have done this is by creating seperate Screen classes for each page which are called in the ScreenManager class. I've then added the python generated layouts to the related Screen classes in the kivy file. Please let me know if there is a better way to handle this.

My app should have a splash screen with a "This is the splash screen" label. Once you press create ply it should switch screen to the ply creation page which dynamically controlls a table of entries. When adding widgets in the Python file nothing dispays in the focus frame. When adding widgets in the KV file the splash screen is displayed but the button will not swap to the other screen. Furthermore, the layouts are not displaying as anticipated. They now seem to be anchored to the bottom and the scroll view no longer works.

The python file:

    #This file contains the graphical user interface logic of the DIS creator

from kivy.app import App
from kivy.metrics import dp

from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.screenmanager import ScreenManager, Screen

class DISCreatormanage(App):
    pass

class Menu(StackLayout):

#Menu funcitons
    def start_create_ply(self):
        screen_manager.current = "create_ply_screen"

class Splash(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

#Dimensional control
        self.spacing = dp(5)
        self.padding = dp(10)
        self.cols = 1
        self.size_hint = (1, None)

        label = Label(text="This is the splash screen")
        self.add_widget(label)

class CreatePly(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

#Dimensional control
        self.spacing = dp(5)
        self.padding = dp(10)
        self.cols = 1
        self.size_hint = (1, None)

#Varibales
        self.constituent_name = []
        self.constituent_arealweight = []
        self.structural = []

#Add and remove consituents
#Header
        self.add_remove_header = BoxLayout()
        self.add_remove_header.size_hint = (1, None)
        self.add_remove_header.height = dp(40)
        label = Label(text="Add constituents to create a ply")
        self.add_remove_header.add_widget(label)
        self.add_widget(self.add_remove_header)

#Add remove buttons
        self.add_remove_buttons = GridLayout()
        self.add_remove_buttons.cols = 4
        self.add_remove_buttons.size_hint = (1, None)
        self.add_remove_buttons.height = dp(40)
        self.add_remove_buttons.add_widget(Widget())
        button = Button(text="+", size_hint=(None, None), width=dp(40), height=dp(40))
        button.bind(on_press = lambda x: self.add_constituent_press())
        self.add_remove_buttons.add_widget(button)
        button = Button(text="-", size_hint=(None, None), width=dp(40), height=dp(40))
        button.bind(on_press = lambda x: self.remove_constituent_press())
        self.add_remove_buttons.add_widget(button)
        self.add_remove_buttons.add_widget(Widget())
        self.add_widget(self.add_remove_buttons)

#Constituent table
        self.constituent_table = GridLayout()
        self.constituent_table.cols = 3
        self.constituent_table.size_hint_y = None
        self.constituent_table.bind(minimum_height=self.constituent_table.setter('height'))

        label = Label(text="Consituent name", size_hint=(0.55, None), height=dp(20))
        self.constituent_table.add_widget(label)
        label = Label(text="Areal weight (g/m2)", size_hint=(0.3, None), height=dp(20))
        self.constituent_table.add_widget(label)
        label = Label(text="Structural?", size_hint=(0.15, None), height=dp(20))
        self.constituent_table.add_widget(label)

        textinput = TextInput(size_hint=(0.55, None), height=dp(40))
        self.constituent_name.append(textinput)
        self.constituent_table.add_widget(textinput)
        textinput = TextInput(size_hint=(0.3, None), height=dp(40))
        self.constituent_arealweight.append(textinput)
        self.constituent_table.add_widget(textinput)
        toggle = ToggleButton(text="No", size_hint=(0.15, None), height=(dp(40)))
        toggle.bind(state=(lambda self, x: CreatePly.structural_constituent_toggle(self, toggle)))
        self.structural.append(toggle)
        self.constituent_table.add_widget(toggle)
        self.add_widget(self.constituent_table)

#Build ply button
        self.footer = GridLayout()
        self.footer.cols = 3

        self.footer.size_hint = (1, None)
        self.footer.height = dp(40)
        self.footer.add_widget(Widget())
        button = Button(text="Create ply", size_hint=(None, None), width=dp(120), height=dp(40))
        button.bind(on_press = lambda x: self.create_ply_click())
        self.footer.add_widget(button)
        self.footer.add_widget(Widget())
        self.add_widget(self.footer)

#Create ply functions
    def structural_constituent_toggle(self, toggle):
        if toggle.state == "normal":
            toggle.text = "No"
        else:
            toggle.text = "Yes"

    def add_constituent_press(self):
        textinput = TextInput(size_hint=(0.55, None), height=(dp(40)))
        self.constituent_name.append(textinput)
        self.constituent_table.add_widget(textinput)

        textinput = TextInput(size_hint=(0.3, None), height=(dp(40)))
        self.constituent_arealweight.append(textinput)
        self.constituent_table.add_widget(textinput)

        toggle = ToggleButton(text="No", size_hint=(0.15, None), height=(dp(40)))
        toggle.bind(state=(lambda self, x: CreatePly.structural_constituent_toggle(self, toggle)))
        self.structural.append(toggle)
        self.constituent_table.add_widget(toggle)

    def remove_constituent_press(self):

        if len(self.constituent_name) == 1:
            pass
        
        else:
            self.constituent_table.remove_widget(self.constituent_name[-1])
            del self.constituent_name[-1]

            self.constituent_table.remove_widget(self.constituent_arealweight[-1])
            del self.constituent_arealweight[-1]

            self.constituent_table.remove_widget(self.structural[-1])
            del self.structural[-1]

    def create_ply_click(self):
        print("create ply click")


#Screen manager code
class SplashScreen(Screen):
    size_hint = (1, 1)
    pass

class CreatePlyScreen(Screen):
    size_hint = (1, None)
    pass

screen_manager = ScreenManager()
screen_manager.add_widget(SplashScreen(name="splash_screen"))
screen_manager.add_widget(CreatePlyScreen(name="create_ply_screen"))

#Run loop
DISCreatormanage().run()

The KV file:

#This file contains the graphical user interface elements of the DIS creator app

#This is the layout of the entire screen
MainLayout:

<MainLayout@BoxLayout>:
    #Background colour
    canvas.before:
        Color:
            rgba:(.3,.3,.3,1)
        Rectangle:
            pos: self.pos
            size: self.size

    padding: '10dp'
    spacing: '10dp'
    Menu:
    FocusFrame:

#This is the layout for the menu which remains in place at all times.
<Menu>:
    #Background colour
    canvas.before:
        Color:
            rgba:(0,0,0,1)
        Rectangle:
            pos: self.pos
            size: self.size

    #Dimension control
    size_hint: None, 1
    width: "160dp"
    spacing: "2dp"
    padding: "10dp"

    Button:
        text: "Create ply"
        size_hint: 1, None
        height: "40dp"
        on_press:
            root.start_create_ply()


#This places layouts in their respective screen classes

<SplashScreen>:
    Splash:

<CreatePlyScreen>:
    CreatePly:
        height: self.minimum_height

#This is the layout for the scrollable focus frame which will be changed on press of a menu button
<FocusFrame@ScrollView>:
    canvas.before:
        Color:
            rgba:(0,0,0,1)
        Rectangle:
            pos: self.pos
            size: self.size
    
    ScreenManager:
        SplashScreen:
            name: "splash_screen"
        CreatePlyScreen:
            name: "create_ply_screen"

Solution

  • Several problems. One is that your code:

    screen_manager = ScreenManager()
    screen_manager.add_widget(SplashScreen(name="splash_screen"))
    screen_manager.add_widget(CreatePlyScreen(name="create_ply_screen"))
    

    defines a ScreenManager and adds Screens to it, but that screen_manager is not used in your GUI. Your code:

    class Menu(StackLayout):
    
    #Menu funcitons
        def start_create_ply(self):
            screen_manager.current = "create_ply_screen"
    

    uses that screen_manager, but since that screen_manager is not part of your GUI (as defined by your kv file), that code has no visible effect. Those lines that define screen_manager can be eliminated. Properties can be defined in your kv to allow easy access to the correct ScreenManager. With the changes to your kv file that I show below, you can replace the Menu code with:

    class Menu(StackLayout):
    
        # Menu funcitons
        def start_create_ply(self):
            screen_manager = App.get_running_app().root.fframe.screen_manager  # uses properties defined in kv
            screen_manager.current = "create_ply_screen"
    

    Another issue is setting the height of the ScreenManager to fit its current Screen. This can be done using on_size: in the Screens to adjust the ScreenManager size in your kv file

    Here is my modified version of your kv file with changes that I made commented:

    #This file contains the graphical user interface elements of the DIS creator app
    
    #This is the layout of the entire screen
    MainLayout:
    
    <MainLayout@BoxLayout>:
        fframe: ff  # creates a property that references the FocusFrame through the id "ff"
        #Background colour
        canvas.before:
            Color:
                rgba:(.3,.3,.3,1)
            Rectangle:
                pos: self.pos
                size: self.size
    
        padding: '10dp'
        spacing: '10dp'
        Menu:
        FocusFrame:
            id: ff  # used in the above "fframe" property
    
    #This is the layout for the menu which remains in place at all times.
    <Menu>:
        #Background colour
        canvas.before:
            Color:
                rgba:(0,0,0,1)
            Rectangle:
                pos: self.pos
                size: self.size
    
        #Dimension control
        size_hint: None, 1
        width: "160dp"
        spacing: "2dp"
        padding: "10dp"
    
        Button:
            text: "Create ply"
            size_hint: 1, None
            height: "40dp"
            on_press:
                root.start_create_ply()
    
    
    #This places layouts in their respective screen classes
    
    <SplashScreen>:
        Splash:
    
    <CreatePlyScreen>:
        size_hint_y: None
        height: cp.height  # Screen height adjusts to fit its child
        CreatePly:
            id: cp  # added for use in setting height of containing Screen
            height: self.minimum_height
    
    #This is the layout for the scrollable focus frame which will be changed on press of a menu button
    <FocusFrame@ScrollView>:
        screen_manager: sm  # creates a property that references the ScreenManager through the id "sm"
        
        ScreenManager:
            id: sm  # used in the above "screen_manager" property
            size_hint_y: None  # to allow setting height
            SplashScreen:
                name: "splash_screen"
                on_size:
                    self.manager.height=self.height  # adjusts manager height
            CreatePlyScreen:
                name: "create_ply_screen"
                on_size:
                    self.manager.height=self.height  # adjusts manager height