I want to make a simple program that is just showing definitions that are stored in text file.One label and button to show next definition. I try to do it with documentation but i cannot find how to load text into label. Can someone show me to some good resources or code samples ?
My code for now (i want to build in on top of example from kivy website):
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
return Label(text = 'Hello world')
if __name__ == '__main__':
MyApp().run()
The easiest way to update widgets in the UI are by binding to their properties. This can be done in code, but the real power of kivy in my opinion comes from using it's declarative UI language. Using kv, you get automatic binding.
Here is a quick example of what you might do:
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import StringProperty
kv = '''
BoxLayout:
orientation: 'vertical'
Label:
text: app.text
Button:
text: 'click me'
on_press: app.clicked()
'''
class MyApp(App):
text = StringProperty("hello world")
def build(self):
return Builder.load_string(kv)
def clicked(self):
self.text = "clicked!"
if __name__ == '__main__':
MyApp().run()
In the kv description of the UI, you tell kivy that you want the text on the Label to be bound to a StringProperty on the app which you defined on the class. The auto-binding means that anytime you set a value to that property (like in the clicked function), the UI will update with the new value automatically.
Related
I need to insert a piece of code in the text of a Label that involves variables inside kv lang as shown below:
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.core.window import Window
Window.size=(200,200)
Builder.load_string("""
<Screen>
BoxLayout:
orientation:'vertical'
size:root.width,root.height
Label:
id:label
text:'Nothing'
Button:
id:button
text:'Insert'
on_release: root.insert_text()
""")
class Screen(Widget):
def insert_text(self):
self.ids.label.text='Something' if button.text=='Insert' else 'Nothing' #Label should say "Something" if Button's text says 'Insert'
class App(App):
def build(self):
return Screen()
if __name__=='__main__':
App().run()
When running this code I get the error: NameError: name 'button' is not defined
How can I avoid this error and have the entered code work within kv lang?
I already tried putting self.ids.button.text instead of button.text and despite not getting the error, it doesn't work inside the application either.
Edit: Also tried insert button.text as a raw string but kv lang just ignored it.
If you want Label text to change without a Button press, then you probably want to use a Property. You can reference a Property in kv and any changes to that Property will trigger evaluation where that Property is used. So, here is a modified version of your code that uses a Property named follow_changes that controls whether the Label text follows the Button text:
from kivy.app import App
from kivy.properties import BooleanProperty
from kivy.uix.widget import Widget
from kivy.lang.builder import Builder
from kivy.core.window import Window
Window.size = (200, 200)
Builder.load_string("""
<Screen>
BoxLayout:
orientation:'vertical'
size:root.width,root.height
Label:
id:label
text: 'Something' if root.follow_changes and button.text == 'Insert' else 'Nothing' # use follow_changes Property
Button:
id:button
text:'Insert'
on_release:
root.follow_changes = True # turn on following_changes
Button:
text: "Change Button Text"
on_release:
button.text = 'Abba'
""")
class Screen(Widget):
follow_changes = BooleanProperty(False) # Property to control Label text
class App(App):
def build(self):
return Screen()
if __name__=='__main__':
App().run()
Since the error is name 'button' is not defined, you just need to define button. Like this:
def insert_text(self):
button = self.ids.button # define button
self.ids.label.text='Something' if button.text=='Insert' else 'Nothing' #Label should say "Something" if Button's text says 'Insert'
>> BACKGROUND :
I want to update/change the text of a Button in the SecondScreen with a press of a Button in the MainScreen. Well I did some research and did what I want, and when I checked in the terminal the text did change. BUUT, the text shown on the SecondScreen did not.
>> THIS IS WHAT I DID :
((Mind you that I'm only using snippets of code for example, I'm going to post the whole code below.))
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
## on press it goes to it's root and do the "funcself" function in it
which is :
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
## it re-directs to the SecondScreen and do the "funcscreen" function
which is :
class SecondScreen(Screen):
def funcscreen(self):
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
and then I checked if I did it successfully by doing print(self.ids["button"].text), and yes!
It did change, but when I navigated to the next screen, the text shown still didn't change.
Anyone mind helping and explaining?
FULL CODE :
python file :
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class MainScreen(Screen):
def funcself(self):
app.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
second = SecondScreen()
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
app = reproduce()
app.run()
kivy file :
<MainScreen>:
GridLayout:
rows:2
Label:
text: "PRESS TO GO TO THE NEXT PAGE"
GridLayout:
cols:2
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.funcself()
Button:
text:">>>"
on_press:
app.change_screen("second")
root.manager.transition.direction = "left"
<SecondScreen>:
GridLayout:
rows:2
Label:
id:label
text: "PRESS TO CHECK AND RETURN TO PREV PAGE"
Button:
id:button
text:"TEXT BEFORE CHANGE"
on_press:
app.change_screen("first")
root.manager.transition.direction = "right"
app.check()
GridLayout:
cols: 1
ScreenManager:
id:sm
MainScreen:
id:main
name:"first"
SecondScreen:
id:second
name:"second"
Root Cause
It did not change because there are two instances of SecondScreen() i.e. one instantiated in the kv file and the other one instantiated in the App class, reproduce(). The view presented is created from the kv file and the second instance does not has a view associated to it.
Solution
There are two solutions to the problem, and remove second = SecondScreen() from the App class.
Kivy Screen ยป default property manager
Each screen has by default a property manager that gives you the
instance of the ScreenManager used.
Using get_screen()
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
Using App.get_running_app() & ids
class MainScreen(Screen):
def funcself(self):
App.get_running_app().root.ids.second.funcscreen()
Example
In the following example, there are two solutions provided but one of it is commented off.
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
class MainScreen(Screen):
def funcself(self):
self.manager.get_screen('second').funcscreen()
# App.get_running_app().root.ids.second.funcscreen()
class SecondScreen(Screen):
def funcscreen(self):
value = self.ids["button"]
self.ids["button"].text = "SUPPOSED TO CHANGE TO THIS"
kv = Builder.load_file("reproduce.kv")
class reproduce(App):
def build(self):
return kv
def change_screen(self, x):
scrnmngr = self.root.ids["sm"]
scrnmngr.current = x
def check(self):
print(self.second.ids["button"].text)
if __name__ == "__main__":
reproduce().run()
Output
The second attribute you define in your app class, is a new instantiation of the screen, and not really the instance you got in your screenmanager, which you add in kv. This is why when you check, you see its changed, but not on the right instance. And again when you call app.second.func, from mainscreen, again its the wrong instance.
But your app always has a root. In your case its the gridlayout. And every screen has a manager. There are a couple of ways to acces it. But you can do like this.
In your mainscreen class in kv:
Button:
text:"PRESS TO CHANGE TEXT"
on_press:
root.manager.get_screen("second").ids["button"].text = "Something"
Here it gets the screenmanager, and uses its get_screen() method to get the screen named second, and then the id's of that kv rule.
I'm a complete novice to both python and kivy having learnt python from codeacademy about a week ago and kivy from youtube tutorials.
Could someone please explain to me why the code below does not result in a screen with a label displaying n, which is incremented by the button?
Python file
import kivy
from kivy.app import App
from kivy.uix.button import Button, Label
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty
class Example(BoxLayout):
n = 0
def n_plus(self):
self.n += 1
class ExampleApp(App):
def build(self):
return Example()
example = ExampleApp()
example.run()
KV file
<Example>:
BoxLayout:
Label:
text: str(root.n)
Button:
text: "+1"
on_press: root.n_plus()
Then could you explain why making n = NumericProperty(0) makes this work?
I'd like to run some functions on n which don't seem to work on numeric properties.
Because when you use NumericProperty()
As the official document said:
It produces events such that when an attribute of your object changes,
all properties that reference that attribute are automatically
updated.
So, in short, it creates a binding relationship between your UI(.kv) and attribute of its class(.py)
But, actually, you can modify the UI by yourself without the help from the kivy framework. I changed your example as the following:
Add an id attribute to your widget
Access the id attribute by using self.ids.your_id_in_kv_file
But it's obviously not good, since now you need to update your UIby yourself everytime you want to update your UI. But with that XXXXProperty from kivy, you just need to change the value of that attribute, you don't need to worry about the UI at all.
Another disadvantage of the solution is that, when you need to change the UI, you need to change tons of code if you modify them all by yourself...
Here is the example:
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.lang.builder import Builder
from kivy.uix.boxlayout import BoxLayout
Builder.load_string("""
<Example>:
BoxLayout:
Label:
id: lbl
text: "0"
Button:
text: "+1"
on_press: root.n_plus()
""")
class Example(BoxLayout):
def n_plus(self):
value = self.ids.lbl.text
self.ids.lbl.text = str(int(value) + 1)
class ExampleApp(App):
def build(self):
return Example()
if __name__ == '__main__':
ExampleApp().run()
I have written a code with GridLayout which need to be added with buttons in Python file. So, the add_widget() mainpulation should be done in build(). I am getting errors and couldn't get it.Someone Please help me.
In short, instead of add_btn(), I need it in build() of MineApp class.
Thanks in advance.`
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class MainLayout(BoxLayout):
def build(self):
pass
def add_btn(self,id):
for i in range(100):
id.add_widget(Button())
class MineApp(App):
def build(self):
return MainLayout()
if __name__ == '__main__':
MineApp().run()
mine.kv file:
<MainLayout>:
orientation:'vertical'
BoxLayout:
orientation:'horizontal'
height: '30px'
size_hint_y:None
TextInput:
id: tinput
text:'10'
Button:
text:'start'
on_press:root.add_btn(grid)
Label:
id:mylabel
text:'0'
GridLayout:
id: grid
cols:10
rows:10
It took some time but think I get what you're trying to say! You can get the same effect by passing the id parameter of Grid in the .py file like so...
class MainLayout(BoxLayout):
def build(self):
for i in range(100):
self.ids.grid.add_widget(Button())
Then, you can just take your start button and...
Button:
text:'start'
on_press:root.build()
This works because "self" in .py refers to the class while in the .kv a similar wording would be 'root' (while 'self' in .kv refers to the widget!) Is this what you kind of had in mind? Let me know! I tried it out myself and had no problem running it through :)
Also, it wasn't put explicitly here that to test the code you'd need to import Builder via:
from kivy.lang import Builder
and do...
Builder.load_file("mine.kv")
as your .py and .kv don't share the same name (which you'd still need to add a "#File name: main.py" to the .kv if they did match names! Other than that, it looks good!
I've defined two buttons: one in kv and one in Python. They are located in different screens and are used to navigate between them. What I found strange is that the button that was defined in Python successfully switched the screen, while the one defined in kv did not. Perhaps I'm not accessing the App class method properly?
Here is the code of the issue:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
Builder.load_string('''
<MyScreen1>:
Button:
id: my_bt
text: "back"
on_release: app.back
''')
class MyScreen1(Screen):
pass
class TestApp(App):
def here(self, btn):
self.sm.current = "back"
def back(self, btn):
self.sm.current = "here"
def build(self):
self.sm = ScreenManager()
s1 = Screen(name = "here")
bt = Button(text = "here",
on_release = self.here)
s2 = MyScreen1(name = "back")
#s2.ids['my_bt'].bind(on_release = self.back)
self.sm.add_widget(s1)
s1.add_widget(bt)
self.sm.add_widget(s2)
return self.sm
TestApp().run()
So if I define the switching function in kv (on_release), I can't go to the "here" screen. But if I uncomment that line in Python and comment the on_release: app.back instead, everything works fine.
I'm pretty sure that this is the correct way to access the current app, since it doesn't give me any errors (which means that the method was successfully located)
That's a subtle difference between kv and python: In kv you actually have to write the callback as a function call (a python expression), in this case:
on_release: app.back(self)