I've recently started using the Kivy framework to create an app with multiple screens that I would like to merge, so that, for example, pressing a button on the login screen opens another page. I've been trying to use the ScreenManager library for days but I can't get it to work, there are several tutorials on the internet but everyone uses different variations. Below I attach the code of the login page where, by pressing the "Log in" button, the second screen should open.
login.py:
from kivy.core.text import LabelBase
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.app import MDApp
Window.size = (350, 580)
kv = """
MDFloatLayout:
md_bg_color: 0, 0, 0, 1
Image:
source: "img\\logo5.png"
pos_hint: {"center_x": .5, "center_y": .85}
size_hint: .18, .18
MDFloatLayout:
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .68}
canvas:
Color:
rgb: 250/255, 250/255, 250/255, 1
RoundedRectangle:
size: self.size
pos:self.pos
radius: [4]
canvas.before:
Color:
rgb: 217/255, 217/255, 217/255, 1
Line:
width: 1.1
rounded_rectangle: self.x, self.y, self.width, self.height, 4, 4, 4, 4, 100
TextInput:
hint_text: "Phone number, username or e-mail"
size_hint: 1, None
pos_hint: {"center_x": .5, "center_y": .5}
height: self.minimum_height
background_color: 1, 1, 1, 0
font_size: "14sp"
font_name: "MRoboto"
hint_text_color: 170/255, 170/255, 170/255, 1
padding: 13
cursor_color: 0, 0, 0, 1
MDFloatLayout:
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .59}
canvas:
Color:
rgb: 250/255, 250/255, 250/255, 1
RoundedRectangle:
size: self.size
pos:self.pos
radius: [4]
canvas.before:
Color:
rgb: 217/255, 217/255, 217/255, 1
Line:
width: 1.1
rounded_rectangle: self.x, self.y, self.width, self.height, 4, 4, 4, 4, 100
TextInput:
hint_text: "Password"
size_hint: 1, None
pos_hint: {"center_x": .5, "center_y": .5}
height: self.minimum_height
background_color: 1, 1, 1, 0
font_size: "14sp"
font_name: "MRoboto"
password: "true"
hint_text_color: 170/255, 170/255, 170/255, 1
padding: 13
cursor_color: 0, 0, 0, 1
Button:
text: "Log in"
color: 1, 1, 1, 1
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .43}
background_color: 1, 1, 1, 0
font_size: "13sp"
font_name: "BRoboto"
canvas.before:
Color:
rgb: 98/255, 170/255, 243/255, 1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [4]
MDLabel:
text: "Don't have an account?"
color: 172/255, 172/255, 172/255, 1
pos_hint: {"center_x": .74, "center_y": .095}
font_size: "13sp"
font_name: "MRoboto"
MDTextButton:
text: "Sign up"
color: 98/255, 170/255, 243/255, 1
pos_hint: {"center_x": .685, "center_y": .095}
font_size: "13sp"
font_name: "MRoboto"
MDCheckbox:
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_x": .1, "center_x": .1}
on_active: app.show_password(*args)
MDLabel:
id: password_text
text: "Show Password"
pos_hint: {"center_x": .7, "center_x": .43}
"""
class Login(MDApp):
def build(self):
return Builder.load_string(kv)
def show_password(self, checkbox, value):
if value:
self.root.ids.password.password = False
self.root.ids.password_text.text = "Hide Password"
else:
self.root.ids.password.password = True
self.root.ids.password_text.text = "Show Password"
if __name__ == "__main__":
LabelBase.register(name="BRoboto", fn_regular="font\\Roboto-Bold.ttf")
LabelBase.register(name="MRoboto", fn_regular="font\\Roboto-Medium.ttf")
Login().run()
list.py:
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.image import Image
from kivymd.app import MDApp
from kivymd.uix.list import IRightBodyTouch, ILeftBody
from kivymd.uix.selectioncontrol import MDCheckbox
Window.size = (350, 580)
kv = """
<ListItemWithCheckbox@OneLineAvatarIconListItem>:
MyAvatar:
source: "data/logo/kivy-icon-128.png"
MyCheckbox:
<Lists@BoxLayout>
name: "lists"
orientation: "vertical"
MDTopAppBar:
title:"Hide the story to:"
md_bg_color: app.theme_cls.primary_color
elevation: 3
ScrollView:
MDList:
id: scroll
"""
Builder.load_string(kv)
class MyCheckbox(IRightBodyTouch, MDCheckbox):
pass
class MyAvatar(ILeftBody, Image):
pass
class Users(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
self.title = "Liste"
self.theme_cls.primary_palette = "Teal"
self.theme_cls.theme_style = "Dark"
list = Factory.Lists()
for i in range(30):
list.ids.scroll.add_widget(Factory.ListItemWithCheckbox(text="Item %d" % i))
self.root = list
if __name__ == "__main__":
Users().run()
In my tests I tried to instantiate the ScreenManager both in the login.py code and in a third new file created from scratch. Thanks in advance.
I updated your code, so everything is in login.py. It is using the screen manager to switch between screens. You could also put the list class in a list.py file and move the imports it uses to that file.
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.screenmanager import MDScreenManager
from kivy.factory import Factory
from kivy.uix.image import Image
from kivymd import app
from kivymd.uix.list import IRightBodyTouch, ILeftBody
from kivymd.uix.selectioncontrol import MDCheckbox
Window.size = (350, 580)
kv = """
WindowManager:
id: manager
LoginScreen:
id: login_screen
ListScreen:
id: list_screen
<LoginScreen>:
name: 'login_screen'
MDFloatLayout:
md_bg_color: 0, 0, 0, 1
Image:
source: "img_logo5.png"
pos_hint: {"center_x": .5, "center_y": .85}
size_hint: .18, .18
MDFloatLayout:
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .68}
canvas:
Color:
rgb: 250/255, 250/255, 250/255, 1
RoundedRectangle:
size: self.size
pos:self.pos
radius: [4]
canvas.before:
Color:
rgb: 217/255, 217/255, 217/255, 1
Line:
width: 1.1
rounded_rectangle: self.x, self.y, self.width, self.height, 4, 4, 4, 4, 100
TextInput:
hint_text: "Phone number, username or e-mail"
size_hint: 1, None
pos_hint: {"center_x": .5, "center_y": .5}
height: self.minimum_height
background_color: 1, 1, 1, 0
font_size: "14sp"
# font_name: "MRoboto"
hint_text_color: 170/255, 170/255, 170/255, 1
padding: 13
cursor_color: 0, 0, 0, 1
MDFloatLayout:
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .59}
canvas:
Color:
rgb: 250/255, 250/255, 250/255, 1
RoundedRectangle:
size: self.size
pos:self.pos
radius: [4]
canvas.before:
Color:
rgb: 217/255, 217/255, 217/255, 1
Line:
width: 1.1
rounded_rectangle: self.x, self.y, self.width, self.height, 4, 4, 4, 4, 100
TextInput:
hint_text: "Password"
size_hint: 1, None
pos_hint: {"center_x": .5, "center_y": .5}
height: self.minimum_height
background_color: 1, 1, 1, 0
font_size: "14sp"
# font_name: "MRoboto"
password: "true"
hint_text_color: 170/255, 170/255, 170/255, 1
padding: 13
cursor_color: 0, 0, 0, 1
Button:
text: "Log in"
color: 1, 1, 1, 1
size_hint: .9, .07
pos_hint: {"center_x": .5, "center_y": .43}
background_color: 1, 1, 1, 0
font_size: "13sp"
# font_name: "BRoboto"
canvas.before:
Color:
rgb: 98/255, 170/255, 243/255, 1
RoundedRectangle:
size: self.size
pos: self.pos
radius: [4]
on_release: root.manager.current = 'list_screen'
MDLabel:
text: "Don't have an account?"
color: 172/255, 172/255, 172/255, 1
pos_hint: {"center_x": .74, "center_y": .095}
font_size: "13sp"
# font_name: "MRoboto"
MDTextButton:
text: "Sign up"
color: 98/255, 170/255, 243/255, 1
pos_hint: {"center_x": .685, "center_y": .095}
font_size: "13sp"
# font_name: "MRoboto"
MDCheckbox:
size_hint: None, None
size: "48dp", "48dp"
pos_hint: {"center_x": .1, "center_x": .1}
on_active: app.show_password(*args)
MDLabel:
id: password_text
text: "Show Password"
pos_hint: {"center_x": .7, "center_x": .43}
<ListItemWithCheckbox@OneLineAvatarIconListItem>:
MyAvatar:
source: "data/logo/kivy-icon-128.png"
MyCheckbox:
<ListScreen>
name: 'list_screen'
BoxLayout
name: "lists"
orientation: "vertical"
MDTopAppBar:
title:"Hide the story to:"
md_bg_color: app.theme_cls.primary_color
elevation: 3
ScrollView:
MDList:
id: scroll
"""
class WindowManager(MDScreenManager):
""" Window Manager """
pass
class ListItemWithCheckbox:
pass
class LoginScreen(MDScreen):
pass
class Login(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.primary_palette = "Teal"
self.theme_cls.theme_style = "Dark"
def build(self):
return Builder.load_string(kv)
def show_password(self, checkbox, value):
if value:
self.root.ids.password.password = False
self.root.ids.password_text.text = "Hide Password"
else:
self.root.ids.password.password = True
self.root.ids.password_text.text = "Show Password"
class MyCheckbox(IRightBodyTouch, MDCheckbox):
pass
class MyAvatar(ILeftBody, Image):
pass
class ListItemWithCheckbox:
pass
class ListScreen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_enter(self):
app.title = "Liste"
# my_list = Factory.Lists()
for i in range(30):
self.ids.scroll.add_widget(Factory.ListItemWithCheckbox(text="Item %d" % i))
# self.root = list
if __name__ == '__main__':
Login().run()
First I create the WindowManager using the kv language.
WindowManager:
id: manager
LoginScreen:
id: login_screen
ListScreen:
id: list_screen
Then I create each screen uning the format : followed by the name: 'screen_name' so I can reference it later.
<LoginScreen>:
name: 'login_screen'
MDFloatLayout:
md_bg_color: 0, 0, 0, 1
Image:
...
<ListScreen>
name: 'list_screen'
BoxLayout
...
In the Button definition in the kv language I use:
on_release: root.manager.current = 'list_screen'
The root part tells it to look in the current screen for the manager and set the current screen to 'list_screen'
The ListItemWithCheckbox@OneLineAvatarIconListItem: in the kv language, tell it to create a custom widget based on the OneLineAvatarIconListItem widget and set some options every time it is used.
In the Python part, I define the matching custom widgets and the matching WindowManager class.
class WindowManager(MDScreenManager):
""" Window Manager """
pass
class ListItemWithCheckbox:
pass
class MyCheckbox(IRightBodyTouch, MDCheckbox):
pass
class MyAvatar(ILeftBody, Image):
pass
class ListItemWithCheckbox:
pass
class LoginScreen(MDScreen):
pass
Then I define the 2 screens.
class LoginScreen(MDScreen):
pass
class ListScreen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_enter(self):
app.title = "Liste"
# my_list = Factory.Lists()
for i in range(30):
self.ids.scroll.add_widget(Factory.ListItemWithCheckbox(text="Item %d" % i))
The LoginScreen does not need any code in it so I just pass. The ListScreen uses the on_enter method which is run after the screen is fully drawn to set the window title and draw the checkboxes.
Hopefully I got all the explanations correct.