I am new to Kivy so this may be a trivial question. I am working on a project that has two screens, each of which contains a button that generates a popup. I would like for the popup to display a statement containing the name of the current screen. My problem is despite having a method to change the popup text, the placeholder text is always being displayed. Why doesn't the changeText method change the text of the popup?
My problem seems similar to the one shown:
Kivy Label.text Property doesn't update on the UI
However I am having some trouble understanding how to apply it to my specific situation.
Python Code:
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class MyManager(ScreenManager):
pass
class PopUp(Popup):
def changeText(self,nameStr):
self.ids.label.text = "You are on Screen %s!" %nameStr #this is text that I want to display
class PrimaryApp(App):
def build(self):
return MyManager()
PrimaryApp().run()
Kv Code:
#:import Factory kivy.factory.Factory
<MyManager>:
Screen1:
id: screen1
Screen2:
id: screen2
<Screen1>:
name: "one"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen Two"
on_release: root.manager.current = "two"
Button:
id: button2
text: "Display Popup"
on_release:
Factory.PopUp().changeText(root.name)
Factory.PopUp().open()
<Screen2>:
name: "two"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen One"
on_release: root.manager.current = "one"
Button:
id: button2
text: "Display Popup"
on_release:
Factory.PopUp().changeText(root.name)
Factory.PopUp().open()
<PopUp>:
id:pop
size_hint: (.5,.5)
title: "Notice!"
Label:
id: label
text: "PLACEHOLDER TEXT" #this is not the code I want displayed
[1]:
Use Popup event, on_open to change the Popup content, Label widget's text.
Popup » API
Events: on_open:
Fired
when the Popup is opened.
Snippets
<PopUp>:
on_open:
label.text = "You are on Screen %s!" % app.root.current
id:pop
...
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.popup import Popup
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class MyManager(ScreenManager):
pass
class PopUp(Popup):
pass
class PrimaryApp(App):
def build(self):
return MyManager()
PrimaryApp().run()
primary.kv
#:kivy 1.10.0
#:import Factory kivy.factory.Factory
<MyManager>:
Screen1:
id: screen1
Screen2:
id: screen2
<Screen1>:
name: "one"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen Two"
on_release: root.manager.current = "two"
Button:
id: button2
text: "Display Popup"
on_release:
Factory.PopUp().open()
<Screen2>:
name: "two"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen One"
on_release: root.manager.current = "one"
Button:
id: button2
text: "Display Popup"
on_release:
Factory.PopUp().open()
<PopUp>:
on_open:
label.text = "You are on Screen %s!" % app.root.current
id:pop
size_hint: (.5,.5)
title: "Notice!"
Label:
id: label
text: "PLACEHOLDER TEXT" #this is not the code I want displayed
Output
everytime you call Factory().Popup() it creates a brand new Popup which has nothing to do with the previous. What you can do it's:
in the kv:
...
<Screen1>:
name: "one"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen Two"
on_release: root.manager.current = "two"
Button:
id: button2
text: "Display Popup"
on_release:
p = Factory.PopUp()
p.changeText(root.name)
p.open()
And the same thing for the second screen. But everytime you release these buttons it creates a new created popup too much memory wasted. The best thing you can do it is initialize your screen manager with a popup and then only change the text of this popup:
Python:
...
from kivy.properties import ObjectProperty
...
class PopUp(Popup):
def changeText(self,*args):
self.ids.label.text = "You are on Screen %s!" % args[0].current
class MyManager(ScreenManager):
popup = ObjectProperty()
def __init__(self, **kwargs):
super(MyManager, self).__init__(**kwargs)
self.popup = PopUp()
self.bind(current=self.popup.changeText)
and the kv:
...
<PopUp>:
id:pop
size_hint: (.5,.5)
title: "Notice!"
Label:
id: label
text: "You are on Screen one!"
<Screen1>:
name: "one"
GridLayout:
id: grid
rows: 2
Button:
id: button1
text: "Go to Screen Two"
on_release: root.manager.current = "two"
Button:
id: button2
text: "Display Popup"
on_release:
root.manager.popup.open() #Same thing for the second screen
Related
I am trying to change the label of the button "Choose Activity" to "Schlafen" (in the Screen "DayWindow"), if the user clicks on the button "Schlafen" in the popup window.
My problem is that I dont know how to access the Screen "DayWindow" from the Popup-Class.
As you can see in the code, I tried to access it through the ScreenManager but than I get the Error: 'super' object has no attribute '__getattr__'
In another try I got the Error: Screen "day" does not exist
I would be very thankful for any help and tips.
My Pyhton File:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import runTouchApp
from kivy.properties import ObjectProperty
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.popup import Popup
from kivy.properties import ObjectProperty
from datetime import date, timedelta
import json
class MenuWindow(Screen):
pass
class DayWindow(Screen):
null = ObjectProperty("Null")
def btn(self,index):
show_ActivityPopup()
def change_button():
scn = ScreenManager()
scn.ids.null.text = "Geschafft"
class WeekWindow(Screen):
pass
class MonthWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
class ActivityPopup(FloatLayout):
def change_text(self):
DayWindow.change_button()
def show_ActivityPopup():
show = ActivityPopup()
ActivityPopupWindow = Popup(title="Activities", content=show, auto_dismiss=False)
ActivityPopupWindow.open()
kv = Builder.load_file("my.kv")
class MyMainApp(App):
def build(self):
return kv
if __name__ == "__main__":
MyMainApp().run()enter code here
My .kv - File
WindowManager:
MenuWindow:
DayWindow:
WeekWindow:
MonthWindow:
<MenuWindow>:
name: "menu"
GridLayout:
cols:1
Label:
text: "Time/MoodTracker"
Button:
text: "Day"
on_release:
app.root.current = "day"
root.manager.transition.direction ="left"
GridLayout:
cols: 2
Button:
text: "Weelky Overview"
on_release:
app.root.current = "week"
root.manager.transition.direction ="left"
Button:
text: "Monthly Overview"
on_release:
app.root.current = "month"
root.manager.transition.direction ="left"
<DayWindow>:
name: "day"
ScrollView:
GridLayout:
cols: 2
Label:
text: "0:00 - 0:30"
Button:
id: null
text: root.null
on_release:
root.btn(0)
Label:
text: "0:30 - 1:00"
id: Eins
Button:
text: "Choose Aviticity"
on_release: root.btn(1)
Label:
text: "1:00 - 1:30"
Button:
text: "Choose Aviticity"
on_release: root.btn(2)
Label:
text: "1:30 - 2:00"
Button:
text: "Choose Aviticity"
on_release: root.btn(3)
Label:
text: "2:00 - 2:30"
Button:
text: "Choose Aviticity"
on_release: root.btn(4)
<ActivityPopup>:
auto_dismiss: False
ScrollView:
GridLayout:
size_hint_y: None
cols:1
height: dp(600)
spacing: "10dp"
Button:
text: "Schlafen"
on_release:
root.change_text()
root.parent.parent.parent.dismiss()
Button:
text: "Frühstücken"
on_release:
root.parent.parent.parent.dismiss()
Button:
text: "Kochen"
Button:
text: "Lernen"
Button:
text: "Joggen"
<WeekWindow>:
name: "week"
Button:
text: "Back"
on_release:
app.root.current = "menu"
root.manager.transition.direction ="right"
<MonthWindow>:
name: "month"
Button:
text: "Back"
on_release:
app.root.current = "menu"
root.manager.transition.direction ="right"
A couple problems:
The code:
def change_button():
scn = ScreenManager()
scn.ids.null.text = "Geschafft"
is creating a new instance of ScreenManager, which has nothing to do with your App. So nothing that you do with scn will have any effect on your App.
And, in that same method, you are trying to access ids of the ScreenManager. Even if you were actually dealing with the WindowManager in your App, this would not work, because the WindowsManager has no defined ids.
The ids defined in your kv file are defined in the object that is the root of the rule where that id is defined. So, for example, the null id is defined in the ids of the DayWindow. The kivy documentation on this issue is confusing due to its multiple uses of the term root widget.
The fix is to just modify the change_text() method of the ActivityPopup class. Here is an updated version of that method, that should fix your problems:
class ActivityPopup(FloatLayout):
def change_text(self):
scn = App.get_running_app().root.get_screen('day')
scn.ids.null.text = "Geschafft"
So I am building a general knowledge quiz game, and upon startup of the app, the player's score (that is kept in a .txt file) should be displayed in a label.
I've tried to use the on_start() function for this, but I can't seem to access the ids of the 'score' label. Here is the line of my code that gives the error:
self.root.get_screen("home_screen").ids.score.text = str(playerScore)
I receive the following error:
AttributeError: 'super' object has no attribute '__getattr__'
Here is my full code:
#main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_file('design.kv')
class RootWidget(ScreenManager):
pass
class HomeScreen(Screen):
pass
class MainApp(App):
def build(self):
return RootWidget()
def on_start(self):
with open("score.txt", "r") as f:
playerScore = f.readline()
self.root.get_screen("home_screen").ids.score.text = str(playerScore)
if __name__ == "__main__":
MainApp().run()
and
#design.kv
<HomeScreen>:
GridLayout:
cols: 1
GridLayout:
cols: 2
Button:
id: infoButton
text: "Wiki"
Label:
id: score
GridLayout:
cols: 1
Label:
id: Question
text: "Question"
Button:
id: Button1
text: "Option 1"
Button:
id: Button1
text: "Option 2"
Button:
id: Button1
text: "Option 3"
Button:
id: Button1
text: "Option 4"
<RootWidget>:
HomeScreen:
name: "home_screen"
and also a 'score.txt' file with only - '100' inside.
Thank you.
Your approach was right. I am not sure what you want your layout to look like, but you made two mistakes:
Your .kv code was missing indentations after your class definitions
Even with indentations, you placed three GridLayouts on top of each other. One approach would be to put these three GridLayouts inside a BoxLayout, or just structurize your GridLayout in another way (e.g. pack all widgets inside one GridLayout). Basically you had your "Wiki" and "100" (score) button already, but they just were behind your Option buttons so you could barely see them.
#design.kv
<HomeScreen>:
BoxLayout:
GridLayout:
cols: 1
GridLayout:
cols: 2
Button:
id: infoButton
text: "Wiki"
Label:
id: score
GridLayout:
cols: 1
Label:
id: Question
text: "Question"
Button:
id: Button1
text: "Option 1"
Button:
id: Button1
text: "Option 2"
Button:
id: Button1
text: "Option 3"
Button:
id: Button1
text: "Option 4"
<RootWidget>:
HomeScreen:
name: "home_screen"
My app gets data from a database and and is stored into variables in Python. The code below is a simplified version where you have two screens. The first screen has two buttons and the second screen has a label and a back button. The text of the label on the second screen will change depending on what button is pressed.
When run, the label is set to the value of the StringProperty, which is "Test". When one of the buttons are clicked the ChangeScreen function is run and works out the correct new label. The LabelUpdater function on the second is run which should change the string property but doesn't. How do I fix this issue? Thanks <3
Python:
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class DemoScreen1(Screen):
def ChangeScreen(self, button_text):
if button_text == "Button 1":
new_label = "This is the new label when button 1 is pressed"
DemoScreen2.LabelUpdater(new_label)
else:
new_label2 = "This is the new label when button 2 is pressed"
DemoScreen2.LabelUpdater(new_label2)
self.parent.current = "demoscreen2"
class DemoScreen2(Screen):
screen2_label = StringProperty("Test")
def LabelUpdater(NEW_LABEL):
screen2_label = StringProperty(NEW_LABEL)
class AppScreenManager(ScreenManager):
pass
class Tester(App):
pass
if __name__ == '__main__':
Tester().run()
Kivy:
AppScreenManager:
DemoScreen1:
DemoScreen2:
<DemoScreen1>:
name: "demoscreen1"
orientation: "vertical"
GridLayout:
rows: 2
Button:
id: Button1
text: "Button 1"
on_release: root.ChangeScreen(Button1.text)
Button:
id: Button2
text: "Button 2"
on_release: root.ChangeScreen(Button2.text)
<DemoScreen2>:
name: "demoscreen2"
orientation: "vertical"
GridLayout:
rows:2
Label:
text: root.screen2_label
Button:
text:"Back"
on_release: app.root.current = "demoscreen1"
Use ids and reference through AppScreenManager aka the ScreenManager.
self.parent.ids.screen2.screen2_label = new_label
See full example below.
import kivy
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
class DemoScreen1(Screen):
def ChangeScreen(self, button_text):
if button_text == "Button 1":
new_label = "This is the new label when button 1 is pressed"
print('Palim')
#DemoScreen2.LabelUpdater(new_label)
self.parent.ids.screen2.screen2_label = new_label
else:
new_label2 = "This is the new label when button 2 is pressed"
self.parent.ids.screen2.screen2_label = new_label2
#DemoScreen2.LabelUpdater(new_label2)
self.parent.current = "demoscreen2"
class DemoScreen2(Screen):
screen2_label = StringProperty("Test")
def LabelUpdater(NEW_LABEL):
screen2_label = StringProperty(NEW_LABEL)
class AppScreenManager(ScreenManager):
pass
class Tester(App):
pass
if __name__ == '__main__':
Tester().run()
kv
AppScreenManager:
DemoScreen1:
id: screen1
DemoScreen2:
id: screen2
<DemoScreen1>:
name: "demoscreen1"
orientation: "vertical"
GridLayout:
rows: 2
Button:
id: Button1
text: "Button 1"
on_release: root.ChangeScreen(Button1.text)
Button:
id: Button2
text: "Button 2"
on_release: root.ChangeScreen(Button2.text)
<DemoScreen2>:
name: "demoscreen2"
orientation: "vertical"
GridLayout:
rows:2
Label:
text: root.screen2_label
Button:
text:"Back"
on_release: app.root.current = "demoscreen1"
I am trying to update a field that exists in another screen but am not succeeding.
I would be very very pleased when someone could tell me what I am doing wrong here.
myscreenskv.py:
style = r'''
# File: myscreenskv.py
#: import myscreens myscreens
<ScreenManagement>:
MainScreen:
Screen1:
Screen2:
<MainScreen>:
name: 'main'
mainlog:mainlog
id: scrmain
BoxLayout:
orientation: "vertical"
Label:
text: "Main"
Label:
id: mainlog
Button:
text: "go to screen 1"
on_press:
app.root.current = "screen1"
root.action1()
Button:
text: "go to screen 2"
on_press:
app.root.current = "screen2"
root.action2()
<Screen1>:
name: 'screen1'
sc1log:sc1log
id: scr1
BoxLayout:
orientation: "vertical"
Label:
text: "Screen1"
Label:
id: sc1log
Button:
text: "go to main screen"
on_press: app.root.current = "main"
Button:
text: "go to screen 2"
on_press: app.root.current = "screen2"
<Screen2>:
name: 'screen2'
id: scr2
sc2log:sc2log
BoxLayout:
orientation: "vertical"
Label:
text: "Screen2"
Label:
id: sc2log
Button:
text: "go to main screen"
on_press: app.root.current = "main"
Button:
text: "go to screen 1"
on_press: app.root.current = "screen1"
'''
.py:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from myscreenskv import style
class MainScreen(Screen):
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def action1(self):
self.ids.scr1.sc1log.text = 'Coming from main'
def action2(self):
self.ids.scr2.sc2log.text = 'Coming from main'
class Screen1(Screen):
def __init__(self, **kwargs):
super(Screen1, self).__init__(**kwargs)
def action1(self):
self.ids.main.mainlog.text = 'Coming from screen1'
def action2(self):
self.ids.scr2.sc2log.text = 'Coming from screen1'
class Screen2(Screen):
def __init__(self, **kwargs):
super(Screen2, self).__init__(**kwargs)
def action1(self):
self.ids.main.mainlog.text = 'Coming from screen2'
def action2(self):
self.ids.scr1.sc1log.text = 'Coming from screen2'
class MyscreensApp(App):
def build(self):
Builder.load_string(style)
sm = ScreenManager()
sm.add_widget(MainScreen())
sm.add_widget(Screen1())
sm.add_widget(Screen2())
sm.current = 'main'
return sm
if __name__ == '__main__':
MyscreensApp().run()
You are trying to access ids dictionary, that's nice, yet in a completely different instance, that's why this error:
AttributeError: 'super' object has no attribute '__getattr__'
You need to access the right instance, to be able to access its properties, which in your case you need to access the ScreenManager, to access its screens property (a list of instances), from which you can do the desired edits of for example text:
MainScreen.action1():
self.manager.screens[1].sc1log.text = 'Coming from main'
# no ids, because you put it into a variable before
To understand why it works let's look at the widget tree:
<MainScreen>:
id: scrmain
BoxLayout:
Label:
Label:
id: mainlog
Button:
Button:
here the ids are a dictionary in MainScreen accessible from MainScreen().ids(an instance) and this is the output:
{'mainlog': <WeakProxy to <kivy.uix.label.Label object at 0x12345678>>}
which means you can't really assign the root widget to its own dictionary - at least not this way + makes no sense anyway, because you can just call root, which gives you the instance of the root widget.
The whole code is working well. But when u go to:
student > Add New student > > Fill all columns of new student > then submit
it's not working and I can't figure out the issue. Here is the following code. Any help will be appreciated
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen ,FadeTransition
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
import csv
from kivy.uix.textinput import TextInput
Builder.load_string("""
<MenuScreen>:
BoxLayout:
Button:
text: 'Teacher'
on_press: root.manager.current = 'screen1'
Button:
text: 'Student '
on_press:root.manager.current = 'screen2'
Button:
text: 'Quit'
<Screen1>:
BoxLayout:
Button:
text: 'Teacher Info'
#on_press:root.manager.current = 'login'
Button:
text: 'Teacher Attandance'
Button:
text: 'Add New Teacher'
on_press:root.manager.current = 'add_teacher'
Button:
text: 'Back'
on_press:root.manager.current ='menu'
<add_new_teacher>:
GridLayout:
cols:2
Label:
text:'Name'
TextInput:
id: name_input
multiline: False
Label:
text:'Father Name'
TextInput:
id: name_input
multiline: False
Label:
text: 'Mother Name'
TextInput:
id: name_input
multiline: False
Label:
text: 'Class'
TextInput:
id: name_input
multine: False
Label:
text:'Roll no.'
text: 'Student Info'
on_press:root.csv_std()
Button:
text: 'Student Attandance'
# on_press:root.manager.current ='login'
Button:
text: 'Add New Student'
on_press:root.manager.current = 'add_student'
Button
text: 'Back'
on_press:root.manager.current = 'menu'
<add_new_student>:
GridLayout:
cols:2
Label:
text:'Name'
TextInput:
id: self.name
multiline: False
Label:
text:'Father Name'
TextInput:
id: self.fname
multiline: False
Label:
text: 'Mother Name'
TextInput:
id: self.mname
multiline: False
Label:
text: 'Class'
TextInput:
id: self.c
multine: False
Label:
text:'Roll no.'
TextInput:
id: self.r
multiline:False
Button:
text:'Print'
Button:
text:'Submit'
on_press:root.print_text()
Button:
text:'Back'
on_press:root.manager.current= 'screen2'
""")
# Declare both screens
class MenuScreen(Screen):
pass
class add_new_teacher(Screen):
pass
class Screen1(Screen):
pass
class Screen2(Screen):
def csv_std(self):
f = open("a.csv", 'r')
reader = csv.reader(f)
for row in reader:
print(" ".join(row))
pass
class add_new_student(Screen):
def print_text(self):
for child in reversed(self.children):
if isinstance(child, TextInput):
print child.text
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(add_new_teacher(name='add_teacher'))
sm.add_widget(add_new_student(name='add_student'))
sm.add_widget(Screen1(name='screen1'))
sm.add_widget(Screen2(name='screen2'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()
You code formatting was horrible, but at least you didn't use backticks. For future cases, copy&paste your whole example you want to show here, then select that example(whole) and press Ctrl + K, which will indent all selected lines, so that it'd look ok.
The code works exactly how it supposed to work, because root.print_text() targets add_new_student class and its children - not GridLayout which you want to access.
Edit the line with for to this: for child in reversed(self.children[0].children): and you are good to go. :)
Or more efficient solution would be to get that Screen to behave as a layout too, which you can get with inheritting both from Screen and some layout, but ensure the layout is first:
class add_new_student(GridLayout, Screen):
def print_text(self):
for child in reversed(self.children):
if isinstance(child, TextInput):
print child.text
kv:
<add_new_student>:
cols:2
Label:
text:'Name'