I am currently building an app for self measurement. I've faced the following problem : I want to have a result screen, with progress bars for my achievements. So far it looks like this:
<ResultScreen>:
BoxLayout:
Button:
text: 'Назад, к музыке'
on_press: root.manager.current = 'music'
ProgressBar:
max : 1000
value : root.manager.get_screen('music').slider.value
The problem is, root.manager works fine when applied to the Button and returns to the given screen. However in ProgressBar it appears to be None and returns the following error:
AttributeError: 'NoneType' object has no attribute 'get_screen'
What is wrong?
The full code:
main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import (
NumericProperty, ReferenceListProperty, ObjectProperty
)
from kivy.vector import Vector
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen
class PongScreen(Screen):
pass
class MusicScreen(Screen):
pass
class DiscreteScreen(Screen):
pass
class ResultScreen(Screen):
pass
class PongApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(PongScreen(name='menu'))
sm.add_widget(MusicScreen(name='music'))
sm.add_widget(DiscreteScreen(name='discrete'))
sm.add_widget(ResultScreen(name='result'))
return sm
if __name__ == '__main__':
PongApp().run()
pong.kv:
#:kivy 1.0.9
#:import Factory kivy.factory.Factory
<MusicScreen>:
BoxLayout:
Button:
text: 'Назад в меню'
on_press: root.manager.current = 'menu'
Button:
text: 'Дальше, к дискретке'
on_press: root.manager.current = 'discrete'
Slider:
id: slider
min: 0
max: 20
step: 1
orientation: 'vertical'
Label:
text: 'Тактов выучено\n' + str(slider.value)
CheckBox:
id : new_song_checkbox
Label:
text : 'Новую песню выучил?\n' + str(new_song_checkbox.active)
CheckBox:
id : music_pleasure_checkbox
Label:
text : 'Доволен ли исполнением\n' + str(music_pleasure_checkbox.active)
<DiscreteScreen>:
BoxLayout:
Button:
text: 'В меню'
on_press: root.manager.current = 'menu'
Button:
text: 'Назад, к музыке'
on_press: root.manager.current = 'music'
Slider:
id: slider
min: 0
max: 180
step: 15
orientation: 'vertical'
Label:
text: 'Минут на код затрачено\n' + str(slider.value)
CheckBox:
id : article_checkbox
Label:
text : 'Статью прочитал?\n' + str(article_checkbox.active)
CheckBox:
id : lecture_checkbox
Label:
text : 'Посмотрел ли лекцию?\n' + str(lecture_checkbox.active)
<DiscreteScreen>:
BoxLayout:
Button:
text: 'В меню'
on_press: root.manager.current = 'menu'
Button:
text: 'Назад, к музыке'
on_press: root.manager.current = 'music'
Button:
text: 'Дальше, к результатам'
on_press: root.manager.current = 'result'
Slider:
id: slider
min: 0
max: 180
step: 15
orientation: 'vertical'
Label:
text: 'Минут на код затрачено\n' + str(slider.value)
CheckBox:
id : article_checkbox
Label:
text : 'Статью прочитал?\n' + str(article_checkbox.active)
CheckBox:
id : lecture_checkbox
Label:
text : 'Посмотрел ли лекцию?\n' + str(lecture_checkbox.active)
<ResultScreen>:
BoxLayout:
Button:
text: 'Назад, к музыке'
on_press: root.manager.current = 'music'
ProgressBar:
max : 1000
value : root.manager.get_screen('music').slider.value
<PongScreen>:
BoxLayout:
Button:
text: 'К музыке'
on_press: root.manager.current = 'music'
Button:
text: 'К дискретке'
on_press: root.manager.current = 'discrete'
Button:
text: 'К результатам'
on_press: root.manager.current = 'result'
Button:
text: 'Выход'
on_press: app.stop()
First of all value : root.manager.get_screen('music').slider.value is wrong because root.manager.get_screen('music') is supposed to be a Screen object and by default it doesn't have (also not in your code) any prop. slide.
Perhaps you mean value : root.manager.get_screen('music').ids.slider.value. But this will not solve your problem. Let's try to analyze the problem in simple way.
When the method run is called, the kv-rules are applied (by method load_kv) first then method build is called. In the method build you defined ScreenManager first thereafter added the Screens and described the screens in kvlang. Now when it encounters your .kv file and looks for root.manager (in value : root.manager.get_screen('music').ids.slider.value) it finds None (the default value, as it has not yet been added to ScreenManager, defined in your build method). That's the reason behind the AttributeError.
Now to solve this issue one of many ways could be,
You delay the value assigning process by a (built-in or custom) method like on_enter etc. as,
<ResultScreen>:
on_enter: pb.value = self.manager.get_screen('music').ids.slider.value
BoxLayout:
Button:
text: 'Назад, к музыке'
on_press: root.manager.current = 'music'
ProgressBar:
id: pb
max : 1000
Set a conditional statement (likely to be preferable) as,
ProgressBar:
id: pb
max : 1000
value : root.manager.get_screen('music').ids.slider.value if root.manager is not None else 0
Define the root (i.e. ScreenManager here) entirely in kvlang.
Other ways could be creating a property for slide.value in the associate class; manage it in the build method etc.
Related
I have been writing a script to develop an app that calculates the results of Mahjong game.
I have created multiple screens to enter the players name and enter the score etc. in one of the screen I am entering the score and it is not getting saved. As soon as I move to other screen, the score data is lost. How can i save the score in the screen?
.py file looks like below
Window.size = (360, 600)
class MainScreen(Screen):
pass
class Player_Screen(Screen):
#pass
#def callback(self, text):
#self.ids.textbox.text = text
class ScreenManagement(ScreenManager):
shared_data = StringProperty("")
Mahjong = Builder.load_file('trial_app.kv') # Specifying location of kv file
class MainApp(App):
def build(self):
return Mahjong
if __name__ == "__main__":
MainApp().run()
my .kv file looks like below
#: import FadeTransition kivy.uix.screenmanager.FadeTransition
#: import App kivy.app.App
ScreenManagement:
#title: title # so I can use it in the .py file
#score:score # same here
transition: FadeTransition()
MainScreen:
id: title
Player_Screen:
id: Players
name: 'Players'
<MainScreen>:
name: "main"
canvas.before:
Color:
rgba: 0.2, 0.5, 1, 0.5
Rectangle:
pos: self.pos
size: self.size
GridLayout:
cols : 1
Button:
text: "Mahjong"
background_normal:'Mahjong.JPG'
Button:
text: "Let's Play"
on_release: app.root.current = "Contents"
<Player_Screen>:
f_username: Name
BoxLayout:
orientation: 'vertical'
id:f_username
TextInput:
id: Name
hint_text:'Enter Player1'
on_text: root.manager.shared_data = self.text #root.player_name()
TextInput:
id: Name
hint_text:'Enter Player2'
on_text: root.manager.shared_data = self.text
TextInput:
id: Name
hint_text:'Enter Player3'
on_text: root.manager.shared_data = self.text
TextInput:
id: Name
hint_text:'Enter Player4'
on_text: root.manager.shared_data = self.text
Button:
text: 'Back'
on_press: self.background_color = 0,0,0,1
on_release: root.manager.current = 'Contents'
Button:
text: 'Next'
#on_press: root.callback(self.text)
on_press: root.save_data()
on_release: root.manager.current = 'Score'
First of all kindly note that your last edited code is not a reproducible one. It's sometimes quite essential to be such one.
However if I got you right, you want to share or use the entered data in the input fields somewhere in your code as long as the application is running. If that's the requirement, you can do it in multiple ways. Here's a such one:
To store all data centrally in shared_data make it a DictProperty.
class ScreenManagement(ScreenManager):
shared_data = DictProperty(rebind = True)
# rebind is necessary for nested access in kvlang.
Feed the data in the input fields using different keys as follows,
<Player_Screen>:
f_username: Name
BoxLayout:
orientation: 'vertical'
id:f_username
TextInput:
id: Name
hint_text:'Enter Player1'
on_text: root.manager.shared_data["player1"] = self.text
TextInput:
id: Name
hint_text:'Enter Player2'
on_text: root.manager.shared_data["player2"] = self.text
...
Once they are stored, you can access them anywhere from the ScreenManagement instance of your GUI (which also happens to be the root). I've added another screen to demonstrate that.
class PlayerNameScreen(Screen):
pass
Now in kvlang for that screen,
...
ScreenManagement:
#title: title # so I can use it in the .py file
#score:score # same here
transition: FadeTransition()
MainScreen:
id: title
Player_Screen:
id: Players
name: 'Players'
PlayerNameScreen:
name: "player_names"
<PlayerNameScreen>:
Label:
text: "\\n".join(f"{player} : {name}" for player, name in root.manager.shared_data.items())
Button:
size_hint: None, None
size: "100dp", "50dp"
pos_hint: {"center_x" : 0.5, "center_y" : 0.2}
text: "Back"
on_release: root.manager.current = "Players"
I'm trying to build an interface in python with Kivy. To do this, i want to follow this structure:
[ScreenManager#1] -> [Login Screen]
[ScreenManager#2] -> [All others screens]
My ScreenManager#1 is declared in my App function in python file. My ScreenManager#2 is initialised in a kv file. To simply the help that you can bring to me, i've joined the two in the code below.
my AllScreensScreenManager#2, need to have a template shared with all the other screens. I've gave an id to it and call this one with my button in my LoginScreen, like this was suggested here. But the switch is not working. What i'm doing wrong with it.
import kivy
kivy.require('2.1.0') # replace with your current kivy version !
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager,Screen
Builder.load_string('''
<AllScreens>:
id: second_manager
manager: scr_manager
orientation: "vertical"
BoxLayout:
ScreenManager:
id: scr_manager
transition: "left"
Welcome:
GenerateScreen:
ModifyScreen:
ConsultScreen:
LogoutScreen:
BoxLayout:
size_hint: 1, None
height: "40dp"
pos_hint: {"bottom": 0}
spacing: "5dp"
Button:
id: consult
text: "Consult"
on_press: root.manager.current("consult")
Button:
id: modify
text: "Modify"
on_press: root.manager.current("modify")
Button:
id: generate
text: "Generate"
on_press: root.manager.current("generate")
Button:
id: logout
text: "Logout"
on_press: root.manager.current("logout")
<LoginScreen>:
name: "login"
BoxLayout:
Label:
text: "Login"
Button:
text: "Connect"
on_release: second_manager.current("welcome")
<WelcomeScreen>:
name: "welcome"
BoxLayout:
Label:
text: "welcome"
<ConsultScreen>:
name: "consult"
BoxLayout:
Label:
text: "consult"
<GenerateScreen>:
name: "generate"
BoxLayout:
Label:
text: "password"
''')
class ScreenManagement(ScreenManager):
pass
class LoginScreen(Screen):
print("login screen")
pass
class WelcomeScreen(Screen):
pass
class AllScreens(ScreenManager):
pass
class ConsultScreen(Screen):
pass
class GenerateScreen(Screen):
pass
class MyApp(App):
def build(self):
sm = ScreenManagement()
sm.add_widget(LoginScreen(name="login"))
return sm
if __name__ == '__main__':
MyApp().run()
I'm trying to make an app in where the top_part of the app there are a Spinner,Label and Button and these 3 are inside a GridLayout with 3 columns. When I click in the spinner. It's displays some choices such as: "Catalogue", "Buy", "Payment Methods". I want that every time I press the Spinner and select a choice, change the screen that is below of this GridLayout. In these case would be 3 screens because they are 3 choices ("Catalogue", "Buy", "Payment Methods"). and these should be below of BoxLayout in MyLayout(U can see it in the kv.code)
The screens are not working, and I got an error when run the app(invalid class). Just work the part of MyLayout, but from ScreenManager and Screens are not working, I don't know how to fix it.
Py.file
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.spelling import Spelling
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_file('test2.kv')
class MyLayout(Widget):
def spinner_clicked(self,value):
self.ids.Label1.text= f'You selected: {value}'
#Definine our different screens
class FirstWindow(Screen):
pass
class SecondWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
class AwesomeApp(App):
def build(self):
return MyLayout()
if __name__ == '__main__':
AwesomeApp().run()
This is the kv.file
<MyLayout>
BoxLayout:
orientation:"vertical"
size: root.width, root.height
GridLayout:
cols:3
Spinner:
id: spinner_id
text: "Menu"
values: ["Catalogue","Buy","Payment Methods", "Contact"]
on_text: root.spinner_clicked(spinner_id.text)
Label:
id: Label1
text: "My Panel"
Button:
text:"Buy"
Label:
id: Label2
text: "My Panel"
WindowManager:
FirstWindow:
SecondWindow:
<FirstWindow>:
name: "first"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "First Screen"
font_size: 32
Button:
text: "Next Screen"
font_size: 32
on_release:
app.root.current= "second"
root.manager.transition.direction= "left" #up
<SecondWindow>:
name: "second"
BoxLayout:
orientation: "vertical"
size: root.width, root.height
Label:
text: "Second Screen"
font_size: 32
Button:
text: "Go Back"
font_size: 32
on_release:
app.root.current= "first"
root.manager.transition.direction= "right"
As #ApuCoder suggested, you can change MyLayout to extend BoxLayout, and add the WindowManager to that. Just change the definition of MyLayout:
class MyLayout(BoxLayout):
def spinner_clicked(self,value):
self.ids.Label1.text= f'You selected: {value}'
And modify your kv to use the modified MyLayout:
<MyLayout>
orientation:"vertical"
GridLayout:
cols:3
Spinner:
id: spinner_id
text: "Menu"
values: ["Catalogue","Buy","Payment Methods", "Contact"]
on_text: root.spinner_clicked(spinner_id.text)
Label:
id: Label1
text: "My Panel"
Button:
text:"Buy"
# add ScreenManager and Screens
WindowManager:
FirstWindow:
SecondWindow:
<FirstWindow>:
name: "first"
BoxLayout:
orientation: "vertical"
Label:
text: "First Screen"
font_size: 32
Button:
text: "Next Screen"
font_size: 32
on_release:
root.manager.current= "second" # use root.manager instead of app.root
root.manager.transition.direction= "left" #up
<SecondWindow>:
name: "second"
BoxLayout:
orientation: "vertical"
Label:
text: "Second Screen"
font_size: 32
Button:
text: "Go Back"
font_size: 32
on_release:
root.manager.current= "first" # use root.manager instead of app.root
root.manager.transition.direction= "right"
I have a problem with accessing popups content.
Here is popup class in .py:
class LicencePopup(Popup):
def dismiss_popup(self, *args):
self.dismiss()
def submit_licence(self):
# print self.content.ids.licence_field.text
# print self.ids.licence_field.text
# print self.content.ids[licence_field].text
Popup in .kv look like this:
<LicencePopup>:
size_hint: .3, .35
title: 'Enter license key'
separator_height: 0
title_size: 35
BoxLayout:
orientation: "vertical"
TextInput:
id: license_field
multiline: False
text: "59353-58506-87377-00410"
font_size: 24
Label:
id: license_label
BoxLayout:
Button:
text: "Cancel"
on_release: root.dismiss_popup()
Button:
text: "Submit"
on_release: root.submit_licence()
My problem is how to get text from TextInput(id: licence_field).
I have tried a few things(commented prints) but I can't get this working.
Can you please help with this?
I am trying to display the value of a slider on a different screen. i have tried this(below code) but for some reason, the value doesn't seem to show up. the code runs fine but no value is returned. Thanks for your help :) Cheers.
temperature screen
here is a snippet of the python code:
class Thermostat(Screen):
label = StringProperty()
def display(self):
tempVal = self.label
return str(tempVal)
and the kv files:
<Thermostat>:
name: "thermostat"
BoxLayout:
orientation: 'horizontal'
cols: 2
Label:
id: label
font_size: "11sp"
text: "INSIDE: " + root.display()
Label:
text: "More Info"
font_size: "11sp"
kv file 2:This screen holds the true value from the slider, i am trying to pass that value to the Thermostat screen.
<Temperature>:
BoxLayout:
size_hint_y: None
height: '48dp'
cols: 3
Label:
text: 'THERMOSTAT'
Slider:
id: temp
min: 40
max: 100
value: 1
step: 1
on_value: app.root.get_screen('thermostat').label = str('{}'.format(temp.value))
Label:
id: slide_val
text: '{}'.format(temp.value)
root.display is only called once, at the beginning of the program. For it to work well, every time you change the value of the slider root.display should be called.
However, it is very simple to do this using propertis in kv languaje:
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang.builder import Builder
Builder.load_string('''
<Manager>:
id: manager
Thermostat:
id: thermostat
name: 'thermostat'
manager: 'screen_manager'
temp: temperature.temp #<<<<<<<<<<<<
Temperature:
id: temperature
name: 'temperature'
manager: 'screen_manager'
<Thermostat>:
temp: 0 #<<<<<<<<<<<<
BoxLayout:
orientation: 'horizontal'
cols: 3
Label:
id: label
font_size: "11sp"
text: "INSIDE: {}".format(root.temp) #<<<<<<<<<<<<
Label:
text: "More Info"
font_size: "11sp"
Button:
text: ">"
on_release: app.root.current= "temperature"
size_hint_x: None
width: 30
<Temperature>:
temp: temp_slider.value #<<<<<<<<<<<<
BoxLayout:
cols: 4
Button:
text: "<"
on_press: app.root.current = "thermostat"
size_hint_x: None
width: 30
Label:
text: 'THERMOSTAT'
Slider:
id: temp_slider
min: 40
max: 100
value: 40
step: 1
Label:
id: slide_val
text: str(root.temp)
''')
class Thermostat(Screen):
pass
class Temperature(Screen):
pass
class Manager(ScreenManager):
pass
class ExampleApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
ExampleApp().run()
If you want to use the slider's value in your class Temperature, simply declare the property in the class:
from kivy.properties import NumericProperty
class Temperature(Screen):
temp = NumericProperty()
def __init__(self, **kwargs):
super(Temperature, self).__init__(**kwargs)