Understanding Kivy properities and binding methods - python

I am having problems understanding the usage of custom Properities and ways of binding methods to events.
Here's my code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import StringProperty
kivy_lang = '''
<MainWidget>:
on_my_property: my_label.text = 'from button bind method via StringProperty' + my_property
Label:
id: my_label
text: root.my_property
Button:
id: my_button
text: 'intro button'
'''
class MainWidget(BoxLayout):
# bind some properties
my_property = StringProperty('0')
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
# if needed to do sth on widget construction
self.ids.my_button.bind(on_press=self.my_method)
def my_method(self,*args,**kwargs):
self.my_property = str(int(self.my_property)+1)
self.ids.my_button.text = 'new'
class MyApp(App):
def build(self):
Builder.load_string(kivy_lang)
return MainWidget()
if __name__ == '__main__':
MyApp().run()
When I run it it renders OK, but when I click a button, as a result I get
NameError: name 'my_property' is not defined
I tried binding method for Button in kv lang with (and removing whole 'init()' on python side):
on_press: root.my_method
and then when I press button the app doesn't crash but nothing happens
Can someone explain me how to adjust this code to work?
I understand the code is a little 'mixed techniques' but I did it that way to get to know different approaches, so I would appreciate if You don't turn it all around :)

1/ you are missing 'self' before 'my_property' in 'on_my_property' bindind, hence the crash
2/ in kv bindings. the python code is called as written, so you need '()' after 'root.my_method', or the statement has no effect.

Related

Black Screen When Using on_enter Calling a Function with Kivy/Python

I'm new to Python, and especially new to Kivy.
I'm sure that whatever I'm doing is a simple fix, but I just cannot figure it out for the life of me.
I've been doing this all in a Python file, with no my.kv.
What I'm trying to do is call a function upon entering the first screen of my app, but when I do this it just gives me a blank screen.
Sorry if my code is an absolute mess.
This is my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
import requests
import json
Builder.load_string("""
<Manager>:
BuildScreen:
SubmitScreen:
<BuildScreen>:
name: 'page1'
on_enter: app.BuildAnswer()
<SubmitScreen>:
name: 'page2'
on enter: app.SubmitAnswer()
GridLayout:
cols:1
row_force_default:True
row_default_height:40
Button:
text:"Return"
on_release: root.manager.current = 'page1'
""")
class MainWidget(Widget):
pass
class Manager(ScreenManager):
pass
class BuildScreen(Screen):
pass
class SubmitScreen(Screen):
pass
class TheLabApp(App):
def __init__(self,**kwargs):
super(TheLabApp, self).__init__(**kwargs)
def BuildAnswer(self):
layout = GridLayout(cols=1, row_force_default=True, row_default_height=40)
self.spell = TextInput(hint_text = "Enter Spell", multiline=False)
button = Button(text="Get Spell", on_release=self.SubmitAnswer)
layout.add_widget(self.spell)
layout.add_widget(button)
return layout
def SubmitAnswer(self):
user_input = self.spell.text
#making input into url ready thingy
making_string = ''.join(str(x) for x in user_input)
x = '-'.join(making_string.split())
url = requests.get('https://www.dnd5eapi.co/api/spells/' + x)
#making it look pretty
pretty_spells = json.dumps(url.json(), indent=2)
#making it so I can get values from json
resp = json.loads(pretty_spells)
print(resp['name'])
print(resp['range'])
#the rest is just printing more of the spell's information
def build(self):
sm=ScreenManager()
sm.add_widget(BuildScreen(name="page1"))
sm.add_widget(SubmitScreen(name="page2"))
return sm
Any and all help would be incredibly appreciated, as I've been trying to find a solution for a couple days now.
A couple problems with your code.
First, the BuildAnswer() method creates and returns a layout, but returning something from an on_enter method has no effect. If you want that layout to appear in the BuildScreen, you must explicitly add that layout to the BuildScreen instance.
Second, defining the BuildAnswer() method in the TheLabApp class makes it difficult to access the BuildScreen instance. This is because the on_enter method is triggered very early in the process (before the root of the App is assigned).
I suggest moving the BuildAnswer() method to the BuildScreen class, and calling add_widget() to actually add the created layout to the BuildScreen instance. To do that, start by modifying your kv:
<BuildScreen>:
name: 'page1'
on_enter: self.BuildAnswer() # reflects new location of BuildAnswer()
And modifying your python code:
class BuildScreen(Screen):
def BuildAnswer(self):
layout = GridLayout(cols=1, row_force_default=True, row_default_height=40)
self.spell = TextInput(hint_text="Enter Spell", multiline=False)
button = Button(text="Get Spell", on_release=App.get_running_app().SubmitAnswer) # to access the SubmitAnswer method
layout.add_widget(self.spell)
layout.add_widget(button)
self.add_widget(layout) # add layout to the GUI
# return layout
And remove the BuildAnswer() method from the TheLabApp class.

Kivy | Can't set 2 buttons on_press binding in screen [duplicate]

I would like to know how to change screens using an on_press event binded to a button, without using a KV file/KV language.
I have read through the Kivy documentation, but have only been able to find solutions using a KV file.
Example:
on_press: root.manager.current = 'screen2'
I can also change the screen in the main python file using:
screenmanager.current = 'screen2'
But I cant figure out how to achieve the same using a button.
A working example with two screens, no kv file everything done in Python:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class ScreenOne(Screen):
def __init__ (self,**kwargs):
super (ScreenOne, self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 1", font_size='24dp')
my_button1 = Button(text="Go to screen 2",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen2'
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super (ScreenTwo,self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 2",font_size='24dp')
my_button1 = Button(text="Go to screen 1",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen1'
class TestApp(App):
def build(self):
my_screenmanager = ScreenManager()
screen1 = ScreenOne(name='screen1')
screen2 = ScreenTwo(name='screen2')
my_screenmanager.add_widget(screen1)
my_screenmanager.add_widget(screen2)
return my_screenmanager
if __name__ == '__main__':
TestApp().run()
One simple way to accomplish this is to define your own button subclass:
class ScreenButton(Button):
screenmanager = ObjectProperty()
def on_press(self, *args):
super(ScreenButton, self).on_press(*args)
self.screenmanager.current = 'whatever'
The on_press method is automatically called when the button is pressed, so the screenmanager's current property will be changed.
Then you can have code something like:
sm = ScreenManager()
sc1 = Screen(name='firstscreen')
sc1.add_widget(ScreenButton(screenmanager=sm))
sc2 = Screen(name='whatever')
sc2.add_widget(Label(text='another screen'))
sm.add_widget(sc1)
sm.add_widget(sc2)
Clicking the button should switch the screens as required.
Another way (which is probably how kv language actually does it) would be to manually use the bind method.
def switching_function(*args):
some_screen_manager.current = 'whatever'
some_button.bind(on_press=switching_function)
This would mean that switching_function is called whenever some_button is pressed. Of course there is a lot of flexibility here regarding how and when you define the function, so (for instance) you could do something more general like pass the screenmanager as the first argument to the function.
I didn't test this code and it isn't a complete app, but hopefully the meaning is clear. Either method should work fine, you can choose the way that seems most sensible. I might construct a more complete example later.
Another solution, was to use the setter method of EventDispatcher, to get a reference to the setter function for screen_manager.current
button.bind(on_press=partial(sm.setter('current'), (sm, 'whatever'))
of course, it's not very sexy, that's why kv is often a cleaner solution to these things, but it should work.
ps: in case you don't know about it, partial comes from the functools module, and it's often useful to build these kind of callbacks with a preloaded parameter.

Interaction between Kivy widgets in Python

I'm doing a proyect using kivy but i have a problem with the checkboxes. At first I'm trying to do the program like python coding (I know it is'nt clean, but I understand more) And i have a first screen with this coding:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.checkbox import CheckBox
class MainScreen(GridLayout):
def __init__(self,**kwargs):
e=[]
super(MainScreen, self).__init__(**kwargs)
self.cols=2
def on_checkbox_active(checkbox, value):
if value:
e.append(value)
print e
else:
print('The checkbox', checkbox, 'is inactive')
self.add_widget(Label(text='Inserta assignatures desitjades',font_size=35))
self.add_widget(Label(text=''))
ch1 = CheckBox()
self.add_widget(ch1)
self.add_widget(Label(text='Termotecnia'))
ch2 = CheckBox()
self.add_widget(ch2)
self.add_widget(Label(text='Termotecnia'))
ch3 = CheckBox()
self.add_widget(ch3)
self.add_widget(Label(text='Termotecnia'))
ch4 = CheckBox()
self.add_widget(ch4)
self.add_widget(Label(text='Termotecnia'))
b1=Button(text='Exit',background_color=[0.7,0.7,1,1],font_size=24)
self.add_widget(b1)
b2=Button(text='Next',font_size=24,font_color=[1,3,4,0],background_color=[1,2,3,6])
self.add_widget(b2)
ch1.bind(active=on_checkbox_active)
ch2.bind(active=on_checkbox_active)
b1.bind(on_press=exit)
b2.bind(on_press=reloaded)
...
class SimpleKivy(App):
def build(self):
return MainScreen()
if __name__=='__main__':
SimpleKivy().run()
I want to select two or three options for example, and save it for the next screen, like a type of selection. If anyone knows how to do it and save information for the next screen it woul help me a lot, because i have the code of the next screen for all the options, but i want to preselect in the first screen and then only use which i have selected. Also if anyone can help me, i want to know hoy to do the transition to another class (screen) when the button "Next" is pressed. I know this question are pretty simple but I'm new in kivy programming and some concepts are pretty difficult. Thanks.
What you want is accessing variables in other classes. Sometimes this can be annoying and you can do it either hard way with all __init__() and stuff, or... a simplier way comes along: it's get_running_app().
You can create a dictionary or something else, where you can store any value your other classes need to access. It's similar to using globals and it costs you less lines of code. For example in your case you could use a dictionary(or nested dictionaries, json, ...) to store for example 'checkboxes':'<names of checked ones>' and in each init you can loop over these values to make checkboxes active
Basically all you need is a = App.get_running_app() somewhere and something to access in main - App - class.
Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string('''
<Root>:
MainScreen:
name: 'main'
AnotherScreen:
name: 'another'
<MainScreen>:
BoxLayout:
Button:
text: 'next screen'
on_release: root.parent.current='another'
Button:
text: 'ping!'
on_release: root.ping()
<AnotherScreen>:
BoxLayout:
Button:
text: 'previous screen'
on_release: root.parent.current='main'
Button:
text: 'ping!'
on_release: root.ping()
''')
class MainScreen(Screen):
def __init__(self, **kw):
super(MainScreen, self).__init__(**kw)
self.a = App.get_running_app()
def ping(self):
print self.a.big_dict['hi']
class AnotherScreen(Screen):
def ping(self):
b = App.get_running_app()
print b.big_dict['hi']
class Root(ScreenManager):
pass
class SimpleKivy(App):
big_dict={'hi':'hi there!'}
def build(self):
return Root()
SimpleKivy().run()
You can see there's no need to call __init__(), no need to write more lines of code if you really don't need to.

How do I set widget attributes by calling a function in Kivy Python?

Suppose I have a ThemeManager object as a class attribute in my RootWidget like so:
class RootWidget(Widget):
theme = ThemeManager()
The ThemeManager defines a function that returns a hex color.
class ThemeManager:
def get_color(self):
return '#ffffffff'
Let's say I create a Button in my RootWidget using a kv file. How would I be able to call the ThemeManager functions from the kv file? Here's an example that doesn't work:
import kivy
kivy.require('1.9.0')
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.lang import Builder
class ThemeManager:
def get_color(self):
return '#ffffffff'
class RootWidget(Widget):
theme = ThemeManager()
my_kv = Builder.load_string("""
#: import get_color_from_hex kivy.utils.get_color_from_hex
RootWidget:
Button:
color: get_color_from_hex(app.root.theme.get_color())
text: "Test"
""")
class TestApp(App):
def build(self):
return my_kv
if __name__ == '__main__':
TestApp().run()
Since your question is already answered, here's a stab at the explanation, it's actually pretty simple (I think).
app.root is None at the point where your Button is trying to read the function. Because the order of things is (loosely):-
RootWidget created
Once it and all it's children are done (init completed), the object gets passed to the line in build()
app.root is only set on the call to TestApp.run()
As to why 3. happens, the init method in app.py initializes self.root as None. It can then be set by load_kv (loads a kv with the same name as this app) or by run (which is what happens most of the time).
So you can call app.root in your on_press events (because these only happen in response to user interaction, when the app is fully created) but not in one-off widget initialization events.
Interestingly enough, root is not defined as an ObjectProperty in app.py, which means you can't bind to changes in it like you can with, say, the title and icon. Not sure if it'd ever change though, so this is probably moot.

Python kivy text input

I'm a newbie at python, and now doing a dictionary with kivy. Issue is when I type text, it's not working. Below there I just want to check if it's working or not, so I put some popup, and if input text is 'a' then print true. It's just checking it's working or not, hope you guys help me, thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.bubble import Bubble
class CustomPopup(Popup):
pass
class Diction(GridLayout):
def __init__(self, **kwargs):
super(Diction, self).__init__(**kwargs)
self.cols=2
self.add_widget(Label(text="Search"))
self.search=TextInput(multiline=False)
self.add_widget(self.search)
if self.search=='A':
print 'True'
else:
print 'False'
self.add_widget(Button(text="click",on_press=self.show_popup))
def show_popup(self, b):
p = CustomPopup()
p.open()
class MyApp(App):
def build(self):
return LoginScreen()
if __name__=="__main__":
MyApp().run()
There are two reasons why is not working:
The if should be in the method that handles the events, i.e. show_popup
You should compare the text in the Label, not the Label itself. Instead of self.search=='A', you should use self.search.text=='A'
Here is the corrected __init__ and show_popup code:
class Diction(GridLayout):
def __init__(self, **kwargs):
super(Diction, self).__init__(**kwargs)
self.cols=2
self.add_widget(Label(text="Search"))
self.search=TextInput(multiline=False)
self.add_widget(self.search)
self.add_widget(Button(text="click",on_press=self.show_popup))
def show_popup(self, b):
if self.search.text=='A':
print 'True'
else:
print 'False'
p = CustomPopup()
p.open()
An alternative approach using the Kivy Language
The Kivy Language could help you to have a cleaner code. Your code could look like this:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
# DON'T forget to import Label!!!
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.lang import Builder
Builder.load_string("""
<CustomPopup#Popup>:
title: "My Custom Poput"
<Diction#GridLayout>:
cols: 2
search: id_search
Label:
text: "Search"
TextInput:
id: id_search
Button:
text: "click"
on_press: root.show_popup(self)
""")
class CustomPopup(Popup):
pass
class Diction(GridLayout):
def show_popup(self, b):
if self.search.text=='A':
print 'True'
else:
print 'False'
# You can send any content to the popup with the content attribute
CustomPopup(content=Label(text=self.search.text)).open()
class MyApp(App):
def build(self):
return Diction()
It helps to keep the logic separated from the interface. You can even keep in separate files if you use the load_file function instead of the load_string.

Categories

Resources