Kivy - How do you change a StringProperty value on a different screen? - python

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"

Related

How to dynamically add widgets on a different screen depending on which button is pressed if the buttons were added dynamically as well?

The Problem
I'm attempting to create dynamic buttons tied to "pages" in my program. Each button and page inherits a name from a database that I have tied to the program. In this case, I'm having trouble adding widgets to a screen and changing to that screen on button press. So far, I'm able to implement dynamic buttons that could change the screen and receive variables for text but the widgets aren't being added or aren't showing up.
The Code
Upon request, I recreated the code with the same problem my program was experiencing.
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import StringProperty
from kivymd.uix.button import MDRectangleFlatButton
from functools import partial
kv = '''
ScreenManager
ScreenOne:
ScreenTwo:
ScreenThree:
<ScreenOne>:
ScrollView:
MDGridLayout:
id: screen_one
cols: 1
adaptive_height: True
padding: "40dp"
MDFlatButton:
text: "Next Page"
theme_text_color: "Custom"
text_color: 0, 0, 1, 1
pos_hint: {"center_x": .5, "center_y": .5}
on_press: root.manager.current = 'Screen 2'
<ScreenTwo>:
name: 'Screen 2'
on_pre_enter: root.on_load()
ScrollView:
MDGridLayout:
id: screen_two
cols: 1
adaptive_height: True
padding: "40dp"
<ScreenThree>:
name: 'Screen 3'
ScrollView:
MDGridLayout:
id: screen_three
cols: 1
adaptive_height: True
padding: "40dp"
'''
sm = ScreenManager()
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def on_load(self):
name_list = ['Page 1', 'Page 2', 'Page 3']
button_list = []
for x in name_list:
page_button = MDRectangleFlatButton(text = x, theme_text_color = "Custom")
button_list.append(page_button)
for x in button_list:
x.bind(on_press = partial(self.load_home, x.text))
self.ids.screen_two.add_widget(x)
def load_home(self, name, *largs):
self.manager.current = 'Screen 3'
Screen3 = ScreenThree()
Screen3.on_load(name)
def on_leave(self):
self.ids.screen_two.clear_widgets()
# Below is the particular code I'm having trouble with. It's supposed
# to display a page with a button with the same name as the button
# that sends you to Screen Three. However, the button is not showing up.
class ScreenThree(Screen):
def on_load(self, name, *kwargs):
test_button = MDRectangleFlatButton(text = name, theme_text_color = "Custom")
self.ids.screen_three.add_widget(test_button)
sm.add_widget(ScreenOne(name="Screen 1"))
sm.add_widget(ScreenTwo(name="Screen 2"))
sm.add_widget(ScreenThree(name="Screen 3"))
class TestApp(MDApp):
def build(self):
screen = Builder.load_string(kv)
return screen
TestApp().run()
All help, tips, and solutions would be greatly appreciated. Thank you in advanced.
I figured it out! Turns out, I can add the buttons from the same method as ScreenTwo's load_home() method by giving each screen ids via screenmanager in the kivy code:
ScreenManager
ScreenOne:
id: one
ScreenTwo:
id: two
ScreenThree:
id: three
And then use the add_widget function in the load_home() method:
def load_home(self, name, *largs):
test_button = MDRectangleFlatButton(text = name, theme_text_color = "Custom")
self.manager.ids.three.ids.screen_three.add_widget(test_button)
self.manager.current = 'Screen 3'

Use ids in on_start() function kivy Python

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"

Displaying an image over multiple buttons - kivy

I want to display a single image over multiple different buttons (as shown in the picture: https://i.stack.imgur.com/BQ99R.jpg)
When I try to do so, the image gets covered by the buttons and hidden in the background.
What I want to know is that is this even possible in kivy? If yes, where do I define the image, in the label or button?
This code:
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder
Builder.load_string("""
<Base>:
GridLayout:
cols: 2
size_hint: 1, 1
Button:
text: "Button 1"
on_press: print(self.text)
Button:
text: "Button 2"
on_press: print(self.text)
Button:
text: "Button 3"
on_press: print(self.text)
Button:
text: "Button 4"
on_press: print(self.text)
Image:
source: 'wolf.png'
""")
class Base(FloatLayout):
def __init__(self, **kwargs):
super(Base, self).__init__(**kwargs)
class SampleApp(App):
def build(self):
return Base()
if __name__ == "__main__":
SampleApp().run()
produces this:
Of course you have to provide an image yourself ;o)

Change popup text in Kivy

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

Refactor on_press into class rule

In my RootWidget I have a label and two buttons. I want to dynamically change the text of the label whenever one of the buttons is clicked.
Here's a minimal working example of how I do it at the moment.
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
Button:
text: '1'
on_press: label.text = self.text
Button:
text: '2'
on_press: label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()
Obviously I want to refactor the line on_press: label.text = self.text. My first tries ended in
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
<MyButton>:
on_press: label.text = self.text
But obviously the MyButton-class doesn't know the property label of the RootWidget-class. And class rules inside class rules is also not allowed.
Is there a way of accomplishing binding the on_press-action dynamically?
You can refer to the Label like this:
<MyButton#Button>:
on_press: self.parent.ids.label.text = self.text
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
It's quite simple actually, because through kv you can access parent of a widget/rule easily with self.parent, so your code would look like:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
But:
text: '1'
But:
text: '2'
<But>:
on_press: self.parent.label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()

Categories