Dynamically adding properties with kivy discouraged – why? - python

The class kivy.event.EventDispatcher has a method called apply_property() which allows adding a property to a class at runtime.
The docs contain a warning:
This method is not recommended for common usage because you
should declare the properties in your class instead of using this
method.
It is not clear to me why this usage is discouraged, and declaration in class preferred. Can anyone enlighten me?

apply_property() is used to add property to a single class instance but has a side effect of polluting properties of other instances as well. Consider following code:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print('np' in self.properties())
''')
class MyButton(Button):
pass
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
mb1.apply_property(np=NumericProperty(100))
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
As you can see, even though I used apply_property on mb1 instance, mb2 reports that it has such property as well. This is because properties are added to the class, not the instance. However, only mb1 has an actual value:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print(self.np)
''')
class MyButton(Button):
pass
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
mb1.apply_property(np=NumericProperty(100))
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
Trying to get the value of np property from mb2 instance yields an AttributeError. A safe alternative:
#!/usr/bin/kivy
import kivy
kivy.require('1.9.1')
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
Builder.load_string('''
<MyButton>:
on_press: print(self.np)
''')
class MyButton(Button):
np=NumericProperty(100)
class MainApp(App):
def build(self):
mb1 = MyButton(text="mb1")
mb2 = MyButton(text="mb2")
layout = BoxLayout()
layout.add_widget(mb1)
layout.add_widget(mb2)
return layout
if __name__ == '__main__':
MainApp().run()
Source: this issue.

In addition to #Nykakin's example, apply_property also interferes with inheritance in a way 'regular' class variables don't, which might therefore lead to unexpected behaviour. Here, MyButton2 is a subclass of MyButton1 and instances get attributes pp and oo. While assigning a class variable oo in MyButton1 does not interfere with instances of MyButton2, adding a Property with apply_property to an instance of MyButton1 suddenly makes pp inaccessible in MyButton2:
from kivy.properties import NumericProperty
from kivy.app import App
from kivy.uix.button import Button
from kivy.lang import Builder
class MyButton1(Button):
pass
class MyButton2(MyButton1):
def __init__(self, **kwargs):
super(MyButton2, self).__init__(**kwargs)
self.pp = 11
self.oo = 42
bl = Builder.load_string("""BoxLayout:
MyButton1:
id: hb01
text: "MyButton1"
MyButton2:
id: hb02
text: "MyButton2"
""")
def on_press(sender):
on_press.counter += 1
print "in on_press:"
try:
print " Now pp == {}".format(sender.pp)
except:
print " no attribute 'pp'"
prop = sender.property('pp', quiet=True)
sender.apply_property(pp=NumericProperty(on_press.counter))
print " Applying property ... Now it's {}".format(sender.pp)
try:
print " Now oo == {}".format(sender.oo)
except:
print " no attribute 'oo'"
sender.__class__.oo = on_press.counter + 17
on_press.counter = 0
def on_press2(sender):
print "on_press2:"
try:
print " Now pp == {}".format(sender.pp)
except:
print " no attribute 'pp'"
try:
print " Now oo == {}".format(sender.oo)
except:
print " no attribute 'oo'"
class T01App(App):
def build(self):
return bl
def on_start(self):
self.root.ids.hb01.bind(on_press=on_press)
self.root.ids.hb02.bind(on_press=on_press2)
if __name__ == '__main__':
T01App().run()

Related

Kivy Creating screens as variables

I have a question about kivy. Is it possible to create screens as variables? Lets say I want to create a new screen with a button inside the kivy application. Is it possible to do that? If so how could you do it??
You just have to create a Screen object that you want to create and add it to the ScreenManager:
screen = Your_Screen(name="some_name")
your_screen_manager.add_widget(screen)
Example:
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
class TestScreen(Screen):
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
layout = BoxLayout(orientation="vertical")
self.add_widget(layout)
layout.add_widget(Label(text=self.name))
button = Button(text="{}: Add Screen".format(self.name))
layout.add_widget(button)
button.bind(on_press=self.add_screen)
def add_screen(self, *args):
n = len(self.manager.screen_names)
screen = TestScreen(name="screen {}".format(n))
self.manager.add_widget(screen)
self.manager.current = screen.name
# Create the screen manager
sm = ScreenManager()
sm.add_widget(TestScreen(name='screen 0'))
class TestApp(App):
def build(self):
return sm
if __name__ == '__main__':
TestApp().run()

How to bind a widget to a value from Kivy App

I have the following Kivy app and I'm trying to change the text of a Label based on another widget's variable.
I mean, if the variable testing of the class TestApp changes, I want also the value of the variable text of the class TestLabel to change.
To do so, I've created a BooleanProperty in the TestLabel class that points to the testing variable of the TestApp class. The problem is that this callback is never executed despite being changing it each time I press the button.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import BooleanProperty
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
rows: 2
TestButton:
text: 'Change value'
on_release: self.change_value()
TestLabel:
''')
class TestLabel(Label):
testing = BooleanProperty()
def __init__(self, **kwargs):
super(TestLabel, self).__init__(**kwargs)
self.app = App.get_running_app()
self.testing = self.app.testing
self.bind(testing=self.changed_value)
def changed_value(self, _instance, newvalue):
self.text = str(newvalue)
class TestButton(Button):
def __init__(self, **kwargs):
super(TestButton, self).__init__(**kwargs)
self.app = App.get_running_app()
def change_value(self):
self.app.testing = not self.app.testing
class MainApp(BoxLayout):
pass
class TestApp(App):
testing = BooleanProperty(False)
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()
It is not necessary to create a testing property in TestLabel since when you do: self.bind(testing = self.changed_value) you are connecting the testing of TestLabel and not the testing of TestApp , so as it never changes testing after the bind then it never gets call the callback.
The bind has to be done using the object that has that property, and in your case the testing belongs to the App, so you must the App.
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.properties import BooleanProperty
Builder.load_string('''
<MainApp>:
orientation: 'horizontal'
rows: 2
TestButton:
text: 'Change value'
on_release: self.change_value()
TestLabel:
''')
class TestLabel(Label):
def __init__(self, **kwargs):
super(TestLabel, self).__init__(**kwargs)
self.app = App.get_running_app()
self.app.bind(testing=self.changed_value)
def changed_value(self, _instance, newvalue):
self.text = str(newvalue)
class TestButton(Button):
def __init__(self, **kwargs):
super(TestButton, self).__init__(**kwargs)
self.app = App.get_running_app()
def change_value(self):
self.app.testing = not self.app.testing
class MainApp(BoxLayout):
pass
class TestApp(App):
testing = BooleanProperty(False)
def build(self):
return MainApp()
if __name__ == '__main__':
TestApp().run()

Setting property value at kv file doesn't affect the value of same variable at py simple code

By instantiating a custom GridLayout in the kv file, I set a value for the mes property, an integer. The value is not passed to the python class variable. I have declared the same property as NumericProperty, but when I run the code, the print statement shows up the default value set at py file rather than the value set at kv file. I can't figure why this happens.
The main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.properties import NumericProperty
class FichaSemApp(App):
pass
class ShowCal(GridLayout):
mes = NumericProperty()
def __init__(self,**kwargs):
super().__init__(**kwargs)
print('VALUE OF mes: ',self.mes)
FichaSemApp().run()
I'm using the init method because I need some computation for populating the grid.
The fichasem.kv
ShowCal:
mes: 2
cols: 7
rows: 7
You might want to do the following:
Program
import kivy
kivy.require('1.10.0')
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.properties import NumericProperty
class ShowCal(GridLayout):
mes = NumericProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
print('VALUE OF mes: ', self.mes)
class FichaSemApp(App):
def build(self):
return ShowCal()
if __name__ == '__main__':
FichaSemApp().run()
Kivy File
#:kivy 1.10.0
<ShowCal>:
mes: 2
cols: 7
rows: 7
Output

Trying to change a label text in Kivy, and it never changes

Here is a basic sample that I am working with. The label displays as I would expect, but the text never changes, even though I do see the print statements in the console showing that the Clock.schedule_interval is ticking away. Any thoughts as to what have gone wrong???
Thank you and Happy New Year!
First the .kvlang file
<Demo>:
button_text: my_button
BoxLayout:
Label:
id: my_button
text: 'Initial Text!'
And my Python.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
import random
class Demo(BoxLayout):
button_text = ObjectProperty
def change_text(self, dt):
self.button_text.text = str(random.randint(1, 10))
print('Should have changed button text to {}'.format(self.button_text.text))
def start(self):
Clock.schedule_interval(self.change_text, 10)
class TutorialApp(App):
def build(self):
foo = Demo()
foo.start()
return Demo()
if __name__ == "__main__":
TutorialApp().run()
You are missing parenthesis
button_text = ObjectProperty
change to
button_text = ObjectProperty(None) # Ha! :)
Also you should return foo and not create another Demo
def build(self):
foo = Demo()
foo.start()
#return Demo() change to...
return foo
Since the later Demo will not be updated...

widget won't display until screen size changed

I want to make a game and I have chosen Kivy for my GUI, as I have written my backend in Python. I am currently using runTouchApp(appwindow) to run the application, where appwindow is a FloatLayout() object. The way I update my screen is that I run appwindow.clear_widgets() and then appwindow.add_widget(new_screen) where new_screen is the layout object which contains all other widgets.
It worked fine till now, but for some reason I can't understand the widget I add gets loaded properly (according to the cmd running in the background) but won't display till I change the screen size.
Here is a sample code:
import kivy
kivy.require('1.1.1')
from kivy.base import runTouchApp
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
appwindow = FloatLayout()
class MenuScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
menu_items = []
for i in range (0,4):
button = Button(text = 'Menu item no:'+str(i))
button.bind(state = checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size = bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1,1
return floater
class SecondScreen():
def build(self):
bg = Image(source = 'Images/bg/bg.jpg',allow_stretch= True,keep_ratio= False)
box = BoxLayout(orientation = 'vertical')
box.add_widget(bg)
return box
def checkMenuPress(button,*args):
if button.state == 'normal':
sc = SecondScreen().build()
appwindow.clear_widgets()
appwindow.add_widget(sc)
if __name__ == '__main__':
menuscreen = MenuScreen().build()
appwindow.add_widget(menuscreen)
runTouchApp(appwindow)
Okay, there are a few things wrong here. To create an app, you should subclass kivy.app.App and have it's build method return the "root" widget. Only this class needs build method: any others are redundant.
Once you have a root widget, it's just a matter of inserting and removing widgets. This is how I would approach it.
import kivy
kivy.require('1.1.1')
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
class MenuScreen(App):
floater = None
def build(self):
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.checkMenuPress)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
self.floater = FloatLayout(size=bg.size)
self.floater.add_widget(bg)
self.floater.add_widget(box)
self.floater.size_hint = 1, 1
return self.floater
def checkMenuPress(self, button,*args):
if button.state == 'normal':
self.floater.clear_widgets()
self.floater.add_widget(SecondScreen())
class SecondScreen(FloatLayout):
def __init__(self, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
if __name__ == '__main__':
MenuScreen().run()
Okay, so here's a quick rework of the example using Screens. It's difficult without knowing what you are trying to do, so if this does not help, try writing summary of the behavior you are looking for.
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.image import Image
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
"""
The Screen containing the menu
"""
def __init__(self, sm, **kwargs):
super(MenuScreen, self).__init__(**kwargs)
self.sm = sm # Store a reference to the screen manager
bg = Image(source='Images/bg/bg.jpg',
allow_stretch= True,
keep_ratio= False)
box = BoxLayout(orientation='vertical')
menu_items = []
for i in range(0, 4):
button = Button(text='Menu item no:'+str(i))
button.bind(state=self.check_menu_press)
menu_items.append(button)
for button in menu_items:
box.add_widget(button)
floater = FloatLayout(size=bg.size)
floater.add_widget(bg)
floater.add_widget(box)
floater.size_hint = 1, 1
self.add_widget(floater)
def check_menu_press(self, button, *args):
if button.state == 'normal':
self.sm.current = "second"
class SecondScreen(Screen):
def __init__(self, sm, **kwargs):
super(SecondScreen, self).__init__(**kwargs)
self.add_widget(Image(source='Images/bg/bg.jpg',
allow_stretch=True,
keep_ratio=False))
self.sm = sm # Store a reference to the screen manager
def on_touch_down(self, touch):
self.sm.current = "menu"
class MainApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(MenuScreen(sm, name="menu"))
sm.add_widget(SecondScreen(sm, name="second"))
return sm
if __name__ == '__main__':
MainApp().run()
looks like i have found a source of the crashing problem. I had used multi-threading for a server client interaction. i had been using two extra threads, one for the server and another for the client. If i try to integrate the client thread with the GUI it raises another problem. even if the socket.recv() is after the ScreenManager.current = "newscreen" line, the app's screen does not change. it freezes in the way i left it. it resumes only after it gets a message from the server.

Categories

Resources