NumesSanguis
NumesSanguis

Reputation: 6332

kivy access child id

I want to access the id of the child to decide whether to delete the widget or not. I have the following code:

main.py

#!/usr/bin/kivy
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class Terminator(BoxLayout):
    def DelButton(self):
        print("Deleting...")

        for child in self.children:
            print(child)
            print(child.text)

            if not child.id == 'deleto':
                print(child.id)
                #self.remove_widget(child)
            else:
                print('No delete')


class TestApp(App):
    def build(self):
        pass


if __name__ == '__main__':
    TestApp().run()

test.kv

#:kivy 1.9.0

<Terminator>:
    id: masta
    orientation: 'vertical'

    Button:
        id: deleto
        text: "Delete"
        on_release: masta.DelButton()

    Button
    Button

Terminator

However when printing the id with: print(child.id), it always returns: None. Even though print(child.text) correct returns Delete or .

Question

Upvotes: 3

Views: 8620

Answers (2)

Eduard
Eduard

Reputation: 1

The above are good options for implementing the task. I've used them quite successfully in my code. Now I want to recommend the visual code that the Kivy Framework directly recommends in its documentation.
Look at it, it is clear and available at this link: https://kivy.org/doc/stable/api-kivy.uix.widget.html#kivy.uix.widget.Widget.ids

You can quickly figure it out and use it. This section is - Widget class, anchor is - ids.

This is a dictionary of ids defined in your kv language. This will only be populated if you use ids in your kv language code.

ids is a DictProperty and defaults to an empty dict {}.

The ids are populated for each root level widget definition. For example:

# in kv
<MyWidget@Widget>:
    id: my_widget
    Label:
        id: label_widget
        Widget:
            id: inner_widget
            Label:
                id: inner_label
    TextInput:
        id: text_input
    OtherWidget:
        id: other_widget


<OtherWidget@Widget>
    id: other_widget
    Label:
        id: other_label
        TextInput:
            id: other_textinput





# Then, in python:

>>> widget = MyWidget()
>>> print(widget.ids)
{'other_widget': <weakproxy at 041CFED0 to OtherWidget at 041BEC38>,
'inner_widget': <weakproxy at 04137EA0 to Widget at 04138228>,
'inner_label': <weakproxy at 04143540 to Label at 04138260>,
'label_widget': <weakproxy at 04137B70 to Label at 040F97A0>,
'text_input': <weakproxy at 041BB5D0 to TextInput at 041BEC00>}

>>> print(widget.ids['other_widget'].ids)
{'other_textinput': <weakproxy at 041DBB40 to TextInput at 041BEF48>,
'other_label': <weakproxy at 041DB570 to Label at 041BEEA0>}
>>> print(widget.ids['label_widget'].ids)
{}

Upvotes: 0

Nykakin
Nykakin

Reputation: 8747

As you can read in the documentation:

In a widget tree there is often a need to access/reference other widgets. The Kv Language provides a way to do this using id’s. Think of them as class level variables that can only be used in the Kv language.

Accesing ids from Python code is described here. Working example:

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

Builder.load_string("""
<Terminator>:
    id: masta
    orientation: 'vertical'

    MyButton:
        id: deleto

        button_id: deleto

        text: "Delete"
        on_release: masta.DelButton()

    MyButton
    MyButton
""")

class MyButton(Button):
    button_id = ObjectProperty(None)

class Terminator(BoxLayout):
    def DelButton(self):
        for child in self.children:
            print(child.button_id)       

class TestApp(App):
    def build(self):
        return Terminator()    

if __name__ == '__main__':
    TestApp().run()

To skip deleting a button with "Delete" label you can examine its text property. Hovewer deleting from inside the loop will lead to the bugs, since some of the children will get skiped after the list you're iterating on will get altered:

class Terminator(BoxLayout):
    def DelButton(self):
        for child in self.children:            
            self.remove_widget(child) # this will leave one child

You have to create a list of children to delete:

class Terminator(BoxLayout):
    def DelButton(self):
        for child in [child for child in self.children]:            
            self.remove_widget(child) # this will delete all children

In your case:

class Terminator(BoxLayout):
    def DelButton(self):
        for child in [child for child in self.children if child.text != "Delete"]:            
            self.remove_widget(child)

Upvotes: 5

Related Questions