Problem with Screen Manager in Kivy Python - python

What I want to do is to switch the screen when the "join" button is pressed.
This is what I have written.
import kivy
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
class ConnectingPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 2
self.add_widget(Label(text = "Usename:"))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text = "Password:"))
self.password = TextInput(multiline=False,password = True)
self.add_widget(self.password)
self.joinbutton = Button(text="Join")
self.joinbutton.bind(on_press = self.click_join_button)
self.add_widget(Label())
self.add_widget(self.joinbutton)
def click_join_button(self, instance):
username = self.username.text
password = self.password.text
info = "you can enter"
MyApp.screen_manager.current = "Info"
# Simple information/error page
class InfoPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.message = Label(text = "welcome",halign="center", valign="middle", font_size=30)
self.add_widget(self.message)
class MyApp(App):
def build(self):
self.screen_manager = ScreenManager()
self.connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(self.connecting_page)
# Info page
self.info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(self.info_page)
return ConnectingPage()
MyApp().run()
Everything works fine in the starting but when the button "join" is pressed, this is the error:
AttributeError: type object 'MyApp' has no attribute 'self'
Please suggest what to do next.

Your build() method returns ConnectingPage(), so your App is not actually using a ScreenManager. All the code in that build() method (except that return) has no effect. Here is a modified version of your MyApp class that actually uses ScreenManager:
class MyApp(App):
screen_manager = ScreenManager() # this make screen_manager a class vaiable
def build(self):
# self.screen_manager = ScreenManager()
self.connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(self.connecting_page)
self.screen_manager.add_widget(screen) # add screen to ScreenManager
# Info page
self.info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(self.info_page)
self.screen_manager.add_widget(screen) # add screen to ScreenManager
# return ConnectingPage()
return self.screen_manager
This allows your code:
MyApp.screen_manager.current = "Info"
to work, but it is an odd construction.
A more typical construction would be something like this:
class MyApp(App):
def build(self):
screen_manager = ScreenManager()
connecting_page = ConnectingPage()
screen = Screen(name='Connect')
screen.add_widget(connecting_page)
screen_manager.add_widget(screen) # add screen to ScreenManager
# Info page
info_page = InfoPage()
screen = Screen(name='Info')
screen.add_widget(info_page)
screen_manager.add_widget(screen) # add screen to ScreenManager
# return ConnectingPage()
return screen_manager # return the ScreenManager
But this requires a change in the code that changes the Screen:
App.get_running_app().root.current = "Info"

Related

How can I change screen using bottom sheet in kivymd?

I have made a simple app in kivymd. But I can not change screen on click on button inside kivymd. Everything works great. But when I click on button then it popup toast also but screen is not changing. What will be changes or better implementation for this?
app.py
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
from main_screen_str import helper_string
from kivy.core.window import Window
from kivymd.toast import toast
from kivymd.uix.bottomsheet import MDGridBottomSheet
Window.size = (300, 500)
class MainScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class AboutScreen(Screen):
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main_screen"))
self.sm.add_widget(SettingsScreen(name="settings_screen"))
self.sm.add_widget(AboutScreen(name="about_screen"))
self.main_str = Builder.load_string(helper_string)
def build(self):
screen = Screen()
screen.add_widget(self.main_str)
return screen
def callback_for_menu_items(self, *args):
if args[0] == 'Home':
toast(args[0])
self.sm.current = "main_screen"
if args[0] == 'Settings':
toast(args[0])
self.sm.current = "settings_screen"
if args[0] == 'About':
toast(args[0])
self.sm.current = "about_screen"
def show_example_grid_bottom_sheet(self):
self.bottom_sheet_menu = MDGridBottomSheet()
data = {
"Home": "home",
"Settings": "settings",
"About": "information-outline",
}
for item in data.items():
self.bottom_sheet_menu.add_item(
item[0],
lambda x, y=item[0]: self.callback_for_menu_items(y),
icon_src=item[1],
)
self.bottom_sheet_menu.open()
if __name__ == '__main__':
MainApp().run()
This builder string to create screen.
Are there any better solution for this?
builder string
helper_string = """
ScreenManager:
MainScreen:
SettingsScreen:
AboutScreen:
<MainScreen>:
name: 'main_screen'
MDIconButton:
icon: "menu"
theme_text_color: "Custom"
text_color: 1,0,0,1
on_press: app.show_example_grid_bottom_sheet()
<SettingsScreen>:
name: 'settings_screen'
<AboutScreen>:
name: 'about_screen'
"""
In your __init__() method of the App, you are building self.sm with the lines:
self.sm = ScreenManager()
self.sm.add_widget(MainScreen(name="main_screen"))
self.sm.add_widget(SettingsScreen(name="settings_screen"))
self.sm.add_widget(AboutScreen(name="about_screen"))
But self.sm is not used as part of your GUI. So your changes to self.sm has no effect on your GUI. The line following that:
self.main_str = Builder.load_string(helper_string)
basically does exactly the same thing as the previous lines.
Then in your build() method, you are creating a new Screen and adding the self.main_str as a child of that Screen.
While you can have a ScreenManager as a child of a Screen, in your posted example that does not seem to serve any purpose.
Here is a modified version of part of the MainApp that I think will do what you want:
class MainApp(MDApp):
# def __init__(self, **kwargs):
# super().__init__(**kwargs)
# self.sm = ScreenManager()
# self.sm.add_widget(MainScreen(name="main_screen"))
# self.sm.add_widget(SettingsScreen(name="settings_screen"))
# self.sm.add_widget(AboutScreen(name="about_screen"))
#
# self.main_str = Builder.load_string(helper_string)
def build(self):
self.sm = Builder.load_string(helper_string)
return self.sm
# screen = Screen()
# screen.add_widget(self.main_str)
# return screen
The above code greatly simplifies the build() method, eliminates the __init__() method, and now self.sm is actually part of the GUI.
Note that when you load a kv string that has a root node with Builder.load_string(), that root node is created and returned. The lines in your kv string:
ScreenManager:
MainScreen:
SettingsScreen:
AboutScreen:
result in a ScreenManager instance being created along with the three children listed for it, so the code in your __init__() method was duplicating that.

screen update in screen manager

i'm using kivy for building an app screen with 3 screens.
my main screen ( class Base) is showing data from a SQL request.
i would like the user to be able to update these data using a button.
first this class Base is called in the screen manager which is itself called in my root class.
So my question is , how do i clear the data in my class Base and update it with the new data?
i tried to clear the data in my class screen manager.
the refresh function is called from the root class.
i have the below error:
'ScreenManager uses remove_widget only for removing Screens'
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from sql import runsql2
from kivy.core.window import Window
class Manager(ScreenManager):
def __init__(self):
super(Manager, self).__init__()
for i in range(2):
txt = 'Screen {}'.format(i)
lbl = Label(text=txt)
screen = Screen(name=txt)
screen.add_widget(lbl)
self.add_widget(screen)
base=Base('main')
self.add_widget(base)
def refresh(self):
self.clear_widgets(screens='main')
base=Base('main')
self.add_widget(base)
class Nav(GridLayout):
def __init__(self,sm=None):
super(Nav, self).__init__()
self.sm = sm
self.cols =3
self.size_hint = (1,0.1)
self.add_widget(Button(text="Clearing Screen", on_release=self.change))
self.add_widget(Button(text="Go screen 2", on_release=self.goscreen))
self.add_widget(Button(text="Quit", on_release=self.quit))
def change(self, btn):
#self.sm.current = btn.text
self.sm.current='main'
def quit(self,ins):
exit()
def goscreen(self,ins):
self.sm.current='Screen 1'
class Base(Screen):
def __init__(self,name):
super(Base, self).__init__()
self.lay=GridLayout()
self.name=name
self.bout=['[color=33ff99]Refresh[/color]','',"","","","","","","","",""]
self.data=runsql2()
self.lay.cols = 11
self.titre=['[color=ff9900]Market[/color]', '[color=ff9900]B/S[/color]', '[color=ff9900]Volume[/color]', '[color=ff9900]Contract[/color]',
'[color=ff9900]C/P[/color]', '[color=ff9900]Expiry[/color]', '[color=ff9900]Strike[/color]', '[color=ff9900]Price[/color]',
'[color=ff9900]Account[/color]', '[color=ff9900]Give up Member[/color]', '[color=ff9900]Allocation Account[/color]']
#self.lay.add_widget(Button(text='[color=33ff99]Refresh[/color]', size_hint=(1, 0.15), markup=True,on_release=self.do))
for i in range(11):
self.lay.add_widget(Label(text='', size_hint=(1, 0.15)))
for j in range(11):
self.lay.add_widget(Label(text=str(self.titre[j]),size_hint=(0.2,0.2),markup=True))
long = len(self.data)
for i in range(long):
for j in range(11):
self.lay.add_widget(Label(text=str(self.data[i][j])))
self.add_widget(self.lay)
class Root(BoxLayout):
def __init__(self):
super(Root, self).__init__()
self.orientation = "vertical"
#Window.clearcolor = (0.6, 0.6, 0.6,1)
sm = Manager()
self.add_widget(Nav(sm=sm))
self.add_widget(sm)
self.add_widget(Button(text='refresh',size_hint=(0.2,0.2),on_release=self.refresh))
Window.size = (1500, 900)
def refresh(self,ins):
sm=Manager()
sm.refresh()
class TestApp(App):
def build(App):
return Root()
if __name__ == '__main__':
TestApp().run()
Two problems with your code. First, in your refresh() method, the self.clear_widgets(screens='main') is incorrect. The screens arg must be a list of screens. So it should be
def refresh(self):
self.clear_widgets(screens=[self.get_screen('main')])
base=Base('main')
self.add_widget(base)
Since you are only removing one screen, you could use self.remove_widget(self.get_screen('main')) instead.
And, second, your refresh() method in the Root class is creating a new Manager class and calling the refresh() method of that new Manager rather than the one you have displayed. To correct this, you can save a reference to the original Manager, and use that reference in the refresh() method:
class Root(BoxLayout):
def __init__(self):
super(Root, self).__init__()
self.orientation = "vertical"
#Window.clearcolor = (0.6, 0.6, 0.6,1)
sm = Manager()
self.sm = sm # keep a reference for later use
self.add_widget(Nav(sm=sm))
self.add_widget(sm)
self.add_widget(Button(text='refresh',size_hint=(0.2,0.2),on_release=self.refresh))
Window.size = (1500, 900)
def refresh(self,ins):
self.sm.refresh()

Having difficulties making multiple screens using Python Kivy

I am just trying to get code working where I have two screens in a Python Kivy app that can switch back and forth, without using the .kv file stuff.
On this page: https://kivy.org/docs/api-kivy.uix.screenmanager.html, the second block of code from the top is what I am trying to accomplish, except I want to do it without the 'Builder.load_string("""' section, and instead just instantiate buttons normally.
Here is my attempt at doing so, except I can't get it to work:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class MenuScreen(Screen):
def build(self):
def switchScreen():
root.manager.current = 'settings'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
class SettingsScreen(Screen):
def build(self):
def switchScreen():
root.manager.current = 'menu'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MainApp(App):
def build(self):
return sm
if __name__ == '__main__':
MainApp().run()
Running this code just creates a blank page that produces no errors.
Is there a way to designate it to draw a certain screen to begin with that I am missing? I'm not really sure where my issue is.
What you did wrong:
If you want to create Widget content from Python code you should place it inside Widget __init__ method, not build
You're creating a layout and then discarding it. You need to use self.add_widget(f) to actually use it after its creation
You're binding to your switchScreen method, so it needs to accept caller widget as an argument. Or you can simply use *args and not worry about it.
You're not in kv anymore, so there's no root. Use self instead.
Putting this all together:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class MenuScreen(Screen):
def __init__(self, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
def switchScreen(*args):
self.manager.current = 'settings'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
self.add_widget(f)
class SettingsScreen(Screen):
def __init__(self, **kwargs):
super(SettingsScreen, self).__init__(**kwargs)
def switchScreen(*args):
self.manager.current = 'menu'
f = FloatLayout()
button1 = Button(text = "My settings button")
button2 = Button(text = "Back to menu", on_press = switchScreen)
f.add_widget(button1)
f.add_widget(button2)
self.add_widget(f)
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class MainApp(App):
def build(self):
return sm
if __name__ == '__main__':
MainApp().run()

Bind function to Kivy button

I'm trying to bind the following function to a Button in Kivy.
def auth(self):
print(self.username)
if self.username == "Hendricko":
print("self.username == Hendricko")
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
I've tried
class Foo():
def initUI(self):
self.add_widget(Button(text="Auth User and Password", on_press=self.auth))
but this doesn't work. What am I doing wrong?
here is my whole code
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello", on_press=self.auth)
self.add_widget(self.hello)
def auth(self):
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I don't think any of the answers are very clear. Neither explains that problem is that the callback given to on_press gets called with a parameter, the instance of button, so LoginScreen.auth must accept a parameter after the self:
def auth(self, button):
print('button pressed:', instance)
The problem is not that on_press must be given via Button.bind or that the callback must be a function, it can be a bound method, and the docs cited by other answer and by comments link to ButtonbBhavior which indicates that OP use of on_press in constructor was fine:
self.hello = Button(text="hello", on_press=self.auth)
would have worked if auth had been as described above.
If you read the Button documentation, the key seems to be to use the bind function:
def callback(instance):
print('The button <%s> is being pressed' % instance.text)
btn1 = Button(text='Hello world 1')
btn1.bind(on_press=callback)
Replace line
self.hello = Button(text="hello", on_press=lambda a:self.auth())
of your code and use this :
self.hello = Button(text="hello", on_press=lambda a:self.auth())
Also add below line in auth function to see if its called :)
print "auth called"
and There are many ways to perform a particular task .Above code will be to fix your code in minimum effort , However If you would like to do it in another way . Just use code below .
from kivy.uix.popup import Popup
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.stacklayout import StackLayout
class LoginScreen(GridLayout):
def __init__(self, **kwargs):
super(LoginScreen, self).__init__(**kwargs)
self.cols = 2
self.row = 2
self.add_widget(Label(text='User Name'))
self.username = TextInput(multiline=False)
self.add_widget(self.username)
self.add_widget(Label(text='password'))
self.password = TextInput(password=True, multiline=False)
self.add_widget(self.password)
self.hello = Button(text="hello")
self.hello.bind(on_press=self.auth)
self.add_widget(self.hello)
def auth(self,instance):
print "auth called"
if self.username == "Hendricko":
popup = Popup(title="success",
content=Label(text="Howdy !"),
size=(100, 100),
size_hint=(0.3, 0.3),
auto_dismiss=False)
popup.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__ == '__main__':
MyApp().run()
I report an example of buttons created dynamically within the main class, and then triggered into a single listener:
class allMyApp(TabbedPanel):
def __init__(self, **kwargs):
super(allMyApp, self).__init__(**kwargs)
#[...]
for y in sorted(set(emissionYears)):
btn = Button(text = y,
size_hint_y = None,
height = '48dp',
on_release = lambda btn: self.ids.choseEmissionDate.select(btn.text))
btn.bind(on_press = self.sortByYear)
self.ids.choseEmissionDate.add_widget(btn)
def sortByYear(self, instance):
year = instance.text
print(year)

widget won't display until screen size changed

I want to make a game and I have chosen Kivy for my GUI, as I have written my backend in Python. I am currently using runTouchApp(appwindow) to run the application, where appwindow is a FloatLayout() object. The way I update my screen is that I run appwindow.clear_widgets() and then appwindow.add_widget(new_screen) where new_screen is the layout object which contains all other widgets.
It worked fine till now, but for some reason I can't understand the widget I add gets loaded properly (according to the cmd running in the background) but won't display till I change the screen size.
Here is a sample code:
import kivy
kivy.require('1.1.1')
from kivy.base import runTouchApp
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
appwindow = FloatLayout()
class MenuScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
menu_items = []
for i in range (0,4):
button = Button(text = 'Menu item no:'+str(i))
button.bind(state = checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size = bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1,1
return floater
class SecondScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
box.add_widget(bg)
return box
def checkMenuPress(button,*args):
if button.state == 'normal':
sc = SecondScreen().build()
appwindow.clear_widgets()
appwindow.add_widget(sc)
if __name__ == '__main__':
menuscreen = MenuScreen().build()
appwindow.add_widget(menuscreen)
runTouchApp(appwindow)
Okay, there are a few things wrong here. To create an app, you should subclass kivy.app.App and have it's build method return the "root" widget. Only this class needs build method: any others are redundant.
Once you have a root widget, it's just a matter of inserting and removing widgets. This is how I would approach it.
import kivy
kivy.require('1.1.1')
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
class MenuScreen(App):
floater = None
def build(self):
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
self.floater = FloatLayout(size=bg.size)
self.floater.add_widget(bg)
self.floater.add_widget(box)
self.floater.size_hint = 1, 1
return self.floater
def checkMenuPress(self, button,*args):
if button.state == 'normal':
self.floater.clear_widgets()
self.floater.add_widget(SecondScreen())
class SecondScreen(FloatLayout):
def __init__(self, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
if __name__ == '__main__':
MenuScreen().run()
Okay, so here's a quick rework of the example using Screens. It's difficult without knowing what you are trying to do, so if this does not help, try writing summary of the behavior you are looking for.
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
"""
The Screen containing the menu
"""
def __init__(self, sm, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
self.sm = sm # Store a reference to the screen manager
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.check_menu_press)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size=bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1, 1
self.add_widget(floater)
def check_menu_press(self, button, *args):
if button.state == 'normal':
self.sm.current = "second"
class SecondScreen(Screen):
def __init__(self, sm, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
self.sm = sm # Store a reference to the screen manager
def on_touch_down(self, touch):
self.sm.current = "menu"
class MainApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(sm, name="menu"))
sm.add_widget(SecondScreen(sm, name="second"))
return sm
if __name__ == '__main__':
MainApp().run()
looks like i have found a source of the crashing problem. I had used multi-threading for a server client interaction. i had been using two extra threads, one for the server and another for the client. If i try to integrate the client thread with the GUI it raises another problem. even if the socket.recv() is after the ScreenManager.current = "newscreen" line, the app's screen does not change. it freezes in the way i left it. it resumes only after it gets a message from the server.

Categories

Resources