Suppose I have a class with three fields, a, b and sum. The third one should always equal to the sum of a and b.
In pure Python, this would have been implemented simply as:
class MyClass(object):
def __init__(self, a, b):
self.a = a
self.b = b
#property
def sum(self):
return self.a + self.b
However in Kivy, we are encouraged to use the Property descriptors of the framework. Before including sum, the class would be:
class MyClass(Widget):
a = NumericProperty()
b = NumericProperty()
def __init__(self, a, b, **kwargs):
super(MyClass, self).__init__(**kwargs)
self.a = a
self.b = b
However, how should I implement the sum property? I can use a good ol' #property. But shouldn't I instead use some sort of Kivy Property object?
What would be the best practice in Kivy to achieve this?
In general, properties are here to help you display contents of class fields on widgets. You can use them, or not.
You can use the #property, and create update_sum(..) method in some widget to rewrite a new sum to a label, on a button press.
Or you can do this automatically by creating an alias property, updating the label each time the sum changes, so you don't have to click any buttons to update it.
main.py:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import NumericProperty, AliasProperty
class RootBox(BoxLayout):
a = NumericProperty()
b = NumericProperty()
def get_sum(self):
return float(self.a + self.b)
def set_sum(self, value):
self.sum = value
sum = AliasProperty(get_sum, set_sum, bind=['a', 'b'])
def on_sum(self, obj, value):
self.ids.sum_label.text = str(value)
class Test(App):
pass
Test().run()
test.kv:
RootBox:
orientation: 'vertical'
Label:
id: sum_label
BoxLayout:
orientation: 'vertical'
TextInput:
on_text: root.a = float(self.text) if self.text else 0
TextInput:
on_text: root.b = float(self.text) if self.text else 0
Related
in Animation class, how to set d = 0.4, t = 'in_out_quad' by default? Can I use subclass for that?
from kivy.app import App
from kivy.lang import Builder
from kivy.animation import Animation
KV = """
Label
text: '123'
on_touch_down: app.test()
"""
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
def test(self):
a = Animation(x = 500, d = .2, t = 'in_out_quad')
a.start(self.root)
MyApp().run()
Tried something like this (with no success):
from kivy.app import App
from kivy.lang import Builder
from kivy.animation import Animation
KV = """
Label
text: '123'
on_touch_down: app.test()
<MyAnim>:
d: .2
t: 'in_out_quad'
"""
class MyAnim(Animation):
pass
class MyApp(App):
def build(self):
self.root = Builder.load_string(KV)
def test(self):
a = MyAnim(x = 500)
a.start(self.root)
MyApp().run()
I just want not to write the values of the arguments d and t many times if they are the same in my project
I haven't tested this, but I think you could just put those defaults in your class definition:
class MyAnim(Animation):
def __init__(self, **kw):
super(MyAnim, self).__init__(d = .2, t = 'in_out_quad', **kw)
Of course, if you use MyAnim and specify another t= or d=, you will get an error.
That error can be avoided by only adding the default values if they are not already specified:
class MyAnim(Animation):
def __init__(self, **kw):
if 'd' not in kw:
kw['d'] = 0.2
if 't' not in kw:
kw['t'] = 'in_out_quad'
super(MyAnim, self).__init__(**kw)
Consider following code. I would like to update multiple widget instances when prefix changes. As it is the same for all the instances it seems efficient to store/update it only once on class level (so that when instance does not have its own self.prefix, it will automatically refer to class level prefix attribute)
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
import random
kivy_lang = '''
<MainWidget>:
Button:
id: my_button
text: 'increase indice'
<MyLabel>:
on_prefix: self.text = self.prefix +':'+ self.indice
on_indice: self.text = self.prefix +':'+ self.indice
'''
class MyLabel(Label):
prefix = StringProperty('1')
indice = StringProperty('0')
pass
class MainWidget(BoxLayout):
def __init__(self, **kwargs):
super(MainWidget, self).__init__(**kwargs)
self.my_label1 = MyLabel()
self.my_label2 = MyLabel()
self.add_widget(self.my_label1)
self.add_widget(self.my_label2)
self.ids.my_button.bind(on_press=self.my_method)
def my_method(self,*args,**kwargs):
MyLabel.prefix = str(random.randint(0,9))
self.my_label1.indice = str(int(self.my_label1.indice) + 1)
# my_label2 would also be updated if its 'indice' got changed as below
# self.my_label2.indice = str(int(self.my_label2.indice) + 2)
class MyApp(App):
def build(self):
Builder.load_string(kivy_lang)
return MainWidget()
if __name__ == '__main__':
MyApp().run()
As from the python side this seems right, from Kivy side it looks like kivy has problem recognising when prefix got changed (my_label1 only gets updated because indice was also updated and on_indice is triggered).
Is there a way to get 'class level Property' prefix change to trigger on_prefix ?
I don't think this is possible directly, but you could mimic that functionality with AliasProperty and another property stored, say, on App. As long as the instance of MyLabel hasn't changed prefix, the value set for App is used (and automatically updated). Once prefix is set on an instance, _my_prefix is not None, and will be used to retrieve the value for prefix.
Change the <MyLabel> rule to
<MyLabel>:
_prefix: app.prefix
text: self.prefix +':'+ self.indice
And change the python code to
class MyLabel(Label):
indice = StringProperty('0')
_prefix = StringProperty('')
_my_prefix = StringProperty(None)
def get_prefix(self):
if self._my_prefix is None:
return self._prefix
else:
return self._my_prefix
def set_prefix(self, value):
self._my_prefix = value
prefix = AliasProperty(get_prefix, set_prefix, bind=('_prefix', '_my_prefix'))
[...]
def my_method(self,*args,**kwargs):
App.get_running_app().prefix = str(random.randint(0,9))
self.my_label1.indice = str(int(self.my_label1.indice) + 1)
if int(self.my_label1.indice) == 2:
self.my_label2.prefix = 'changed'
[...]
class MyApp(App):
prefix = StringProperty('1')
Essentially, I have a grid with squares, and I keep track of which squares are occupied with a BooleanProperty on each square. Here's a simplified version of all the places in my code I declare the "occupied" property:
class Board(GridLayout):
def __init__(self):
super().__init__()
self.cols = 4
self.grid = []
self.create_slots()
def create_slots(self):
for i in range(10):
self.grid.append([])
for j in range(4):
temp = Square(i,j, "sideboard")
self.grid[i].append(temp)
self.add_widget(temp)
temp.bind(on_occupied = self.do_a_thing)
def do_a_thing(self):
for square in self.children:
#do a thing
class Square(Button):
def __init__(self, row, col, type):
self.row = row
self.col = col
self.id = str(self.row) + "," + str(self.col)
self.type = type
self.occupied = BooleanProperty(False)
super().__init__()
My goal is to bind the "do_a_thing" method to be called each time the value of the square's "occupied" property changes. Because the Square class is used elsewhere in my app, I don't want to set the callback for on_occupied in the kivy language, and I was hoping to avoid creating a Square sub-class just to change the one binding.
When I run my code, it throws no errors, and I've verified that the "occupied" property does actually change. But the "do_a_thing" method never gets fired. Can anyone tell me what I'm doing wrong?
Note that for a property my_property, the change event is called my_property as well. The callback receives two arguments: instance that fired the event, and new value of the property, as shown in the docs. Also, if the class has a method called on_propertyname, this will be called as well.
Here is a self-contained example that works for me:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.properties import BooleanProperty
class Board(GridLayout):
def __init__(self, **kwargs):
super(Board, self).__init__(**kwargs)
for i in range(10):
self.add_widget(Square())
for square in self.children:
print square
square.bind(occupied=self.do_a_thing)
def do_a_thing(self, *args):
print "hello from {}, new state: {}".format(*args)
for square in self.children:
pass
#do a thing
class Square(Button):
occupied = BooleanProperty(False)
def on_occupied(self, *args):
print "Callback defined in class: from {} state {}".format(*args)
class mApp(App):
def build(self):
return Builder.load_string("""
Board:
cols: 4
rows: 3
<Square>:
on_press: self.occupied = ~self.occupied
""")
mApp().run()
Well, I'm having trouble in a specific part of my code. It uses kivy, however I'm pretty sure there is a python solution. Here is the thing: I'll have a button, when pressed will take me to another screen, when it's pressed, calls method vai, that changes, or should change the string variable value that was created in init method. Afterwards, when another screen shows up, its button receives that CHANGED variable at text parameter. But the real issue is that by the time second screen appears, the button text does not change, remaining the value I set up on init once, not the changed value on vai method.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
class Principal(App):
def build(self):
return SM()
class SM(ScreenManager):
def __init__(self):
super(SM, self).__init__()
self.add_widget(Um())
self.current = 'TelaUm'
class Um(Screen):
def __init__(self):
super(Um, self).__init__()
self.name = 'TelaUm'
self.add_widget(UmC())
class UmC(FloatLayout):
def __init__(self):
super(UmC, self).__init__()
self.btnSelecionado = 'qualquer_merda'
self.btn = Button(id = 'Bosta!', text = 'Bosta!', pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None))
self.btn.bind(on_press = self.vai)
self.add_widget(self.btn)
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
class Dois(Screen):
def __init__(self):
super(Dois, self).__init__()
self.name = 'TelaDois'
self.add_widget(DoisC())
class DoisC(UmC, FloatLayout):
def __init__(self):
super(DoisC, self).__init__()
self.btn2 = Button(text = self.btnSelecionado, pos_hint = { 'center_x' : .5, 'center_y' : .5 }, size_hint = (None, None) )
self.add_widget(self.btn2)
Principal().run()
I don't completely understand what you are trying to do. But it looks to me like the second screen is a new instance of UmC and therefore has its own value of btnSelecionado. So inevitably this new instance has the value from init since it's only the old instance that has been changed.
def vai(self, instance):
self.parent.parent.add_widget(Dois())
self.parent.parent.current = 'TelaDois'
self.btnSelecionado = instance.id
Line 2 creates a new instance and line 4 sets the value in the old instance.
Let's say I have 3 classes: a "woking class" where stuff takes place, a label class and a class to contain them.
For example the label class could be a status bar showing the status of something going on the working class. I wish I could find a way to make the label self-update the value to show, since this value is a value of the working class being changed inside the latter.
Here I have an example code
Builder.load_string('''
<CustomLabel>
text: 'Value is {}'.format(root.value)
<WorkingClass>:
orientation: 'vertical'
Button:
text: 'Update'
on_release: root.update()
<MainLayout>
orientation: 'vertical'
''')
class CustomLabel(Label):
value = NumericProperty()
class WorkingClass(BoxLayout):
def __init__(self, *args, **kwargs):
super(WorkingClass, self).__init__(*args, **kwargs)
self.a = 5
def update(self):
self.a += 1
print(self.a)
class MainLayout(BoxLayout):
def __init__(self, *args, **kwargs):
super(MainLayout, self).__init__(*args, **kwargs)
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.add_widget(self.customLabel)
self.add_widget(self.workingClass)
class MyApp(App):
def build(self):
return MainLayout()
if __name__ == "__main__":
MyApp().run()
Is there a way of doing it with properties or whatever? Becouse I don't want to need to manually update (sommehow) the label each time I change the value. Anyway to achieve this?
You're updating a property on WorkingClass, but that doesn't update the value on CustomLabel since you did a direct assignment instead of binding it. But yes, you can use Propertys to make everything work automatically.
In WorkingClass:
class WorkingClass(BoxLayout):
a = NumericProperty()
def __init__(self, **kwargs): ...
This makes a into a Property which you can bind to.
Then in MainLayout's constructor:
self.workingClass = WorkingClass()
self.customLabel = CustomLabel(value=self.workingClass.a)
self.workingClass.bind(a=self.customLabel.setter('value'))
The last line says: "when the value of property a on self.workingClass changes, set the value property of self.customLabel to the same value"
Alternatively, you could just add the Property to WorkingClass above, then get rid of MainLayout's constructor and use kv instead:
<MainLayout>:
orientation: 'vertical'
WorkingClass:
id: working_class
CustomLabel:
value: working_class.a # assigning one property to another in kv automatically binds