Kivy: Self-updating label text - python

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

Related

Stuck with Class Function in Kivy

So I 've been creating this Kivy App, but this happened
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
'''conn = sqlite3.connect('first_db.db')
c = conn.cursor()
c.execute("SELECT name FROM sqlite_master WHERE type='table';")
screens = c.fetchall()
for i in range(len(screens)):
self.add_note()
c.close()'''
self.add_note()
def add_note(self, name = ""):
self.add_widget(NoteScreen(name = name))
print(self.parent)
my .kv file
MDNavigationLayout:
x: toolbar.height
size_hint_y: 1.0 - toolbar.height/root.height
MyScreenManager:
id: screen_manager
NoteScreen:
name: "Home"
When I run the app, instead of printing out the parent of MyScreenManager which is MDNavigationLayout, it printed out "None". I don't know how to fix it. Can anybody help me?
self.parent isn't available at the time of the __init__ method because the class is first instantiated, then added to its parent.
Per documentation:
The parent of a widget is set when the widget is added to another widget
The base Widget.__init_() doesn't set self.parent.
Since Kivy uses an observable property for parent the action can be added in on_parent
def on_parent(self, instance, value):
super(MyScreenManager, self).on_parent(instance, value)
self.add_note()

python - kivy: Call function from another class

I am currently developing a GUI with Python/ Kivy and have some issues when it comes to call a function from another class. I set up a screen, which includes a TextInput widget, that can be used to insert an E-Mail address. By clicking the Submit-Button, the function 'check_mail' is called, which checks the E-Mail using regular expressions and then either prints a text ('E-Mail not valid') or changes the screen (The E-Mail Address will later be transferred to a database, for now its fine to just change the screen after submitting). However, the function does print me the text, if the E-Mail format is not valid, but when it comes to call the change_screen function from the InsertData class, it is not working (AttributeError: 'NoneType' object has no attribute 'ids') If I call the change_screen function within the .kv file {on_release: app.change_screen('home_screen')}, it works fine. How can I access the change_screen function from my InsertData class?
main.py
class HomeScreen(Screen):
pass
class InsertData(Screen):
def check_mail(self):
addressToVerify = self.ids.email_main.text
match = re.match('^[_a-z0-9-]+(\.[_a-z0-9-]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$', addressToVerify)
if match == None:
print('Email not valid!')
else:
MainApp().change_screen('home_screen')
GUI = Builder.load_file("main.kv")
class MainApp(App):
def build(self):
return GUI
def change_screen(self, screen_name):
screen_manager = self.root.ids[
'screen_manager']
screen_manager.transition = CardTransition()
screen_manager.transition.direction = 'up'
screen_manager.transition.duration = .3
screen_manager.current = screen_name
MainApp().run()
insert_data.kv
<InsertData>:
FloatLayout:
canvas:
Rectangle:
size: self.size
pos: self.pos
source: "background/background_main.png"
GridLayout:
rows: 1
pos_hint: {"top": 1, "right": 1}
size_hint: 1, .8
TextInput:
id: email_main
hint_text: "E-Mail Address"
LabelButton:
text: "Submit"
on_release:
root.check_mail()
This is how you can call function x for class B
class A:
def __init__(self):
pass
def x(self):
print('x')
class B:
def __init__(self):
A().x()
Update Managed to solve the problem.
Looking at the error I noticed that every time, the function change_screen is executed (due to a not-valid E-Mail format), it will run the following line in change_screen:
screen_manager = self.root.ids['screen_manager']
The problem was, that self.root was referring to the InsertData class, which does not have the attributes. Since self.roots refers to the GUI in the MainApp class, I changed the line in the change_screen function as follows:
screen_manager = GUI.ids['screen_manager']
Now the function is referring to the fixed GUI instead of the self.root and is running without any problems.
I have some solution just to show how does it will work.
class A (self):
def some_method(self):
print("Print Here")
class B (self):
def some_more_method(self):
print("Let see here")
Details:
Let's say you want to use method/function from class B in class A.
'Add this line'
B.some_more_method(self)
It works for me.

Kivy: How set up item Drop-Down

I need to set up dynamically list in Drop-Down. For setting I want to use constructor of class DiscDropDown, which is decendant of class DropDown. (It means, that want to set up value for variable li_disc in class DiscDropDown). Instance of this class is not part of python code, because I use Builder.
How do I realize it in next code?
Builder.load_string('''
<Save_As>:
drop_down: drop_down
orientation:'vertical'
DiscDropDown:
id: drop_down
size_hint: None,None
height: 50
Button:
id: bt_delete_file
disabled: False
size_hint: 1,None
height: 250
text: 'Fictive'
''')
class DiscDropDown(DropDown):
def __init__(self, **kwarg):
super(DiscDropDown, self).__init__(**kwarg)
self.dropdown = DropDown()
# I wanted to set up value Button.text during creation Buttons
for name in li_disc:
btn = Button(text = name, size_hint_y=None, height=44)
btn.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(btn)
mainbutton = Button(text='Disc', size_hint=(None, None),size = (50,50))
mainbutton.bind(on_release = self.dropdown.open)
self.dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
self.add_widget(mainbutton)
class Save_As(BoxLayout):
# drop_down = ObjectProperty()
def __init__(self, **kwargs):
super(Save_As, self).__init__( **kwargs)
return
class ExplorerApp(App):
def build(self):
self.save_as = Save_As()
return self.save_as
if __name__ == '__main__':
ExplorerApp().run()
For solving my problem I used next process for setting value variable Button.text:
class Save_As(BoxLayout):
drop_down = ObjectProperty()
def __init__(self, **kwargs):
li_disc = ['aa','bb','cc']
super(Save_As, self).__init__( **kwargs)
# I set up Button.text here
for i in range(0,3):
self.children[1].dropdown.children[0].children[i].text = li_disc[i]
return
I think that my solution isn't ideal, cann you recommend me better process?

When to bind to attributes that populated with kv-file?

test.kv
<RootWidget>:
test: test
Label:
id: test
text: "some"
test.py
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
# 1:
print(self.test) # None
# 2:
def after_tick(*args):
print(self.test) # Label object
Clock.schedule_once(after_tick, 0)
super().__init__()
If I'll try to bind something to self.test directly inside __init__ (1), I would get AttributeError since kv rules aren't applied yet and self.test is None. Possible workaround would be to bind to attribute after first event loop iteration (2).
This solution seems to be duct tape. Is there any better way?
I know 3 other ways.
First, make the after_tick a method and execute it in init:
class RootWidget(Widget):
test = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.delayed_init()
#mainthread
def delayed_init(self):
print(self.text)
Second, use a built-in method that is always executed after the first loop frame:
class RootWidget(Widget):
test = ObjectProperty(None)
def add_widget(self, widget, index=0):
super().add_widget(widget, index)
if widget.__class__.__name__ == 'Label':
widget.text = self.test.text
This is useful if you have a lot of same type widgets to bind.
Third, use a property's on_x callback:
class RootWidget(Widget):
test = ObjectProperty(None)
def on_test(self, *args):
print(self.test)

How can I create lazy properties in Kivy?

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

Categories