Converting kv land to python kivy methods (waht is the reasoning for differences between the python methods and the kv lang?)

81 views
Skip to first unread message

Gary Kuipers

unread,
Mar 1, 2023, 1:51:56 PM3/1/23
to Kivy users support
Trying to convert this:

<ButtonsLayout1>:
    Button:
        id:photo
        on_press: root.photo()
        background_normal: 'icons/camera_white.png'
        background_down: 'icons/camera_red.png'

and this:
class ButtonsLayout1(RelativeLayout):
    def __init__(self, **args):
        super().__init__(**args)
        Builder.load_string(BL1)

into this:
class ButtonsLayout1(RelativeLayout):
   def __init__(self, **args):
        super().__init__(**args)
        photo_button = Button(id='photo',
        background_normal='icons/camera_white.png',
        background_down='icons/camera_red.png')

But there's a catch:
TypeError: Properties ['id'] passed to __init__ may not be existing property names.

So out of curiosity, why are the implementations such that the parameters vary (Why is "id" ok in kv landg but not ok in python class instantiations?

If you take the id out other things break, namely this:
def on_size(self, layout, size):
if platform in ['android', 'ios']:
self.ids.photo.min_state_time = 0.3

What can I do to fix this?














Gary Kuipers

unread,
Mar 1, 2023, 2:07:22 PM3/1/23
to Kivy users support
I solved the "how to do it": 

# super(ButtonsLayout1, self).__init__(**args)
photo_button = Button(background_normal='icons/camera_white.png',
background_down='icons/camera_red.png')
# background_color=(1, 0.647, 0, 1))
self.ids['photo'] = photo_button

But my question about "why?" still stands. Why the separate and non-intuitive second line as opposed to including the id in the parameters of the instantiation? 

Elliot Garbus

unread,
Mar 1, 2023, 2:28:15 PM3/1/23
to kivy-...@googlegroups.com

The problem has to do with initialization order.  The ids dict is populated when the kv code is processed, this is after the __init__() for the class has run.  To address this use the on_kv_post() method.

https://kivy.org/doc/stable/api-kivy.uix.widget.html?highlight=on_kv_post#kivy.uix.widget.Widget

 

The on_kv_post() event will fire after the kv code for the widget has been processed, so the ids will be valid.

--
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/d8b41e18-4b6b-4f02-b7eb-ef24a87fac60n%40googlegroups.com.

 

Gary Kuipers

unread,
Mar 3, 2023, 10:33:34 AM3/3/23
to Kivy users support

The problem has to do with initialization order.  The ids dict is populated when the kv code is processed, this is after the __init__() for the class has run.  To address this use the on_kv_post() method.

https://kivy.org/doc/stable/api-kivy.uix.widget.html?highlight=on_kv_post#kivy.uix.widget.Widget

The above link says: "Fired after all the kv rules associated with the widget and all other widgets that are in any of those rules have had all their kv rules applied. base_widget is the base-most widget whose instantiation triggered the kv rules (i.e. the widget instantiated from Python, e.g. MyWidget())."

 The on_kv_post() event will fire after the kv code for the widget has been processed, so the ids will be valid.


Trying to understand:

The self.ids dict is part of the class. 

When creating widgets in __init__ we have to tell the dict the name of what we just created.

If we use the kv land the processor of the kv lang works AFTER init and hadles both the creation of the widget and the inclusion of the id in the class.


OK, I can see that, but why can the id not be specified in the __init__ creation of the Button Widget as I tried to do in the original post of this message thread?
class ButtonsLayout1(RelativeLayout):
    def __init__(self, **args):
        super().__init__(**args)
        photo_button = Button(id='photo',  <<<  TypeError: Properties ['id'] passed to __init__ may not be existing property name
        background_normal='icons/camera_white.png', etc.

It seems to me that Button(... is calling a piece of code that creates an object. Why can't it also include code to populate the ids dict? Is it because the code returns an object and there is no way to have that affect the id's dict?  So why can the code that generates the class do it? Becasue it is generating the class and so can directly affect the ids?

Thanks for the explanations, they help!

Elliot Garbus

unread,
Mar 3, 2023, 1:29:40 PM3/3/23
to kivy-...@googlegroups.com

Lots of good questions here.

The ids dictionary is a member of the Widget class.  It is populated when kv is process.  Widgets do NOT have an id attribute.  I believe this is to reduce confusion and errors. 

 

When the kv is processed the ids dict is used to associate a widget instance with an id.  An ids is only populated for each kivy “rule”.   This allows you to use the ids to navigate the widget tree.

 

In python when you are instancing a widget, you do not know where that widget sits in the hierarchy – so you do not know which ids dict to write the id into.

 

Read: https://kivy.org/doc/stable/api-kivy.uix.widget.html?highlight=ids#kivy.uix.widget.Widget.ids

 

You could hack the ids dict, and update it with additional information. (I would NOT recommend this).  Or create a separate dictionary that associates an id with a widget to ease addressing in some limited cases. I’ll add I have never felt the need to do this.

 

I hope this was helpful.  Let me know what you think.

 

Here is an example that adds content to the dict from python.  Some of the danger with this approach would be knowing which ids dict to update, and overwriting an existing entry.

 

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


kv =
"""
RootBoxLayout:
    orientation: 'vertical'
    Button:
        id: button_1
        text: 'Regular Button'

"""

class RootBoxLayout(BoxLayout):
   
def on_kv_post(self, base_widget):
       
print(f'ids created by kv: {self.ids}')

   
def update_ids(self):
       
# I would NOT recommend this - FOR EDUCATIONAL PURPOSES ONLY
       
for b in self.children:
           
if hasattr(b, 'id'):
               
self.ids.update({b.id: b})
       
print(f'ids after the update {self.ids}')

class IdButton(Button):
    id = StringProperty(
'default id'# used to store the id


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

   
def on_start(self):
        button = IdButton(
text='fake_id_0', id='fake_id_0')
       
self.root.add_widget(button)
        button = IdButton(
text='fake_id_1', id='fake_id_1')
       
self.root.add_widget(button)
       
self.root.update_ids()


BindButtonApp().run()
Reply all
Reply to author
Forward
0 new messages