I want to implement the following:
Create a Kivy label inside python using add_widget() function. However, I want to benefit from the fact that the label is automatically updated when the text variable has changed (in my case self.gui_data.number). I know that I could achieve this when adding the label inside the .kv file, however this is not possible in my case since I need to build up parts of the GUI in run time.
Below you see code for a minimal example. There is a button add label which adds the label and the goal is that if one presses the decrement button, the label changes. Unfortunately this is not the case.
Any help is appreciated. Thanks!
main.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
import parameters
from kivy.properties import StringProperty
from kivy.event import EventDispatcher
class PongBall(Widget):
gui_data = parameters.Data()
def add_label(self):
self.gui_data.number = self.gui_data.number + '5'
label = Label(text=str(self.gui_data.number))
self.ids.board.add_widget(label)
def decrement(self):
self.gui_data.number = self.gui_data.number + '6'
def reset_parameters(self):
self.gui_data.reset()
class PongApp(App):
def build(self):
game = PongBall()
return game
if __name__ == '__main__':
PongApp().run()
parameters.py
from kivy.properties import StringProperty
from kivy.event import EventDispatcher
class Data(EventDispatcher):
number = StringProperty("0")
def reset(self):
self.number = "0"
pong.kv
<PongBall>:
BoxLayout:
orientation: "vertical"
id: board
Button:
text: "Add label"
on_press: root.add_label()
Button:
text: "Decrement"
on_press: root.decrement()
Button:
text: "Reset parameters"
on_press: root.reset_parameters()
In order to do what you want, you need a Property that you can bind to. In the modified version of your code below, I have added a number Property to the PongBall class.
class PongBall(Widget):
gui_data = Data()
number = StringProperty(gui_data.number) # Property for binding
def add_label(self):
self.gui_data.number = self.gui_data.number + '5'
label = Label(text=str(self.gui_data.number))
# bind to the number Property
self.bind(number=partial(self.number_changed, label))
self.ids.board.add_widget(label)
def number_changed(self, label, pongball, number):
# change the Label text
label.text = number
def decrement(self):
self.gui_data.number = self.gui_data.number + '6'
def reset_parameters(self):
Related
I wanted to know how can i make communication between multiple classes of the layout. I'm trying to make the button on the MainLayout called add, add another button to the stacklayout which is one of its children.
in doing so, I have to both share a variable and also a functionality between them to implement the add_widget function on the stack layout. no matter what I do, I can't find a solution
code main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.uix.stacklayout import StackLayout
class Buttons_layout(StackLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.number = 0
for _ in range(5):
self.number += 1
self.add_widget(Button(text=str(self.number),color=(200,100,100),size_hint=(0.2,None),height=dp(100)))
class MainWidget(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
def add_button(self):
#dont know what to do here..................
pass
class CanvosExampleApp(App):
pass
if __name__ == '__main__':
CanvosExampleApp().run()
and the kv file:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
To allow easy navigation in your GUI, you can use ids. Here is a modified version of your kv with two new ids:
MainWidget:
<Buttons_layout>:
<Scroll_layout#ScrollView>:
Buttons_layout:
id: butts # new id
size_hint: 1,None
height:self.minimum_height
<MainWidget>:
Button:
text:'add'
on_press: root.add_button()
size_hint:None,1
width:dp(50)
Scroll_layout:
id: scroll # new id
Then, your add_button() method can be:
def add_button(self):
scroll = self.ids.scroll # get reference to the Scroll_layout
butts = scroll.ids.butts # get reference to the Buttons_layout
butts.add_widget(Button(text='Added',color=(200,100,100),size_hint=(0.2,None),height=dp(100)))
I tried to change the text of another screen from current screen. But it didn't work
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.properties import ObjectProperty
from kivy.lang.builder import Builder
Builder.load_string("""
<Input_Screen>:
input: input
BoxLayout:
orientation : "vertical"
TextInput:
id: input
text: "Changed"
Button:
text: "Enter"
on_press : root.clicked()
<Display_Screen>:
nice: nice
BoxLayout:
orientation : "vertical"
Label:
id: nice
text: "NotChanged"
""")
class Input_Screen(Screen):
input = ObjectProperty(None)
def clicked(self):
Display_Screen().change()
class Display_Screen(Screen):
nice = ObjectProperty(None)
def change(self):
print(self.nice.text) #Checking the Current text of The Label
print(Input_Screen().input.text) #Checking What do we want to change
self.nice.text = Input_Screen().input.text
print(self.nice.text) #Checking if it has change or not
MyApp().sm.current = "Ds" #Changing Screen to Display_Screen
print(self.nice.text) #Checking if it has change or not
class MyApp(App):
sm = ScreenManager()
def build(self):
self.sm.add_widget(Input_Screen(name="Is"))
self.sm.add_widget(Display_Screen(name="Ds"))
return self.sm
MyApp().run()
What I get in My console:
NotChanged #Checked the Current text of The Label
Changed #Checked What do we want to change
Changed #Checked if it has change or not
Changed #Checked if it has change or not
It says that my Label's text has been changed but when it goes to the next screen it hasn't been changed.
Display_Screen
Anyone knows whats the problem? Help me please
Whenever you use a construction like ClassName(), you are creating a new instance of ClassName. You are doing this in your change() method and in your clicked() method. Whenever you do that, you are referencing that new instance, and not the instance that is in your GUI.
To fix that, change:
def clicked(self):
Display_Screen().change()
to:
def clicked(self):
self.manager.get_screen('Ds').change()
and in your change() method, replace:
Input_Screen()
with:
self.manager.get_screen('Is')
and replace"
MyApp()
with:
App.get_running_app()
I am trying to have a function run continuously and spit out a distance that the label uses that I'll eventually tie to a sonar module, but the label remains blank and I am at a loss as to what I am doing wrong. If I just add a print statement for that distance variable it prints and updates just fine, just can't get the label to use it.
Part II of my question is how do I reference my same function in the second window and also have a label that updates from that same function?
Thanks for the help in advance, I am very very new to kivy and just started learning python a few months ago as well.
Python code:
from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen # for multiple screens
from kivy.properties import StringProperty
class MySonar(Screen):
global i
i = 1
distance = StringProperty("")
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
self.root.distance=str(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
kv = Builder.load_file("help.kv")
class HelpMe(App):
def build(self):
#running interval update to keep running code above
Clock.schedule_interval(lambda dt: MySonar.sonar(self), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Kivy:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text:root.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
##text:root.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
To simplify the access to distance, you can put that StringProperty in the HelpMe App:
class MySonar(Screen):
global i
i = 1
#Generic function that just adds itself up, just using to try and get the label to change before I throw in my real function
def sonar(self):
global i
if i < 250:
distance = (10 + .1 * i)
i += 1
else:
i = 1
distance = 10
# set the value of distance in the StringProperty of the App
App.get_running_app().distance=str(distance)
print(distance)
class DropWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
# kv = Builder.load_file("help.kv")
class HelpMe(App):
distance = StringProperty('')
def build(self):
kv = Builder.load_file("help.kv")
#running interval update to keep running code above
sonar_instance = kv.get_screen('Main')
Clock.schedule_interval(lambda dt: sonar_instance.sonar(), 0.1)
return kv
if __name__ == "__main__":
HelpMe().run()
Note that I have also moved the Builder.load_file() inside the App. This is good practice when you reference app in the kv file (as I have done). Also, calling the sonar() method using MySonar.sonar(self) will not work. You need to use a reference to the instance of MySonar that is in your GUI.
Now the kv file becomes:
WindowManager:
MySonar:
DropWindow:
<MySonar>:
name:"Main"
GridLayout:
cols:1
##Need this to update
Label:
text: app.distance
Button:
text:"Next Window"
on_release:
app.root.current="Drop"
root.manager.transition.direction="left"
<DropWindow>:
name:"Drop"
GridLayout:
cols:1
##Need this to update, commented out the text so the program will run and you can see the blank label for part one of my question
Label:
text: app.distance
Button:
text:"Cancel"
on_release:
app.root.current="Main"
root.manager.transition.direction="right"
The change is that the text attribute of both Labels is now just app.distance.
I'm trying to build a Kivy application that has 2 screens which are re-used over and over again with different text.
So I go from a FirstScreen with a Label that says "First1" to a SecondScreen with a Label that says "Second1", and then back to the FirstScreen but this time with the Label "First2", then SecondScreen and "Second2", and so on and so forth.
The code for this is pretty straightforward, but there seems to be a problem in updating the Label text without a designated update button. For some reason, my Python code manages to update the text, but it isn't updated in my .kv file. So for instance, my print statements will tell me that the Label text is "First2", but Kivy displays "First1" for me. I've illustrated this in the Screenshot below:
By adding a Button that updates the text on press, everything is updated, synced up and works, but I'd really like it to work without the extra user input. Does anybody know how I can go about this? I've scoured the docs and stackoverflow questions left and right but can't seem to find the answer to my seemingly simple problem.
Here's the code:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty, ObjectProperty
from kivy.lang import Builder
S_ID = 1 # global screen ID. I'm using this to detect what text to use.
class FirstScreen(Screen):
text = StringProperty("")
lbl = ObjectProperty(None)
def __init__(self, **kwargs):
super(FirstScreen, self).__init__(**kwargs)
global S_ID
print("\nS_ID is ", S_ID)
self.update()
def update(self):
print("FIRST - UPDATE")
if S_ID == 1:
print("FIRST1")
self.text = "FIRST1"
elif S_ID == 2:
print("FIRST2")
self.text = "FIRST2"
print("self.lbl.text", self.lbl.text)
else:
print("FIRST ELSE")
self.text = "FIRST ELSE"
def pressed(self):
sm.current = "second"
class SecondScreen(Screen):
text = StringProperty("")
def __init__(self, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.update()
def update(self):
print("SECOND - UPDATE")
if S_ID == 1:
print("SECOND1")
self.text = "SECOND1"
elif S_ID == 2:
print("SECOND2")
self.text = "SECOND2"
else:
print("SECOND ELSE")
self.text = "SECOND ELSE"
def pressed(self):
global S_ID
S_ID += 1
FirstScreen.update(FirstScreen())
sm.current = "first"
sm = ScreenManager()
kv = Builder.load_file("test.kv")
sm.add_widget(FirstScreen(name='first'))
sm.add_widget(SecondScreen(name='second'))
sm.current = "first"
class MyApp(App):
def build(self):
return sm
if __name__ == '__main__':
MyApp().run()
and here's the .kv file:
<FirstScreen>:
name: "first"
lbl: lbl:
GridLayout:
cols:2
Label:
id: lbl
text: root.text
Button:
text: "next"
on_press: root.pressed()
<SecondScreen>:
name: "second"
GridLayout:
cols:2
Label:
text: root.text
Button:
text: "next"
on_press:
root.pressed()
The problem is your statement:
FirstScreen.update(FirstScreen())
This statement is creating a new instance of FirstScreen and updating that instance. Unfortunately, that instance is not the one shown in your GUI. You can correct that by replacing the above statement with:
first_screen = self.manager.get_screen('first')
first_screen.update()
This code gets the instance of FirstScreen from the ScreenManager and calls update() on that instance.
I am trying to make a program that reads the dictionary after the user inputs their name and assigns a random selection based on weighted values in the dictionary. As of now the logic for selecting a random value from the dictionary is working but I have it printing to the console. I would like it to appear in a popup window (which i have but cannot get the output variable to show up there)
four.kv
WindowManager:
MainWindow:
<MainWindow>:
name:'main'
player_python:player_kv
GridLayout:
cols:1
GridLayout:
cols:2
Label:
text:'Player:'
TextInput:
id: player_kv
multiline: False
Button:
text: 'Random'
on_press: root.btn()
<P>:
output:output
FloatLayout:
Label:
id: output
main4.py
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
import random
#from Dict import *
#### example dictionary
character = {
'John':
{'Water': 2, #50%
'Fire': 1, #25%
'Earth': 1,}, #25%
'Bill':
{'Water': 1, #25%
'Fire': 2, #50%
'Earth': 1,}} #25%
####
class MainWindow(Screen):
player_python = ObjectProperty(None)
output = StringProperty('')
def btn(self):
show_popup()
player = self.player_python.text
weighted_list = []
for c in character[player]:
for w in range(character[player][c]):
weighted_list.append(c)
self.output= random.choice(weighted_list)
print(self.output) ###### instead of this printing to console I want it to display in popup window
self.player_python.text = ''
class P(FloatLayout):
pass
def show_popup():
show = P()
popupWindow = Popup(title='random character', content=show, size_hint=(None,None),size=(400,400) )
popupWindow.open()
class WindowManager(ScreenManager):
pass
kv = Builder.load_file('four.kv')
class FourApp(App):
def build(self):
return kv
if __name__ == '__main__':
FourApp().run()
https://gist.github.com/PatrickToole/00cc72cdd7ff5146976e5d92baad8e02
Thanks in advance
-P
I haven't tested this code, but try passing self.output to your show_popup() method. This would mean changing your btn() method to something like:
def btn(self):
player = self.player_python.text
weighted_list = []
for c in character[player]:
for w in range(character[player][c]):
weighted_list.append(c)
self.output= random.choice(weighted_list)
print(self.output) ###### instead of this printing to console I want it to display in popup window
self.player_python.text = ''
show_popup(self.output)
And in the show_popup() method:
def show_popup(output):
show = P()
show.output.text = output
popupWindow = Popup(title='random character', content=show, size_hint=(None,None),size=(400,400) )
popupWindow.open()
As I mentioned, I haven't tested this code, but something like this should work.