I am having difficulty figuring out how to correctly change screens using the on_press attribute of a button inside of a ModalView widget.
On pressing the button in the ModalView, I want the screen to change to the game_screen_name defined in the Game1HomeScreen class and other GameHomeScreen classes (as is done with the NewGameButton and SavedGameButton below). This app has multiple games, so I would rather not make a call directly to Game1HomeScreen1().game_screen_name and want to instead keep it generic, so game_screen_name takes on the value of the class from which NewGamePopup is called.
What is a good way to do this?
The main.py code:
class Game1HomeScreen(Screen):
game_screen_name = 'game1_gameboard_screen_name'
class NewGamePopup(ModalView):
pass
class GamesApp(App):
sm = ScreenManager()
def show_new_game_popup(self):
p = NewGamePopup()
p.open()
def prev_screen(self):
self.sm.current = self.game_screen_name #this line does not work of course, because there is no game_screen_name variable in the NewGamePopup class.
The .kv code:
<NewGamePopup>:
size_hint: .5, .3
NewGameBoxLayout:
padding: [10,10,10,10]
orientation: 'vertical'
Label:
font_name: 'fonts/playce.ttf'
font_size: '14sp'
markup: True
text: '[color=#000000]Are you sure? Current game will be erased![/color]'
Button:
font_name: 'fonts/playce.ttf'
font_size: '14sp'
text: 'Confirm'
background_normal: 'img/red_button5.png'
background_down: 'img/red_button5.png'
size_hint_y: None
on_press: root.dismiss(); app.prev_screen()
<Game1HomeScreen>:
GeneralBoxLayout:
BannerGridLayout1:
BodyBoxLayout:
rows: 2
Image:
source: 'img/logo.png'
size_hint: (1.0,.9)
GridLayout:
cols: 2
spacing: '5dp'
padding: '5dp'
size_hint: (1.0,.1)
NewGameButton:
id: game1
on_press:
if saved_game1.disabled == False: app.show_new_game_popup()
else: root.manager.current = root.game_screen_name; saved_game1.disabled = False
SavedGameButton:
id: saved_game1
on_press: root.manager.current = root.game_screen_name;
FooterGridLayout:
ReturnButton:
text: 'Return to main menu'
on_press: root.manager.current = 'home'
Save the game screen name in a string property when the game is selected
from kivy.properties import StringProperty
....
class GamesApp(App):
game_screen_name = StringProperty('')
Then you can use the sm.current call later as needed. Too many things were left out of the code snippet in the question to create a working version; even the build method was missing.
Related
I have a dropdown menu in which I would like to know which value the user has selected so I can use it later in my program. This is my .kv code:
BoxLayout:
orientation: 'horizontal'
size_hint_x: 1
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: units_num_btn
text: '0'
size_hint_y: None
height: 44
on_parent: drop_player_numbers_units.dismiss()
on_release: drop_player_numbers_units.open(self)
DropDown:
id: drop_player_numbers_units
on_select: units_num_btn.text = '{}'.format(args[1])
on_select: app.return_player_numbers()
Button:
id: units_num_btn_1
text: '1'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('1')
Button:
id: units_num_btn_2
text: '2'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('2')
and so on.
My .py code is here:
class drop_content(DropDown):
pass
class PlayerScreen(Screen):
pass
class TheApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(PlayerScreen(name='player_setup'))
sm.current = 'player_setup'
return sm
def main():
Builder.load_file('menu.kv')
app = TheApp()
app.run()
if __name__ == '__main__':
main()
I have previously used a function such as this:
# .py example
def return_text(self):
text = self.root.get_screen('screen').ids.specific_id.text
print(text)
# .kv example
TextInput:
id: input
text: "2"
on_text: app.return_text()
which did return text using a Textinput type in my .kv file. I know it doesn't work for the dropdown menu since the text is not inputted in the same way. Do you know how I would do this?
Thanks in advance
from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.lang.builder import Builder
class TestScreen(Screen):
def return_player_numbers(self,play_number):
print('Test : ',play_number)
kv = '''
ScreenManager:
TestScreen:
<TestScreen>:
BoxLayout:
orientation: 'horizontal'
size_hint_x: 1
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: units_num_btn
text: '0'
size_hint_y: None
height: 44
on_parent: drop_player_numbers_units.dismiss()
on_release: drop_player_numbers_units.open(self)
DropDown:
id: drop_player_numbers_units
on_select:
units_num_btn.text = '{}'.format(args[1])
app.root.current_screen.return_player_numbers(args[1])
app.return_player_numbers(args[1])
Button:
id: units_num_btn_1
text: '1'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('1')
Button:
id: units_num_btn_2
text: '2'
size_hint_y: None
height: 35
on_release: drop_player_numbers_units.select('2')
'''
class TheApp(App):
def return_player_numbers(self,play_number):
print('App : ',play_number)
def build(self):
return Builder.load_string(kv)
if __name__ == '__main__':
TheApp().run()
I have found a way that I think is simpler than the current answer provided. This method uses that fact that the text on the button used to initiate the drop down changes as another button is selected. This is to show the user what they have selected. Since I want to know the text the user has selected from the drop down menu, and this is in fact the same thing that is updated on the initial button, I can just read the text from the button to obtatin my result like so:
(in .kv file)
<player_setup>
BoxLayout:
orientation: 'horizontal'
size_hint_x: 0.2
pos_hint: {'x':.4, 'y':0}
Button:
pos_hint:{'center_x': .5, 'center_y': .5}
id: tens_num_btn
text: '0'
size_hint_y: None
# size_hint_x: 0.1
height: 44
on_parent: drop_player_numbers_tens.dismiss()
on_release: drop_player_numbers_tens.open(self)
DropDown:
id: drop_player_numbers_tens
on_select:
#######
tens_num_btn.text = '{}'.format(args[1])
# this line here ^ is the one that re-formats the text of the button above to
# the selected text
app.return_player_numbers()
max_height: 120
(.py file)
def return_player_numbers(self):
player_number = self.root.get_screen('player_setup').ids.tens_num_btn.text
return player_number
This also allows me to concatenate multiple dropdown results using a single function, however it is menu specific. In my case, this works better for me
I want to catch a value from my first screen into my thirdscreen.
In the first, I write my name in an input field.
I go to the next window.
And I try to show my name in this last window.
So I share the code with you and I hope I will find an issue.
Python code :
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen
#define ou different screens
class FirstWindow(Screen):
def envoyer(self):
name = self.ids.nom_input.text
self.ids.my_name.text = name
class SecondWindow(Screen):
pass
class ThirdWindow(Screen):
#PROBLEM HERE
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
class WindowManager(ScreenManager):
pass
class MonWidget(Widget):
pass
kv = Builder.load_file('new_window.kv')
class AwesomeApp(App):
def build(self):
Window.clearcolor = (0,0,0,0)
return kv
if __name__ == '__main__':
AwesomeApp().run()
My KV CODE :
WindowManager:
FirstWindow:
SecondWindow:
ThirdWindow:
<FirstWindow>:
name: "romain"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
id: my_name
text: "Entrez votre nom"
font_size: 32
TextInput:
id: nom_input
multiline: False
size_hint: (1, .5)
Button:
text: "Next screen"
font_size: 32
on_press: root.envoyer()
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "left"
<SecondWindow>:
name: "Mickael"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Entre votre ville"
font_size: 32
TextInput:
id: ville_input
multiline: False
size_hint: (1, .5)
Button:
text: "VĂ©rifier les infos"
font_size: 32
on_release:
app.root.current = "foncier"
root.manager.transition.direction = "left"
Button:
text: "go back first screen"
font_size: 32
on_release:
app.root.current = "romain"
root.manager.transition.direction = "right"
<ThirdWindow>:
name: "foncier"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Verifier : "
font_size: 32
Label:
id: recup_infos
text: ""
font_size: 32
color: 'white'
Button:
text: "On press"
font_size: 32
#Problem HERE
on_press: root.on_press()
Button:
text: "Précedent"
font_size: 32
on_release:
app.root.current = "Mickael"
root.manager.transition.direction = "right"
Could you help me ?
Thank you
Romain
In your on_press method:
def on_press(self):
self.ids.recup_infos.text = self.root.get_screen('FirstWindow').ids.my_name.text
self.root.get_screen('FirstWindow').ids.my_name.text isn't the correct way to get access to widgets outside of the class that you are in right now, or in this situation, screen. The correct way is to use the App.get_running_app() method:
self.ids.recup_infos.text = App.get_running_app().root.ids.First.ids.my_name.text
But before doing that, you have to give ids to the screens of your app, so that the First argument of the method demonstrated above actually makes sense:
WindowManager:
FirstWindow:
id: First
# "First" is the id of the FirstWindow class
# which can also explain why there was a "First" arg
# inside "App.get_running_app().root.ids.First.ids.my_name.text"
SecondWindow:
id: Second
ThirdWindow:
id: Third
Still confused to why this works? Let's divide the attributes of App.get_running_app().root.ids.First.ids.my_name.text into 3 parts:
App.get_running_app(): this method returns the location of your running App class, in this case AwesomeApp. This also acts as self if you were to get the variable inside the App object itself
.root.ids.First: if you read the Kivy documentation, or just simply watched Kivy course videos online, carefully, you should know that self.root.ids inside the App object returns a list of ids of the widgets inside your root widget. In this case, App.get_running_app().root.ids is doing the same thing here, and your screens are passed in the ScreenManager root widget, hence make First an available attribute in App.get_running_app().root.ids
.ids.my_name.text: same as above, App.get_running_app().root.ids.First acts the same as self if you were to run it in your FirstWindow class, which gives you the opportunity to get access to variables outside of your working classes/screens
I'm trying to make a list of screens and randomly select one of them in this python file (other code is not included here, like builder etc):
class selectionScreen
testScreen = StringProperty('Screen1')
def screenSelector(self)
screenList = ['Screen1', 'Screen2']
testNumber = random.randint(0, 1)
testScreen = screenList[testNumber]
return testscreen
I then want to choose this screen in my .kv-file, which looks somewhat like this, but also includes screen 1 and 2
ScreenManager:
SelectionScreen:
name: 'SelectionScreen'
Screen0:
name: 'Screen0'
Screen1:
name: 'Screen1'
<SelectionScreen>:
BoxLayout:
Button:
text: "Click when ready"
size_hint: .6, .5
font_size: 10
on_press: root.screenSelector()
on_release: root.manager.current = 'Screen0'
<Screen0>:
BoxLayout:
orientation: 'vertical'
Button:
text: "Next"
on_release: root.manager.current = str(root.testScreen)
The SelectionScreen is working fine, but now I am not able to reach screen0. When removing the line
on_press: root.screenSelector()
I can, however, do this.
Thank you in advance for any help!
Problem
You are not able to reach screen0 because it is not in your screenList.
The variable, testScreen defined in method screenSelector is a local variable, which is not the same class variable, testScreen defined in class selectionScreen.
Undefined variable, return testscreen due to typo error.
At Screen0, the code, on_release: root.manager.current = str(root.testScreen) will cause an AttributeError: 'Screen0' object has no attribute 'testScreen'
Solution
In order to reach Screen0, you have to do the following:
Python Script
Add Screen0 into the variable, screenList
Replace random.randint(0, 1) with random.randint(0, 2)
Replace testScreen with self.testScreen in method screenSelector
kv file
Define an id, e.g. id: selection_screen
Reference it, e.g. root.manager.ids.selection_screen.testScreen
Add on_press call to screenSelector in the other screens, e.g. on_press: root.manager.ids.selection_screen.screenSelector()
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty
import random
class ScreenManager(ScreenManager):
pass
class SelectionScreen(Screen):
testScreen = StringProperty('Screen1')
def screenSelector(self):
screenList = ['Screen0', 'Screen1', 'Screen2']
testNumber = random.randint(0, 2)
self.testScreen = screenList[testNumber]
return self.testScreen
class Screen0(Screen):
pass
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class Test(App):
def build(self):
return ScreenManager()
if __name__ == "__main__":
Test().run()
test.kv
#:kivy 1.10.0
<ScreenManager>:
SelectionScreen:
id: selection_screen
name: 'SelectionScreen'
Screen0:
name: 'Screen0'
Screen1:
name: 'Screen1'
Screen2:
name: 'Screen2'
<SelectionScreen>:
BoxLayout:
Button:
text: "Click when ready"
size_hint: .6, .5
font_size: 10
on_press: root.screenSelector()
on_release:
root.manager.current = str(root.manager.ids.selection_screen.testScreen)
<Screen0>:
BoxLayout:
orientation: 'vertical'
Button:
text: "Screen 0"
on_press: root.manager.ids.selection_screen.screenSelector()
on_release:
root.manager.current = str(root.manager.ids.selection_screen.testScreen)
<Screen1>:
BoxLayout:
orientation: 'vertical'
Button:
text: "Screen 1"
on_press: root.manager.ids.selection_screen.screenSelector()
on_release:
root.manager.current = str(root.manager.ids.selection_screen.testScreen)
<Screen2>:
BoxLayout:
orientation: 'vertical'
Button:
text: "Screen 2"
on_press: root.manager.ids.selection_screen.screenSelector()
on_release:
root.manager.current = str(root.manager.ids.selection_screen.testScreen)
Output
I have an app with 2 screens (ScreenManager).
I wrote a function in the second screen (SettingsScreen) and I want this function to update a label in the first screen.
Below the two classes:
class MenuScreen(Screen):
count = NumericProperty(30)
class SettingsScreen(Screen):
def set_20(self):
self.count = 20
The classes are linked to a button and a label in the Kivy stylesheet with two different screens
<MenuScreen>:
FloatLayout:
orientation: 'horizontal'
Label:
id: l_label
text: str(root.count)
<SettingsScreen>:
BoxLayout:
orientation: 'vertical'
padding: 50
FloatLayout:
Button:
text: "20"
on_release: root.set_20()
To be clearer, the user has to click on the Button in the SettingsScreen to set the value of the NumericProperty in the first screen to 20.
At the moment, if I click on the button nothing happens.
What you see above is an extract - The full code of the app is stored below if you want to see more.
https://github.com/marcogdepinto/LifeCounter
Thanks in advance for your help.
I think the problem is that when your button gets clicked and calls the set_20 method, that method is trying to set the property count of the SettingsScreen to 20. In other words
def set_20(self):
self.count = 20
would try to set the the count property inside the SettingsScreen (hence the word self) and it cannot find it. There is another count property inside MenuScreen which it knows nothing about. To fix this I think you should give an id to MenuScreen
<MenuScreen>:
id: menu
and in the on_release method of the Button do
on_release: menu.count = 20
EXAMPLE
Python file
class MenuScreen(Screen):
counter = NumericProperty(0)
class SettingsScreen(Screen):
pass
class MyWidget(ScreenManager):
pass
class MyRandomApp(App):
def build(self):
return MyWidget()
if __name__ == '__main__':
MyRandomApp().run()
kv file
<MyWidget>:
id:manager
MenuScreen:
id: menu
name: 'menuscreen'
BoxLayout:
orientation: 'vertical'
Label:
text: str(menu.counter)
Button:
text: 'Go to S2'
on_press: manager.current = 'settingsscreen'
SettingsScreen:
id: settings
name: 'settingsscreen'
BoxLayout:
orientation: 'vertical'
Button:
text: 'Click to edit label of Prev screen'
on_press: menu.counter = 20
Button:
text: 'Go to S1'
on_press: manager.current = 'menuscreen'
I am trying create a GUI by implementing the template of the ComicCreator GUI sample as a template for my own project. The code is easy to follow, but I would like to be able to reconfigure the drawingspace.kv, each time a button is pushed, say for example something like this:
Q: How could I configure the drawingspace.kv to have a different layout with different widgets for each button that is pressed?
A neat way to do this is to use screen.
Since I allready have an example of this app from you earlier question, it was easy to implement the screens, and rewrite the classes a bit.
When a button is pressed, you set the screenmanager's current to whatever the name you named the screen you want.
Then you just edit the layouts as you want inside of each screen, in the kv file, or python file.
I choose to make most of the layout stuff in kv language here. Because I find it easier to develop a layout the way I want it this way.
Later I could rewrite it to python if I want that.
So my python file looks like this now:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock
from kivy.uix.screenmanager import Screen,ScreenManager,NoTransition
from kivy.lang import Builder
import time
Builder.load_file("kv.kv")
class MyLayout(BoxLayout):
def __init__(self,**kwargs):
super(MyLayout,self).__init__(**kwargs)
self.orientation = "vertical"
self.padding = 10
class MainScreen(Screen):
pass
class RemoveScreen(Screen):
pass
class GroupScreen(Screen):
pass
class MyLogo(BoxLayout):
your_time = StringProperty()
def __init__(self,**kwargs):
super(MyLogo,self).__init__(**kwargs)
Clock.schedule_interval(self.set_time, 0.1)
def set_time(self,dt):
self.your_time = time.strftime("%m/%d/%Y %H:%M")
class MyApp(App):
def __init__(self,**kwargs):
super(MyApp,self).__init__(**kwargs)
self.sm = ScreenManager(transition=NoTransition())
self.sm.add_widget(MainScreen(name = "main"))
self.sm.add_widget(RemoveScreen(name = "remove"))
self.sm.add_widget(GroupScreen(name = "group"))
self.sm.current = "main"
def build(self):
return self.sm
if __name__ == "__main__":
MyApp().run()
And kv.kv file looks like this:
#:kivy 1.9.1
<MyButtons#BoxLayout>:
padding: 10,10,10,0
spacing: 10
size_hint: 1,0.3
orientation: "horizontal"
Button:
text: "Clear"
on_press: app.sm.current = "main"
Button:
text: "Remove"
on_press: app.sm.current = "remove"
Button:
text: "Group"
on_press: app.sm.current = "group"
Button:
text: "Color"
Button:
text: "Gestures"
<MyLogo>:
spacing: 10
padding: 10,10,10,0
orientation: "horizontal"
BoxLayout:
orientation: "vertical"
size_hint: 0.3,1
canvas:
Rectangle:
pos: self.pos
size: self.size
AsyncImage
source: 'http://lmsotfy.com/so.png'
Label:
size_hint: 1,0.3
text: root.your_time
color: [0,0,0,1]
Label:
size_hint: 1,0.3
text: "NYC, New York, USA"
color: [0,0,0,1]
<MainScreen>:
MyLayout:
MyLogo:
#Button:
# text: "main"
MyButtons:
#buttons
BoxLayout:
padding: 10,10,10,10
size_hint: 1,0.3
Button:
text: "Total figures: 1 Kivy Started"
<RemoveScreen>:
MyLayout:
MyLogo:
BoxLayout:
orientation: "horizontal"
Label:
font_size: "40sp"
text: "Remove"
Button:
font_size: "20sp"
text: "Remove this or something"
MyButtons:
#buttons
BoxLayout:
padding: 10,10,10,10
size_hint: 1,0.3
Button:
text: "Total figures: 1 Kivy Started"
<GroupScreen>:
MyLayout:
MyLogo:
BoxLayout:
orientation: "vertical"
Label:
font_size: "40sp"
text: "Group"
Button:
font_size: "20sp"
text: "Something groups stuff"
MyButtons:
#buttons
BoxLayout:
padding: 10,10,10,10
size_hint: 1,0.3
Button:
text: "Total figures: 1 Kivy Started"
The layout frame should be a screen manager, and each layout a screen. Screen transitions would be then triggered by pressing the buttons. You can also watch a tutorial here if you don't know how to do this, but the documentation should be enough.