kivy NumericProperty to StringProperty - python

I tried to make my own Coockie Clicker in kivy, but with cristmas coockies.
I created an Image of an Coockie, you can click on and an Label that shows you how many times you have clicked.
The Label required an String, so I tried to convert the numeric property into an string, but it did not work, because I got the error message:
<kivy.properties.NumericProperty object at 0xa6e32cc>
This is the part of the code, where I suspect the error:
class Kecks(Widget):
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count))
Here is the rest of the code:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.core.window import Window
from kivy.clock import Clock
from kivy.animation import Animation
from kivy.core.text.markup import *
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
Builder.load_string('''
<Root>:
Kecks:
pos: 300, 300
<Kecks>:
Image:
pos: root.pos
id: my_image
source: 'piernik.png'
Label:
id: my_Label
font_size: 50
text: root.txt
center_x: root.width / 4
''')
class Root(FloatLayout):
pass
class Kecks(Widget):
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count))
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.count += self.amount
print self.txt
class app(App):
def build(self):
Window.clearcolor = (10, 0, 0, 1)
return Root()
if __name__ == "__main__":
app().run()

That's not an error message, that's a string identifying a property instance. Do you really mean you got an error, or is that what's printed by your program? I guess the latter, because...
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty(str(count))
To understand this, you need to know a little about how properties work - you declare them at the class level, not within a method, so they are attributes of the class (which are inherited as attributes of individual instances). One effect of this is that all instances of the class share the same property objects.
When you access them within a method, e.g. with self.count, you get an instance-specific result that behaves like a normal non-property attribute, even though it's really a property object, because the property internally takes care of returning the right thing (I think it's right to say it's a descriptor).
What's happening here is that you're accessing the result at class level, when it doesn't have any special behaviour - you asked for str(count) and that's what you got, the string identifying the property object.
Probably the correct pythonic way to resolve this is
class Kecks(Widget):
count = NumericProperty(0)
amount = NumericProperty(1)
txt = StringProperty()
def __init__(self, *args, **kwargs):
super(Kecks, self).__init__(*args, **kwargs):
self.txt = str(self.count)
By setting the value in __init__, you get the correct instance-level behaviour. You can also do stuff like mess around with accessing count.defaultvalue to get its actual value, but this is probably a bad idea.
If you want txt to be bound to the value of count (automatically changing to track str(count)), you have to do more again, but I'm not clear if that's your intention so I won't go into it and you can check the doc anyway.

Related

How to observe a property inside a widget to change a readonly AliasProperty outside it?

This sample code
from kivy.app import App
from kivy.properties import AliasProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class SampleLayout(BoxLayout):
def get_something(self):
return self.sample_widget.width + 30
something = AliasProperty(get_something, bind=['sample_widget.width'], cache=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sample_widget = Button(on_release=self.sample_test)
self.add_widget(self.sample_widget)
def sample_test(self, *args):
print(self.something)
class MyApp(App):
def build(self):
return SampleLayout()
MyApp().run()
Returns this error
AttributeError: type object 'SampleLayout' has no attribute 'sample_widget.width'
bcs (I guess) bind is searching for an attribute of the instance and not an attribute of an attribute of the instance... Maybe with getattr() which also doesn't work if using dots in the variable name.
So, is there an API for such bind? or should I bind directly with self.sample_widget.bind(width=idontknow)?
If it is the last case, what is that function idontknow to call the getter of a (cached) readonly property?
Understand also that the code above is an example, I need this functionality for something more complex. I cannot just put print(self.sample_widget.width + 30) under sample_test to get it working.
Thanks a lot.
Answering myself :)
I don't know if this is the better approach, but works.
sample_width = NumericProperty() # an intermediate
...
something = AliasProperty(get_something, bind=['sample_width'], cache=True)
...
# sync the intermediate with the wanted var
self.sample_widget.bind(width=self.setter('sample_width'))
so the example code would be like this:
from kivy.app import App
from kivy.properties import AliasProperty, NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
class SampleLayout(BoxLayout):
def get_something(self):
return self.sample_widget.width + 30
sample_width = NumericProperty() # an intermediate
something = AliasProperty(get_something, bind=['sample_width'], cache=True)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.sample_widget = Button(on_release=self.sample_test)
self.add_widget(self.sample_widget)
# sync the intermediate with the wanted var
self.sample_widget.bind(width=self.setter('sample_width'))
def sample_test(self, *args):
print(self.something)
class MyApp(App):
def build(self):
return SampleLayout()
MyApp().run()

How to position kivy widgets in floatlayout using pos_hint?

I'm stuck trying to position widgets in kivy in a FloatLayout using pos_hint.
If the label exists from the beginning, e.g. if I can define the pos_hint in the .kv file, everything works as expected. However, I'm trying to create buttons later on. Using this .kv file (named layouttest.kv):
<NewButton#Button>:
size_hint: (0.1, 0.1)
<BasicFloatLayout#FloatLayout>:
Button:
size_hint: (0.4, 0.2)
pos_hint: {'x': 0.0, 'top': 1.0}
text: 'create Button'
on_release: self.parent.create_Button()
and this python code, I am trying to position newly created blank buttons at a random y-position ranging from 0%-100% of the size of my BasicFloatLayout, and at a random x-position ranging from 0-200px.
If I press the button once, everything behaves as expected. On a second press, the first created button will change its y-position such that it is identical with the newly created button. On a third press, both old buttons will align with the newly created button and so on. The x-positioning will however remain as expected. Can someone please explain what I'm doing wrong here?
(Bonus points if you can help me moving the buttons using the update function and pos_hint)
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.properties import NumericProperty
from kivy.clock import Clock
import random
class BasicFloatLayout(FloatLayout):
timer = NumericProperty(0)
def update(self, *args):
self.timer += 1
for child in self.children:
try: child.update()
except AttributeError:
pass
def create_Button(self):
button = NewButton( (random.random(), random.random()) )
self.add_widget(button)
class NewButton(Button):
def __init__(self, pos, **kwargs):
super(NewButton, self).__init__(**kwargs)
self.pos[0] = 200*pos[0]
self.pos_hint['y'] = pos[1]
class layouttestApp(App):
def build(self):
GUI = BasicFloatLayout()
Clock.schedule_interval(GUI.update, 1/30.)
return GUI
if __name__ == "__main__":
layouttestApp().run()
First, make pos_hint: {'x': 0.0, 'y': 0.5}(because it's hard to get 2 different things work, if you are using x, use y instead of Top, if you are using Top, then use Bottom insead of x)
Second, instead of giving on_release: self.parent.create_Button() in the kv file, do this: on_release: root.create_Button()
Third, you have only assigned for the y value , you should also assign for the x value, the line is self.pos_hint['y'] = pos[1] inside the NewButton class.
But you can make it more simple by doing this:
#from your first class......
def create_Button(self):
button = NewButton(self.pos_hint{'x' : random.random(), 'y' : random.random()}
self.add_widget(button)
class NewButton(Button):
def __init__(self, *kwargs):
pass
Hope this makes some kind of sense, and you can modify it more.
(Note: I haven't wrote the begining part of your main class, I am lazy ;-p)

Getting TypeError: object.__init__() takes no parameters Kivy

I developed an app with python and faced this problem:
TypeError: object.__init__() takes no parameters
My files are the following :
Main.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import Screen
from kivy.uix.button import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from clinics.clinicsbanner import ClinicBanner
import psycopg2
class HomeScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class InfoScreen(Screen):
pass
class ResultScreen(Screen):
pass
class ImageButton(ButtonBehavior, Image):
pass
GUI = Builder.load_file('main.kv')
class MainApp(App):
def build(self):
return GUI
def on_start(self):
result_banner = self.root.ids['result_screen'].ids['result_banner']
con = psycopg2.connect(
host="localhost",
database="here is my db",
user="here is my user",
password="here is my password")
cur = con.cursor()
city = self.root.ids['home_screen'].ids.city.text
cur.execute("SELECT * FROM clinic WHERE city='%s'" %city)
rows = cur.fetchall()
for row in rows:
C = ClinicBanner(cities=row[1])
result_banner.add_widget(C)
con.commit()
cur.close()
con.close()
clinicbanner.py:
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class ClinicBanner(GridLayout):
rows = 1
def __init__(self, **kwargs):
super(ClinicBanner, self).__init__(**kwargs)
centre = FloatLayout()
centre_label = Label(text=kwargs['cities'], size_hint=(1, .2), pos_hint={"top": .2, "left": 1})
centre.add_widget(centre_label)
self.add_widget(centre)
I think it may be connected with database or clinicbanner.py file, in super function.
I believe that you can help me.
Thank you for your answers and help !
As inclement stated out correctly you pass an argument to the class that does not exist C = ClinicBanner(cities=row[1]). It is the cities argument. If you use it this way you better write your init method like this:
def __init__(self, cities, **kwargs):
super(ClinicBanner, self).__init__(**kwargs)
centre = FloatLayout() centre_label = Label(text=cities, size_hint=(1, .2), pos_hint={"top": .2, "left": 1})
I added cities to the init method as an argument and changed the labels text to cities (I assume cities is a str). This way it should work, but if you create a new instance of ClinicBanner you now always have to add a cities argument. If you dont want that you can change the init method to def __init__(self, cities="", **kwargs): to add an empty string as default. I hope, it now works for you.
The error means you're passing arguments to the superclass that it does not expect. In this case it's probably the cities argument, but I didn't look closely. Don't pass the argument to the parent because the parent doesn't have anything to do with it.

Kivy: is it possible to trigger events with class level (not instance) Properties?

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')

kivy remove_widget is not working

I wanted to make a kivy game with an stickman running around the screen, and as soon as you click on it, the stickman is removed.
I tried to remove the enemy widget by using Place.remove_widget(Enemy), but the Program crashed an I got this error message:
TypeError: unbound method remove_widget() must be called with Place instance as first argument (got WidgetMetaclass instance instead)
Here is my source code:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.clock import Clock
from kivy.animation import Animation
class Place(FloatLayout):
pass
class Enemy(Widget):
velocity = NumericProperty(1)
def __init__(self, **kwargs):
super(Enemy, self).__init__(**kwargs)
Clock.schedule_interval(self.Update, 1/60.)
def Update(self, *args):
self.x -= self.velocity
if self.x < 1:
self.velocity = 0
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
print 'es geht'
self.velocity = 0
Place.remove_widget(Enemy)
ROOT = Builder.load_string('''
Place:
Button:
text: 'Go Back'
size_hint: 0.3, 0.1
pos_hint: {"x": 0, 'y':0}
Enemy:
pos: 400, 100
<Enemy>:
Image:
pos: root.pos
id: myimage
source: 'enemy.png'
''')
class Caption(App):
def build(self):
return ROOT
if __name__ == '__main__':
Caption().run()
Place.remove_widget(Enemy)
This is the problem - you aren't trying to remove an instance of the Enemy class from an instance of the Place class, but instead trying to remove the actual class itself from the other. This is the difference between a = Place and a = Place() - the former is the instructions for how to make a Place, the latter is an actual individual Place instance.
In this case you could probably do self.parent.remove_widget(self); self.parent is the Place instance containing the Enemy instance.

Categories