Short version:
How can I alter button text displayed onscreen from the main.py file of a Kivy application?
Longer version:
I'm putting togther a multiple-choice game using Kivy.
I have the gameplay working as I want it to at the python level: when the user clicks on the correct answer button they are awarded points and the answers attached to the buttons are changed. Under the bonnet everything seems to be fine.
My problem is that I can't work out how to update the text on the buttons displayed onscreen from the main.py file. The game plays fine, but the text displayed on the buttons never alters. How do I fix my code to do this?
Below is a simplified version of what I'm tring to do:
My main.py file:
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import ObjectProperty
from functools import partial
import random
vocabDict = {'latte': 'milk', 'mela': 'apple', 'melograno': 'pomegranate', 'fragola': 'strawberry', 'acqua': 'water'}
vocabList = ['acqua', 'latte', 'mela', 'melograno', 'fragola']
currentWord = 'acqua'
score = 0
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super(ScreenTwo, self).__init__(**kwargs)
Screen.currentWord = self.getCurrentWord()
Screen.questionText = Screen.currentWord
Screen.vocabDict = self.getVocabDict()
Screen.currentScore = self.getCurrentScore()
Screen.possibleAnswerList = [Screen.currentWord]
self.playHand(Screen.currentWord,Screen.vocabDict,Screen.currentScore)
def getCurrentWord(self):
return currentWord
def getVocabDict(self):
return vocabDict
def getCurrentScore(self):
return score
def playHand(self,currentWord,vocabDict,score):
possibleAnswerList = [currentWord]
currentWord = currentWord
vocabDict = vocabDict
currentScore = score
while len(possibleAnswerList) < 3:
potentialChoice = random.choice(vocabDict.keys())
if potentialChoice not in possibleAnswerList:
possibleAnswerList.append(potentialChoice)
random.shuffle(possibleAnswerList)
# How do I visualize these changes on screen?
Screen.button1Text = vocabDict[possibleAnswerList[0]]
Screen.button2Text = vocabDict[possibleAnswerList[1]]
Screen.button3Text = vocabDict[possibleAnswerList[2]]
Screen.possibleAnswerList = possibleAnswerList
print "Screen.button1Text = " + Screen.button1Text
print "Screen.button2Text = " + Screen.button2Text
print "Screen.button3Text = " + Screen.button3Text
def button1Pressed(instance):
print "Screen.possibleAnswerList[0] = " + Screen.possibleAnswerList[0]
print "Screen.currentWord = " + Screen.currentWord
if Screen.possibleAnswerList[0] == Screen.currentWord:
print "Correct!"
Screen.currentScore += 1
print Screen.currentScore
instance.playHand(Screen.currentWord,Screen.vocabDict,Screen.currentScore)
else:
print "Incorrect!"
def button2Pressed(instance):
if Screen.possibleAnswerList[1] == Screen.currentWord:
print "Correct!"
Screen.currentScore += 1
print instance.currentScore
instance.playHand(Screen.currentWord,Screen.vocabDict,Screen.currentScore)
else:
print "Incorrect!"
def button3Pressed(instance):
if instance.possibleAnswerList[2] == currentWord:
print "Correct!"
instance.currentScore += 1
print instance.currentScore
instance.playHand(Screen.currentWord,Screen.vocabDict,Screen.currentScore)
else:
print "Incorrect!"
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreensApp(App):
def build(self):
m = Manager(transition=NoTransition())
return m
if __name__ == "__main__":
ScreensApp().run()
My screens.kv file:
#:kivy 1.8.0
<ScreenOne>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
text: "Main Menu"
Button:
text: "Button 1"
on_release: root.manager.current = "screen2"
<ScreenTwo>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
id: label
text: root.questionText
Button:
id: button1
text: root.button1Text
on_release: root.button1Pressed()
Button:
id: button2
text: root.button2Text
on_release: root.button2Pressed()
Button:
id: button3
text: root.button3Text
on_release: root.button3Pressed()
<Manager>:
id: screen_manager
screen_one: screen_one
screen_two: screen_two
ScreenOne:
id: screen_one
name: "screen1"
manager: screen_manager
ScreenTwo:
id: screen_two
name: "screen2"
manager: screen_manager
As should be pretty evident, I'm a total beginner at Kivy, so I'd really appreciate it if you could show me exactly what I need to change, including the specific syntax that should be used.
Thanks in advance for your time and wisdom.
You need to make button1/2/3Text a StringProperty such as
from kivy.properties import StringProperty # <---- (:
class ScreenTwo(Screen):
button1Text = StringProperty ('some text')
button2Text = StringProperty ('some text2')
button3Text = StringProperty ('some text3')
def __init__(...):
...
#notice that Screen was replaced by "self"
# How do I visualize these changes on screen?
self.button1Text = vocabDict[possibleAnswerList[0]]
self.button2Text = vocabDict[possibleAnswerList[1]]
self.button3Text = vocabDict[possibleAnswerList[2]]
Please also replace Screen with self in all other places as well since you are putting variables on a kivy class and its weird ;)
I hope this will help you
Related
I know this is a duplicate, but no answers for this have worked for me.
I am trying to add a widget which serves as an input line with a prompt.
I am getting a name error stating that 'prompt_text' in line 2 of client.kv is not defined.
I believe this is an issue to do with the StringProperty() not being run.
Here is my python file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.widget import Widget
import kivy.properties as kyProps
import ChatClient
CLIENT = None
VERSION = open("Version.txt").read()
class LoginWindow(Screen):
namee = kyProps.ObjectProperty(None)
password = kyProps.ObjectProperty(None)
def loginBtn(self):
global CLIENT
CLIENT = ChatClient(self.namee.ti.text, self.password.ti.text, VERSION)
if CLIENT.didConFail:
errorMsg = f"Failed to connect. Reason: {CLIENT.connectFailedReason}"
pop = Popup(title='Login Error', content=Label(text=errorMsg))
pop.open()
elif "REFUSED" in CLIENT.finalSetUpMsg:
pop = Popup(title='Login Error', content=Label(text=CLIENT.finalSetUpMsg))
pop.open()
else:
sm.current = "main"
class MainWindow(Screen):
pass
class StyleEditorWindow(Screen):
pass
class PMWindow(Screen):
pass
class ChatWidget(Widget):
pass
class InputLineWidget(Widget):
prompt_text = kyProps.StringProperty("")
ti = kyProps.ObjectProperty(None)
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("client.kv")
sm = WindowManager()
screens = [LoginWindow(name="login"), MainWindow(name="main"), StyleEditorWindow(name="styleEditor"), PMWindow(name="pm")]
for screen in screens:
sm.add_widget(screen)
sm.current = "login"
class CpClientApp(App):
def build(self):
return sm
if __name__ == "__main__":
CpClientApp().run()
And here is my client.kv file:
<InputLineWidget>:
prompt_text: prompt_text
ti: ti
GridLayout:
cols: 2
size: root.width, root.height
Label:
text: prompt_text
TextInput:
id: ti
multiline: false
<ChatWidget>:
<LoginWindow>:
namee: namee
password: password
size: root.width, root.height
GridLayout:
cols: 1
InputLineWidget:
id: namee
prompt_text: "Name: "
InputLineWidget:
id: password
prompt_text: "Password: "
Button:
text: "Log In"
on_release:
root.manager.transition.direction = "up"
root.loginBtn()
<MainWindow>:
<StyleEditorWindow>:
<PMWindow>:
I've tried many solutions based on others encountering similar issues but none have worked.
Im running python 3.7 on Windows 10 using Kivy 2.0.0
Any help would be greatly appreciated. Thank you.
The line:
prompt_text: prompt_text
creates an ObjectProperty named prompt_text that contains a reference to an object that has an id of prompt_text. But since that id is not defined in the <InputLineWidget> rule of your kv file, you get the noted error.
If you want the prompt_text to be used as the text for the label, change the kv to:
<InputLineWidget>:
ti: ti
GridLayout:
cols: 2
size: root.width, root.height
Label:
text: root.prompt_text
TextInput:
id: ti
multiline: false
I have a question about kivy and it runs weird on anaconda
First weird thing: I have to add into the kivy file then run it and delete the <> from the MenuScreen in the kivy code and run the file again in order for my code to runs like I want. if I don't do this the code will give FactoryException: Unknown class <MenuScreen>
Second weird thing is that after I end kivy and run it again it doesn't let me change screen on the app, I have to restart the shell and do everything like the (first weird thing above) for it to switch screen and run properly.
Here is the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
kv = Builder.load_file('mymain.kv')
class MainWindow(Screen):
pass
class SecondWindow(Screen):
point = ObjectProperty(None)
note = ObjectProperty(None)
def pressed(self):
''' this fuction is the function of the good action button'''
point1 = self.point.text
Note1 = self.note.text
try:
point1 = int(point1)
print("You just got: ",point1)
print('Note of this action: ')
print(Note1)
except:
if point1 == '':
print('please enter something')
else:
print("point should be a number")
#print(point1, Note1)
self.point.text = ""
self.note.text = ''
#return grid
def increased(self):
''' this function is to increase the mark'''
point2 = self.point.text
try:
point2 = int(float(point2))+1
print('increase point is', point2)
#self.point.text = str(point2)
except:
point2 = 1
print('your point is: ','1')
self.point.text = str(point2)
def decreased(self):
''' this function decrease the point'''
point3 = self.point.text
try:
point3 = int(float(point3))-1
print('decrease point is', point3)
#self.point.text = str(point3)
except:
point3 = -1
print('your point is: ', '-1')
self.point.text = str(point3)
class MenuScreen(ScreenManager):
pass
class MyMainApp(App):
def build(self):
return kv
if __name__ == '__main__':
MyMainApp().run()
Here is the mymain.kv
MenuScreen:
MainWindow:
SecondWindow:
<MainWindow>:
name: "main"
GridLayout:
cols: 1
Button:
text: 'Good'
on_release: app.root.current = 'second'
Button:
text: 'Bad' #not doing anything yet
<SecondWindow>:
name: 'second'
point: point
note: note
GridLayout:
cols: 1
size: root.width, root.height
GridLayout:
cols: 3
Label:
text: 'Good Point: '
TextInput:
id: point
multiline: False
GridLayout:
cols: 1
Button:
text: 'up'
on_press: root.increased()
Button:
text: 'down'
on_press: root.decreased()
Label:
text: 'Note: '
TextInput:
id: note
multiline: True
Button:
text: ' submit'
on_press: root.pressed()
please help me. Idk why kivy runs like this. Thank you
The problem is that your mymain.kv is getting loaded twice. Kivy will automatically load a kv file if it is named correctly (as your is - See the documentation), and you are also loading it via a Builder.load_string() call. The fix is to remove the line:
kv = Builder.load_file('mymain.kv')
and simplify your App class to:
class MyMainApp(App):
pass
# def build(self):
# return kv
in mymain.kv change:
MenuScreen:
MainWindow:
SecondWindow:
to
<MenuScreen>:
MainWindow:
SecondWindow:
My App has three screens, on running the App the first screen is always the same screen, on running the app, I need it to display different screens based on the return values of a specific function.
I have the UI to change screens and a function that returns a numerical value, based on this logic the screens should change.
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.core.window import Window
class ScreenMain(Screen):
pass
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
pass
class Manager(ScreenManager):
screen_main_id = ObjectProperty()
screen_one_id = ObjectProperty()
screen_two_id = ObjectProperty()
class ScreenmainApp(App):
def build(self):
'''This method returns the Manager class'''
self.auth()
return Manager()
def auth(self):
'''This function is called by build(), return
value should determine which screen is displayed on running the App,
by default the MAIN SCREEN IS FIRST SHOWN'''
a = 3
b = 5
value = a + b
if value >0 <= 5:
print('Show screen 1')
elif value >5<=10:
print('Show screen 2')
else:
print('Show main screen')
print('This is the return value: ',value)
if __name__ =="__main__":
ScreenmainApp().run()
screenmain.kv
#:kivy 1.10.0
#:include screenone.kv
#:include screentwo.kv
<Manager>:
screen_main: screen_main_id
screen_one: screen_one_id
screen_two: screen_two_id
# The order below determines which screen is displayed after app loads
ScreenMain:
id: screen_main_id
name: 'ScreenMain'
ScreenOne:
id: screen_one_id
name: 'Screen1'
ScreenTwo:
id: screen_two_id
name: 'Screen2'
<ScreenMain>:
BoxLayout:
orientation: 'vertical'
Label:
text:"WELCOME TO THE MAIN SCREEN"
Button:
text:'Go to Screen 1'
on_press: root.manager.current = 'Screen1'
Button:
text:'Go to Screen 2'
on_press: root.manager.current = 'Screen2'
Label:
screenone.kv
#:kivy 1.10.0
<ScreenOne>:
BoxLayout:
orientation: 'vertical'
Label:
text: 'This is SCREEN ONE'
BoxLayout:
orientation: 'horizontal'
Button:
text: "Go to Screen 2"
on_press: root.manager.current = 'Screen2'
Button:
text: "Back to Home"
on_press: root.manager.current = 'ScreenMain'
screentwo.kv
#:kivy 1.10.0
<ScreenTwo>:
BoxLayout:
orientation: 'vertical'
Label:
text: 'This is SCREEN TWO'
BoxLayout:
orientation: 'horizontal'
Button:
text: "Go to Screen 1"
on_press: root.manager.current = 'Screen1'
Button:
text: "Go to Home"
on_press: root.manager.current = 'ScreenMain'
Actual results: On App load up, the main screen always shows up first.
Expected results: Based on the value returned in auth() the screens are supposed to change each time on running the app.
The solution is instantiate the ScreenManager before calling self.auth() method.
Snippets
def build(self):
'''This method returns the Manager class'''
self.root = Manager()
self.auth()
return self.root
def auth(self):
'''This function is called by build(), return
value should determine which screen is displayed on running the App,
by default the MAIN SCREEN IS FIRST SHOWN'''
a = 3
b = 5
value = a + b
if value > 0 <= 5:
print('Show screen 1')
self.root.current = 'Screen1'
elif value > 5 <= 10:
print('Show screen 2')
self.root.current = 'Screen2'
else:
print('Show main screen')
self.root.current = 'ScreenMain'
print('This is the return value: ', value)
I'm putting together a Kivy app and am having some problems working out how to change screens at an arbitrarily chosen point within the python code.
In the following example, I would like to know how to switch from Screen2 back to Screen1 by executing a function my main.py file.
Here's my main.py:
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import ObjectProperty
from functools import partial
import random
import time
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super(ScreenTwo, self).__init__(**kwargs)
self.displayScreenThenLeave()
def displayScreenThenLeave(self):
# 'time.sleep(3)' is a stand-in for "execute some code here"
time.sleep(3)
self.changeScreen()
# I want this function to send the user back to ScreenOne.
def changeScreen(self):
pass
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreensApp(App):
def build(self):
m = Manager(transition=NoTransition())
return m
if __name__ == "__main__":
ScreensApp().run()
And here's my screens.kv:
#:kivy 1.8.0
<ScreenOne>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
text: "Main Menu"
Button:
text: "Button 1"
on_release: root.manager.current = "screen2"
<ScreenTwo>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
id: label
text: "Welcome to Screen 2, please wait three seconds"
<Manager>:
id: screen_manager
screen_one: screen_one
screen_two: screen_two
ScreenOne:
id: screen_one
name: "screen1"
manager: screen_manager
ScreenTwo:
id: screen_two
name: "screen2"
manager: screen_manager
As should be pretty evident, I'm a total beginner at Kivy, so I'd really appreciate it if you could show me exactly what I need to change, including the specific syntax that should be used.
Thanks in advance for your time and wisdom.
Here is a simple 'game' example based on your additional info. When a player enters the game screen, their health points are bleeding. When the pool reaches zero, they are sent back to menu screen.
main.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import ObjectProperty, NumericProperty
import time
import threading
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
health_points = NumericProperty(100)
def __init__(self, **kwargs):
super(ScreenTwo, self).__init__(**kwargs)
def on_health_points(self, instance, value):
if value < 1:
self.changeScreen()
def on_enter(self, *args):
thread = threading.Thread(target=self.bleed)
thread.daemon = True
thread.start()
def bleed(self, *args):
while self.health_points > 0:
self.health_points -= 5
time.sleep(0.1)
def displayScreenThenLeave(self):
self.changeScreen()
def changeScreen(self):
if self.manager.current == 'screen1':
self.manager.current = 'screen2'
else:
self.manager.current = 'screen1'
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreensApp(App):
def build(self):
m = Manager(transition=NoTransition())
return m
if __name__ == "__main__":
ScreensApp().run()
screens.kv:
#:kivy 1.8.0
<ScreenOne>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
text: "Main Menu"
Button:
text: "Button 1"
on_release:
root.manager.current = "screen2"
# reset health_points
root.manager.ids.screen_two.health_points = 100
<ScreenTwo>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
Label:
id: label
text: "health points: " + str(root.health_points)
<Manager>:
id: screen_manager
screen_one: screen_one
screen_two: screen_two
ScreenOne:
id: screen_one
name: "screen1"
manager: screen_manager
ScreenTwo:
id: screen_two
name: "screen2"
manager: screen_manager
Make these little changes
#import Clock to create a schedule
from kivy.clock import Clock
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super(ScreenTwo, self).__init__(**kwargs)
#this is event that is fired when the screen is displayed.
def on_enter(self, *args):
self.displayScreenThenLeave()
def displayScreenThenLeave(self):
#schedued after 3 seconds
Clock.schedule_once(self.changeScreen, 3)
def changeScreen(self, *args):
#now switch to the screen 1
self.parent.current = "screen1"
for more imformation go to these links Screen Manager, Clock
im currently looking into kivy to start with crossplatform development. i have a bit of python experience (but basic) and now wanted to code a little game in kivy to get into. i probably wont finish this but i like learning stuff while doing it with something im intrested in.
Anyway my "App" is supposed to be seperated in two seperate "screens" the top one is only used for displaying stuff and the all interactive stuff is controlled from the bottom "screen".
Now i want to display some text in old school way by getting it written letter by letter to the screen.
This is working fine but for some reason the Label widget is only updated on screen if i call the "print_something" function from the top screen, if i call it from the bottom screen the function is indeed called but the Label widget wont change on screen.
Am i doing something wrong?
Here is a stripped version of the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock
Builder.load_string('''
<MainUI>:
orientation: 'vertical'
# both these variables can be the same name and this doesn't lead to
# an issue with uniqueness as the id is only accessible in kv.
<Screen1>:
print_txt: print_txt
layout: layout
RelativeLayout:
id: layout
pos: 0, 400
size: 480, 400
Button:
pos: 0, 200
size_hint: (1, 0.2)
text: "Test Print"
on_press: root.print_something('TEST PRINT FROM SCREEN1')
AnchorLayout:
anchor_x: 'center'
anchor_y: 'bottom'
Label:
id: print_txt
padding_x: 10
markup: True
text_size: self.size
halign: 'left'
valign: 'top'
size_hint: (1, 0.2)
text: ""
<Screen2>:
btn1: btn1
RelativeLayout:
pos: 0, 0
size: 480, 400
Button:
id: btn1
pos_hint: {'x': .15, 'center_y': .5}
size_hint: (0.7, 0.5)
text: "Test Print"
on_press: root.print_text()
''')
class Screen1(Widget):
print_txt = ObjectProperty(None)
layout = ObjectProperty(None)
def print_something(self, string):
print 'Function called...'
self.print_txt.text = ''
counter = [0]
string_len = len(string)
def print_step(dt):
if counter[0] == string_len:
return False
else:
self.print_txt.text += string[counter[0]]
counter[0] = counter[0] + 1
Clock.schedule_interval(print_step, 2.0/60.0)
print 'Function End..'
class Screen2(Widget):
btn1 = ObjectProperty(None)
def __init__(self):
super(Screen2, self).__init__()
def print_text(self):
print 'Trying to print Text from Screen2 to Screen1'
target = Screen1()
target.print_something('TEST PRINT FROM SCREEN2')
class MainUI(Widget):
def __init__(self):
super(MainUI, self).__init__()
self.screen1 = Screen1()
self.add_widget(self.screen1)
self.add_widget(Screen2())
class MainApp(App):
def build(self):
Window.size = (480, 800)
return MainUI()
if __name__ == '__main__':
MainApp().run()
Your Screen2 print_text method creates a new Screen1 instance, which is modified but not displayed anywhere so you don't see anything change.
You could change the call to instead something like
on_press: root.parent.screen1.print_text()
...to access the print_text function of the Screen1 instance that you actually want to update.