Reputation: 3
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.
Upvotes: 0
Views: 2035
Reputation: 473
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.
Upvotes: 0
Reputation: 701
Let me show you Kivy screens basics. Below it's an example app with 3 screens. Each screen contain buttons:
On top of screenssample.kv
file there is a screens hierarchy. Class MyScreens
is a screen manager, parent of three screens: MyScreen1
, MyScreen2
, MyScreen3
.
To go to another screen you have to assign it's screen name to manager's current attribute manager.current
.
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen
# hierarhy:
# ScreensSample (App)
# |- MyScreens (ScreenManager)
# |- MyScreen1 (Screen)
# |- MyScreen2 (Screen)
# |- MyScreen3 (Screen)
class MyScreens(ScreenManager):
def screen_manager_method(self):
print('Hello from screen manager')
class MyScreen1(Screen):
def screen_method(self):
print('Hello from screen 1')
class MyScreen2(Screen):
def screen_method(self):
print('Hello from screen 2')
class MyScreen3(Screen):
def screen_method(self):
print('Hello from screen 3')
class ScreensSample(App):
def app_method(self):
print('Hello from app')
ScreensSample().run()
screenssample.kv
MyScreens:
MyScreen1:
MyScreen2:
MyScreen3:
<MyScreen1>:
name: 'screen_one'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 2'
on_release: root.manager.current = 'screen_two'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 2)'
on_release: root.manager.get_screen('screen_two').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
<MyScreen2>:
name: 'screen_two'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 3'
on_release: root.manager.current = 'screen_three'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 1)'
on_release: root.manager.get_screen('screen_one').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
<MyScreen3>:
name: 'screen_three'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 1'
on_release: root.manager.current = 'screen_one'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 1)'
on_release: root.manager.get_screen('screen_one').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
I hope this sample is easy to understand. But even based on this simple py and kv file you may notice that having all screens within one kv file and all screen classes within one py file will produce huge files hard to develop and maintain.
Below is same example but every screen is definied within its separate kv and py files. I think it makes code more readable, easier to develop/maintain.
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager
import screen1, screen2, screen3
# hierarhy:
# ScreensSample (App)
# |- MyScreens (ScreenManager)
# |- MyScreen1 (Screen)
# |- MyScreen2 (Screen)
# |- MyScreen3 (Screen)
class MyScreens(ScreenManager):
def screen_manager_method(self):
print('Hello from screen manager')
class ScreensSample(App):
def app_method(self):
print('Hello from app')
ScreensSample().run()
screenssample.kv
#:include screen1.kv
#:include screen2.kv
#:include screen3.kv
MyScreens:
MyScreen1:
MyScreen2:
MyScreen3:
screen1.py
from kivy.app import App
from kivy.uix.screenmanager import Screen
class MyScreen1(Screen):
def screen_method(self):
print('Hello from screen 1')
# self.manager.get_screen('screen_two').screen_method() # sample how to call other screen method from here
# self.manager.screen_manager_method() # sample how to call screen manager method from here
# App.get_running_app().app_method() # sample hot to call app method from here
screen1.kv
<MyScreen1>:
name: 'screen_one'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 2'
on_release: root.manager.current = 'screen_two'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 2)'
on_release: root.manager.get_screen('screen_two').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
screen2.py
from kivy.uix.screenmanager import Screen
class MyScreen2(Screen):
def screen_method(self):
print('Hello from screen 2')
screen2.kv
<MyScreen2>:
name: 'screen_two'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 3'
on_release: root.manager.current = 'screen_three'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 1)'
on_release: root.manager.get_screen('screen_one').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
screen3.py
from kivy.uix.screenmanager import Screen
class MyScreen3(Screen):
def screen_method(self):
print('Hello from screen 3')
screen3.kv
<MyScreen3>:
name: 'screen_three'
BoxLayout:
orientation: 'vertical'
Label:
text: f'I am {root.name}'
Button:
text: 'Go to screen 1'
on_release: root.manager.current = 'screen_one'
Button:
text: 'Go to next screen'
on_release: root.manager.current = root.manager.next()
Button:
text: 'Go to previous screen'
on_release: root.manager.current = root.manager.previous()
Button:
text: 'This screen method'
on_release: root.screen_method()
Button:
text: 'Other screen method (from screen 1)'
on_release: root.manager.get_screen('screen_one').screen_method()
Button:
text: 'Screen manager method'
on_release: root.manager.screen_manager_method()
Button:
text: 'App method'
on_release: app.app_method()
Additionally within screen1.py
I included how to access same methods which are accessed from kv files but from Python's screen method. As you may notice there is small difference how to access method (or attribute) from kv file and python code, like:
kv: root.manager.get_screen('screen_two').screen_method()
python: self.manager.get_screen('screen_two').screen_method()
kv: root.manager.screen_manager_method()
python: self.manager.screen_manager_method()
kv: app.app_method()
python: App.get_running_app().app_method()
Upvotes: 3