Custom Attributes in KV-File

718 views
Skip to first unread message

Nobody999

unread,
May 16, 2021, 4:05:14 PM5/16/21
to Kivy users support
I think I'm on the hose ...

I tried to establish a custom attribute for a Class inheriting from Label in a kivy file this way:

KV file:
...
MyLabel:
custom_attrib: "some text"
text: "just a label text"

My class definition is like this:

Python:
...
class MyLabel(Label):
     custom_attrib = StringProperty()

    def __init__(self, **kwargs):
        super(MyLabel, self).__init__(**kwargs)
        print(kwargs)
        ...

If I create an object from within python I can access kwargs and use it to assign the values to the class attributes.

label1 = MyLabel(custom_attrib = "some text", text = "just a label text")

kwargs is: {'custom_attrib': 'some text', 'text': 'just a label text'}

But I didn't manage to get the custom_attrib from the kv language file. Obviously the text attribute is automatically assigned to the object but the custom_attrib is not. If I try to print out the kvargs variable it is {'__no_builder': True}
What should I do to assign a custom attribute from within a kv file to a custom defined class attribute?

I searched the web for hours but didn't crack the nut.

Thanks for any help.

Elliot Garbus

unread,
May 16, 2021, 4:20:07 PM5/16/21
to kivy-...@googlegroups.com

Here is an example creating 2 custom attributes and using them in Python and in kv.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty

kv =
"""
<TwoLabels>:
    orientation: 'vertical'
    Label:
        text: root.text_1
    Label:
        text: root.text_2

BoxLayout:
    TwoLabels:
        text_1: 'This is Text 1 from kv'
        text_2: 'This is Text 2 from kv'

"""


class TwoLabels(BoxLayout):
    text_1 = StringProperty()
    text_2 = StringProperty()


class CustomAttribApp(App):
   
def build(self):
       
return Builder.load_string(kv)

   
def on_start(self):
        w = TwoLabels(
text_1='text 1 from Python', text_2='text 2 from python')
       
self.root.add_widget(w)


CustomAttribApp().run()

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/af64c08d-570a-4414-8d3d-5b4697fd8812n%40googlegroups.com.

 

Nobody999

unread,
May 17, 2021, 2:02:53 AM5/17/21
to Kivy users support
Thanks a lot.
I'll have to take a look at it later and then get back to you.
If I see it correctly, the properties are now assigned to the TwoLabels class, i.e. the BoxLayout and not the label itself. That irritates me a bit.

Elliot Garbus

unread,
May 17, 2021, 9:10:31 AM5/17/21
to kivy-...@googlegroups.com
You can add a property to a label, just as I added it to the TwoLabel class. 

Sent from my iPhone

On May 16, 2021, at 11:02 PM, Nobody999 <Andrea...@bleiguba.de> wrote:



Nobody999

unread,
May 18, 2021, 5:40:47 PM5/18/21
to Kivy users support
Now I tryed your code and it works well. The problem I encountered may be different.
I create objects of the MyLabel class (inherited from Label) on different screens to show up date or time. The format of the time/date string is stored in the customattribute 'text-format'. I like to create a Clock.schedule_interval on the MyLabel class to  update the time/date. To have scheduler for every MyLabel object I think it should be created in the __init__ method of MyLabel.
But trying this always ends up in errors.
It would be easyer to have just one scheduler created in the "on_start" methon of the App but how can this know how many MyLabel objects exist. It would have to go through all "MyLabels".

Here's my code. I put all the screen defs in a .kv file and use a screen manager. One screen shows the date, the other the time. A clock scheduled update method of MyLabel should update the time/date string (e.g. the text attribute of the MyLabel) according to the specific text-format-string (custom attribute) of the MyLabel instance. But as I said I didn't manage to get a init-Method containing a Clock.scheduler to work. Do you know a solution?

PYTHON (customattrib.py):

import time

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen


class ScreenMain(Screen):
    pass


class ScreenTwo(Screen):
    pass


class ScreenMngr(ScreenManager):
    pass


class MyLabel(Label):
    text_format = StringProperty()

#   def __init__(self): # init method gives a build error
#       Label.__init__(self)
#       Clock.schedule_interval(app.root.ids.timelabel.update, 1) # How can I add a Clock to update the time/date

def update(self, dt):
    self.text = time.strftime(self.text_format, time.localtime())


class CustomAttribApp(App):
    def build(self):
        return kv

def on_start(self):
    Window.clearcolor = (0.9, 0.9, 0.9, 1)
    #Clock.schedule_interval(app.root.ids.timelabel.update, 1) # possibly something like this with an id on the label?


if __name__ == '__main__':
    kv = Builder.load_file('customattrib.kv')
    app = CustomAttribApp()
    app.run()



.KV FILE (customattrib.kv) :

ScreenMngr:
    ScreenMain:
    ScreenTwo:

<ScreenMain>:
    name: "smain"
    BoxLayout:
        size: root.width, root.height
        orientation: 'vertical'
        Widget:
        Button:
            text: "Go to subscreen"
            font_size: 28
            size_hint: None, None
            size: 300, 100
            on_release:
                app.root.current = "stwo"
                root.manager.transition.direction = "left"
        MyLabel:
            text: '01.01.2000'
            text_format: '%d.%m.%y'

<ScreenTwo>:
    name: "stwo"
    BoxLayout:
        size: root.width, root.height
        orientation: 'vertical'
        Widget:
        Button:
            text: "Go back to mainscreen"
            size_hint: None, None
            size: 200, 100
            font_size: 28
            on_release:
                app.root.current = "smain"
                root.manager.transition.direction = "right"
        MyLabel:
            text: '00:00:00'
            text_format: '%H;%M:%S'

<MyLabel>:
    id: timelabel
    font_size: 18
    color: 0,0,0,1
    size_hint: 1, None
    height: 30
    text_size: self.width-20, self.height
    valign: 'middle'

Elliot Garbus

unread,
May 18, 2021, 7:45:03 PM5/18/21
to kivy-...@googlegroups.com

The code below is a fix for your current approach.  The __init__() method takes a **kwargs argument, and super() is used to call the __init__ for the parent.

 

I removed your build method.  Because the kv file has the same method name as app the kv file loads automagically.

 

If you wanted to have a single update method you could for example create a string property in app, and bind that value to the labels that you wanted to display.  For example:

MyApp(App):

    time_of_day = StringProperty()

    current_date = StringProperty()

 

In the lv code:

    Label:

        text: app.time_of_day

    Label:

        text: app.current_date

… and update these properties in your update method.

 

 

Your python code updated:

======

 

import time

from kivy.app import App
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen


class ScreenMain(Screen):
   
pass


class
ScreenTwo(Screen):
   
pass


class
ScreenMngr(ScreenManager):
   
pass


class
MyLabel
(Label):
    text_format = StringProperty()

   
def __init__(self, **kwargs): # init method gives a build error
       
super().__init__(**kwargs)
        Clock.schedule_interval(
self.update, 1) # How can I add a Clock to update the time/date

   
def update(self, dt):
       
self.text = time.strftime(self.text_format, time.localtime())


class CustomAttribApp(App):

   
def on_start(self):
        Window.clearcolor = (
0.9, 0.9, 0.9, 1)
       
#Clock.schedule_interval(app.root.ids.timelabel.update, 1) # possibly something like this with an id on the label?


CustomAttribApp().run()

Nobody999

unread,
May 19, 2021, 2:17:55 PM5/19/21
to Kivy users support
This all works great. Thank you very much - you helped me a lot.

I tried several times before with and without **kwargs in many varieties but always got that errors. I used something like: super(MyLabel, self).__init__(**kwargs) - don't know where I got this from but it obviously was nonsense.
To init the parent I now found Label.__init__(self, **kwargs) working as well - think it doesn't make a difference  to use super or not as long as I don't have multi inheritance and get into problems with that methode resolution order thing that I read about without really understanding it.

I thought about the two alternatives - holding the custom properties in MyLabel or in App.
Having them in App is much more flexibel as I can create as many total different strings as I want and easily assign them to different MyLabel objects.On the other side it collides with the principles of OOP. Actually I should encapsulate all the functionality of MyLabel in the class. Having the update routine, the string creation and the scheduler in App breaks that rule.
Keeping all of this in the MyLabel class may require a parameter that tells the update routine which of the different strings to pass to the MyLabel.text. Also it would create multiple clock schedulers. This may not be advantageous regarding performance. (Meanwhile I found this not to be an problem... find mor on that below.)
How do you think about that?

There's another little thing that caught my eye: If I have the attributes in the MyLabel class it takes a second until the label content is updates (because the scheduler is set to 1 second). To have it updated immediately at start I added "self.update(0)" to the init methode - but that doesn't work. Seems that the app loop is first started after the initialisation of both screens so update() has no effect within the __init__ methode. (Logically this problem does not appear when the scheduler is defined within App.)
A solution I found ist adding "Clock.schedule_once(self.update, 0)" .
Is there a more elegant way to call update() while initiating the MyLabel objects although App still is not running? Probably something like on_start() ?

class MyLabel(Label):
    text_format = StringProperty()

def __init__(self, **kwargs):
    super().__init__(**kwargs)
    Clock.schedule_interval(self.update, 1)
    self.update(0)                                # has no effect
    Clock.schedule_once(self.update, 0)           # fixes the problem

def update(self, dt):
    print(dt)
    self.text = time.strftime(self.text_format, time.localtime())


Console output:
0      # This is output of print(dt) which is 0 when update is called from within __init__.
0
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
0.998341688      # This is output of print(dt) when called from the scheduler.
0.998341688

Console output with schedule_once:
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
0.5470451599999999     # takes obviously 0,547 seconds from scheduler init to startup the whole app.
0.5470451599999999    
1.004368501
1.004368501

0.9963966549999999
0.9963966549999999


The time (dt) for both MyLabel objects is absolutely identical. I explored that a bit deeper. Even if I add two more MyLabels on runtime (the second one  0.5 seconds after the first one) all timings are identical and the MyLabels are updatet simultaneously.  Obviously kivy runs all MyLabel objects on one single scheduler. This may answer my question above: There's only one scheduler initiated for the whole MyLabel class which also has no negative impact on performance.

I just tried out what happens if I initiate the scheduler with different timings for both MyLabel objects (1 and 1.01 or 1 and 1.5 or 1 and 2 seconds) by giving them a numeric custom attribute delta to use as interval at creating the scheduler. Obviously self.delta doesn't exist yet at creation time - so the scheduler is initiated with 0. Updates now happen roughly between 0.011 and 0.013 seconds (may be the shortest possible timer intervall) - which is not what I intendet. Furthermore two MyLabels are not updated synchronous anymore - even if the timing ist set identical for both - what seems logical. Setting the Interval to 0 may have negative impact on performance.
On the other hand with MyLabel objects created on runtime (within App) you can initiate different scheduler intervals beeing executed  independently without the short 0-intervals.

I did not find a way to initiate different scheduler intervals for MyLabel objects created from within the .kv file as the self.delta attribute is not populated until runtime.
Do you have a solution for this?

class MyLabel(Label):
    text_format = StringProperty()
    delta = NumericProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_interval(self.update, self.delta)   # self.delta is 0 when initiated from a .kv file
                                                           # works perfect if initiated from within the App class


Your help gave me a lot of food for thought. Thank you for that.

Elliot Garbus

unread,
May 19, 2021, 2:43:33 PM5/19/21
to kivy-...@googlegroups.com

 

I’d recommend you use super(), it is safer and adds flexibility of you change the parent class – you do not have this dependency.  It is also the convention.

 

I used something like: super(MyLabel, self).__init__(**kwargs)

This is how it was done in Python 2, This is simplified in Python3 to: super().__init__(**kwargs)

 

Keeping all of this in the MyLabel class may require a parameter that tells the update routine which of the different strings to pass to the MyLabel.text. Also it would create multiple clock schedulers. This may not be advantageous regarding performance. (Meanwhile I found this not to be an problem... find mor on that below.)

How do you think about that?

In general you can waste a lot of development time being concerned about potential performance issue before they come up.  In this particular case I would not be concerned.

 

Is there a more elegant way to call update() while initiating the MyLabel objects although App still is not running? Probably something like on_start() ?

If you are using a kivy property to display the data, you could initialize the kivy property with the initial data you want.

time_of_day = StringProperty(‘time of day call’)

 

I suspect the issue you are seeing is that you are setting self.text in __init__() this is before the kv code gets processed so your kv code is overwriting the value you set here.

 

The dt attribute is providing information to the callback about how delayed the call back was from the time requested.   I don’t understand your question about different schedule intervals.  The natural way to it might be to use schedule_once() with a delay to start another schedule.

 

 

From: Nobody999
Sent: Wednesday, May 19, 2021 11:18 AM
To: Kivy users support
Subject: Re: [kivy-users] Custom Attributes in KV-File

 

This all works great. Thank you very much - you helped me a lot.

(Label):
    text_format = StringProperty()

   
def __init__(self, **kwargs): # init method gives a build error
       
super().__init__(**kwargs)
        Clock.schedule_interval(
self.update, 1) # How can I add a Clock to update the time/date

   
def update(self, dt):
       
self.text = time.strftime(self.text_format, time.localtime())


Nobody999

unread,
May 19, 2021, 6:17:16 PM5/19/21
to Kivy users support

If you are using a kivy property to display the data, you could initialize the kivy property with the initial data you want.

time_of_day = StringProperty(‘time of day call’)

I suspect the issue you are seeing is that you are setting self.text in __init__() this is before the kv code gets processed so your kv code is overwriting the value you set here.

The dt attribute is providing information to the callback about how delayed the call back was from the time requested.   I don’t understand your question about different schedule intervals.  The natural way to it might be to use schedule_once() with a delay to start another schedule.


Text initialisation is a minor problem. It should be set with the first update which schould happen as soon as possible. Initialising a clock scheduler with '1' will cause the text to be shown first after a second. Calling an update immediately with schedule_once solves this.
What I more wanted is, to give each MyLabel widget in the .kv file an attribute 'delta' which is the specific clock schedule interval of this particular Label. This didn't work because that delta property is not yet available to python while initiating the clock scheduler in the init methode.
I found the following to work well:

class MyLabel(Label):
    text_format = StringProperty()
    delta = NumericProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        Clock.schedule_once(self.clkinit, 0)

    def update(self, _):
        self.text = time.strftime(self.text_format, time.localtime())

    def clkinit(self, _):
        self.update(self)
        self.clk = Clock.schedule_interval(self.update, self.delta)

The .kv file can now have Labels updated in individual intervals:
MyLabel:
    delta: 1     #This label is updated every second
    text_format: '%H:%M:%S'
MyLabel:
    delta: 2     #This label is updated every 2 seconds
    text_format: '%H:%M:%S'

The clock scheduler is not created in init anymore. After the start of the app, schedule_once immediately calls clkinit() which updates the Label AND creates a scheduler with an individual intervall (value of delta coming from the .kv file) for eachMyLabel object.
It was more of an academic interest.  In practice this will hardly be relevant. But I like to understand how things work in the background. And this made al lot clearer to me.
Reply all
Reply to author
Forward
0 new messages