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"
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