Kivy Tutorials

Using IDs in Kivy KV Language

This article can be read in about 29 minutes.

Kivy has a concept called id for referencing widgets from Python code. This article explains the use of id in the kv language and the scope (valid range) of ids.

Basics of IDs

IDs can be used to reference and retrieve information about a widget defined in a kv file. ids can only be defined in a kv file as a property common to all widgets and cannot be defined in Python code.

The ids should be a unique value in the same kv file. Also, id has a valid range. The valid range is limited to within a single tree, and the way an id is referenced differs between in-scope and out-of-scope.

It is not necessary to define an id for every widget. It is usually better to define them only for widgets that are operated dynamically, since defining them for every id would make management difficult.

Cautions for using ids
  • Define the id in the kv file.
  • The id cannot be added or changed directly in Python code.
  • The id should be a unique value.
  • The id in the same scope and id outside the scope are referenced in different ways.
  • The id should be defined only for the widget you want to reference.
  • The id should be defined first in a property.

How to Write IDs

To set an id, define it as follows. id is defined first in the property

id: id_name

The name of the id should be an English word. It does not need to be enclosed in single or double quotes.

Label:
    id: mylabel
    text: "Set label id"
Button:
    id: myButton
    text: "Set button id"

How IDs Works in Kivy

The id property set in the kv file is automatically registered in the ids dictionary when the kv file is loaded, and the Python code can refer to and change the widget defined by the id by traversing the ids namespace.

Diagram of the relationship between Kivy's id and ids dictionaries.

The actual ids are registered as follows

{'test_label': <WeakProxy to <kivy.uix.label.Label object at 0x0000023637A62F20>>, 'status_label': <WeakProxy to <kivy.uix.label.Label object at 0x0000023637AAD550>>}

Note that ids references can cause errors depending on the timing. To understand this, please refer to the following article. (This article has not been translated yet.)


Referencing IDs in KV File

You can also reference or change widgets within a kv file.

Referencing Own Widgets

You can refer to the properties of your own widget. When referring to yourself, you do not need to specify the id, but only specify the property followed by the self keyword.

self.property_name
Label:
    id: mylabel
    text: self.text  # Refer to own property.
    on_touch_down: self.text = "It's clicked."  # Updating Ourselves

The above code makes a recursive call that references itself and updates the text when the Label is clicked. Note that you cannot reference self.text directly in the string.

Referencing Other Widgets in the KV File

To reference other widgets in the kv file, write the id of the widget to be referenced followed by the name of the widget property.

Referenced_widget_id.property_name
Label:
    id: label1
    text: "label1 text"
         
Label:
    id: label2
    text: label1.text  # Specify widget with id label1
    on_touch_down: self.text =  f"Label2 text:{label1.text}"

The above code assigns the text of label1 to the text property of the label of label2. Also, the text changes when label2 is clicked.

Referencing IDs in Python

To refer to a widget with an id in Python code, write the following

keyword.ids.id_name.widget_property

The keyword becomes self when referring to it from within a class.

#Referencing from within a class

# When referencing from within a class
text = self.ids.mylabel.text

#To change
self.ids.mylabel.text = 'changed text'

In the above case, the text property of the Label widget is referenced.

When referencing from an App subclass, it will be self.root.

# Referencing from App subclass

# When referencing
text = self.root.ids.mylabel.text

#To change
self.root.ids.mylabel.text = 'changed text'

Widgets Created Dynamically in Python Code

Python code cannot assign an id to a widget that is not defined in the kv file if it is created dynamically in Python code.

What Is a Dynamically Created Widgets?

A dynamically created widget is a widget that is created by manipulating an originally defined widget. For example, clicking on a button that was originally defined will create a different button, change the text, etc. The following is an example of a dynamically created widget.

example_ids1.py

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

class RootWidget(BoxLayout):
    def add_label(self):
        self.ids.container.add_widget(Label(text="Dynamically added labels."))
        print(self.ids)  # Refer to ids dictionary.

class ExampleIds1(App):
    def build(self):
        return RootWidget()
    
if __name__ == "__main__":
    ExampleIds1().run()

exampleids1.kv

<RootWidget>:
    BoxLayout:
        id: container
        orientation: "vertical"
    
        Button:
            text: "Add labels"
            on_press: root.add_label()

This code creates labels each time the button is pressed. The ids dictionary is also displayed on the console screen to check the ids.

{'container': <WeakProxy to <kivy.uix.boxlayout.BoxLayout object at 0x00000299BF212580>>}

It is natural because we have not defined an id (id cannot be described), but only the idcontainer” defined in the kv file will be registered. Widgets added dynamically cannot be registered in the ids dictionary.

To Reference Dynamically Created Widgets

You can manipulate dynamically created widgets directly in Python code without the need for an id by manipulating the widget object created with add_widget().

self.label = Label(text="text")
self.add_widget(self.label)

# Change the text property of the Label widget.
self.label.text = 'changed'

Scope of ID (valid range)

The scope of an id is limited to within the tree of widgets. Rules outside the target tree are out of scope and cannot be referenced in the usual way.

In Scope and Out of Scope

In the following case, MyWidget becomes the root widget and the widgets under it are in scope.

<MyWidget> # Root widget
+--- BoxLayout # Widgets below this are within scope.
+---+--- Label
+---+--- Label
+---+--- Button
+---+--- Button

If you define multiple rules, the other trees will be out of scope; if you have a MainWidget and a SubWidget, each tree will have a different scope.

  • MainWidget tree has the same scope
  • SubWidget tree has the same scope
  • SubWidget tree from the MainWidget tree is an out-of-scope widget
  • MainWidget from SubWidget tree is out of scope
<MainWidget> # Root widget
+--- BoxLayout # Same scope in MainWidget tree.
+---+--- Button
+---+--- Button

<SubWidget>
+--- BoxLayout # Same scope in SubWidget tree.
+---+--- Label

The root widget’s tree cannot be manipulated from outer scopes. The root widget can manipulate other trees by placing other classes in its own. When a class is instantiated, it becomes an object, and Kivy treats it as a widget.

A SubWidget cannot manipulate a MainWidget in the following cases: The MainWidget can manipulate widgets in the SubWidget tree by placing the SubWidget in its own tree. SubWidget is treated as a widget, which means that you place a widget of a custom class in a BoxLayout. Without this arrangement, the SubWidget will be isolated and not displayed on the screen. Also note that if you do not define an id, you will not be able to trace the namespace.

<MainWidget> # Root widget
+--- BoxLayout
+---+--- Button
+---+--- Button
+---+--- SubWidget # SubWidget can be manipulated by placing SubWidget in the root widget.
+---+---+--- id: sub_id # Cannot be referenced without defining an id

<SubWidget>
+--- BoxLayout # SubWidget cannot manipulate widgets in MainWidget.
+---+--- Label

Differences in Referencing Methods

When referencing from Python code, you can use the normal referencing method within the same scope, but outside of scope you must retroactively specify the ids namespace.

Referencing Within a Scope

# Referencing from within a subclass.
self.ids.id_name.widget_property

# Referencing from App subclasses.
self.root.id_name.widget_property

Out-of-scope References

To reference an out-of-scope id, you must go back through the namespace as follows. The id name of the class instance is another class rule (subclass) that is not the root widget. The id definition is required for use in this namespace.

# Referencing from within a subclass.
# self.ids.class_instance_id_name.ids.id_name.widget_property
self.ids.sub_id.ids.sub_label.text

# Referencing from App subclasses.
# self.root.ids.class_instance_id_name.id_name.widget_property
self.root.ids.sub_id.ids.sub_label.text

Example ID Scopes and Referencing Methods

Here are some samples to help you understand the scope of an id

Sample 1: Normal scope of ID

The scope of an id is in the same tree. In the following tree structure, all widgets under the MyWidget root widget have the same scope. The reference method is the normal reference method described in the previous chapter, but let’s review it again for a refresher.

<MyWidget>
+---BoxLayout
+---+--- Label
+---+--- Label
+---+--- Button
+---+--- Button

example_ids2.py

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

class MainWidget(BoxLayout):
    def change_label(self):
        self.ids.label2.text = "Update label2 from root."

class ExampleIds2(App):
    def build(self):
        return MainWidget()
    
    def change_label(self):
        self.root.ids.label1.text = "Update label1 from app."

if __name__ == "__main__":
    ExampleIds2().run()

exampleids2.kv

<MainWidget>:
    orientation: 'vertical'

    Label:
        id: label1
        text: "Click here"
        on_touch_down: self.text += "\nSelf-referenced"

    Label:
        id: label2
        text: f"Referencing other widgets from a kv file:\n text of button1 is {button1.text}"

    Button:
        id: button1
        text: "Change from app."
        on_press: app.change_label()
        
    Button:
        text: "Change from root"
        on_press: root.change_label()

Any id under the MainWidget tree can be referenced. Also, any id can be referenced from the subclass ExampleIds2, which inherits from the App class.

Example 2: Scope Restriction of ID (multiple class rules)

Let’s look at the case where multiple class rules are defined. In this case, since they are in different trees, their id’s are out of scope and cannot be referenced in the usual way (i.e., in the namespace normally used). Also, a SubWidget cannot reference a widget in a MainWidget.

<MainWidget> # The descendants of this tree can be referenced.
+--- BoxLayout
+---+--- SubWidget # Place SubWidget.
+---+--- Label
+---+--- Button
+---+--- Button

<SubWidget> # The descendants of this tree can be referenced, but not the MainWidget.
+--- BoxLayout
+---+--- Label
+---+--- Button

example_ids3.py

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

class SubWidget(BoxLayout):
    def update_sub_label(self):
        # Cannot refer to scopes other than subwidgets.
        # self.ids.main_label.text = "Update Main_label from SubWidget."  # This will result in an error.

        # main_widget = self.ids.main_widget
        # main_widget.ids.main_label.text = "Update Sub_label from SubWidget."  # This will result in an error.

        self.ids.sub_label.text = "Update sub_label from SubWidget."
        print(f"SubWidget.ids:{self.ids}")

class MainWidget(BoxLayout):
    def update_sub_label(self):
        # No direct access to the id in the subwidget.
        # self.ids.sub_label.text = "Update sub_label from MainWidget."  # This will result in an error.

        sub_widget = self.ids.sub_widget # The right way.
        sub_widget.ids.sub_label.text = "Update sub_label from MainWidget"
        print(f"MainWidget.ids:{self.ids}")

class ExampleIds3(App):
    def build(self):
        return MainWidget()
    
    def update_sub_label(self):
        # No direct access to the id in the subwidget.
        # self.root.ids.sub_label.text = "Update sub_label from ExampleIds3"  # This will result in an error.

        sub_widget = self.root.ids.sub_widget # The right way.
        sub_widget.ids.sub_label.text = "Update sub_label from ExampleIds3"
        print(f"ExampleIds3.ids:{self.root.ids}")
    
if __name__ == "__main__":
    ExampleIds3().run()

exampleids3.kv

<SubWidget>:
    BoxLayout:
        orientation: "vertical"
    
        Label:
            id: sub_label
            text: "SubWidget label"
        
        Button:
            text: "Change SubWidget to SubWidget."
            on_press: root.update_sub_label()

<MainWidget>:
    BoxLayout:
        orientation: "vertical"
        
        SubWidget:
            id: sub_widget

        Label:
            id: main_label
            text: "MainWidget's label \n This cannot be manipulated from outside the scope."

        Button:
            text: "Change SubWidget from ExampleIds3."
            on_press: app.update_sub_label()
        
        Button:
            text: "Change SubWidget from MainWidget."
            on_press: root.update_sub_label()

This sample references sub_label in SubWidget in each class.

The sub_label is a scope in the SubWidget tree, so it cannot be referenced directly from the MainWidget or ExampleIds3 class, so it must be referenced back in the namespace. if referenced from the App subclass, it will be self.root.ids ids when referring to it from the App subclass.

        sub_widget = self.ids.sub_widget  # The right way.
        sub_widget.ids.sub_label.text = "Update sub_label from MainWidget"

# Can be written on one line.
# self.ids.sub_widget.ids.sub_label.text

Since the SubWidget is within a label in its own tree, the normal reference method can be used.

self.ids.sub_label.text = "Update sub_label from SubWidget."

From the SubWidget point of view, MainWidget is out of scope, so we cannot reference main_label. Going back through the namespace as shown below will result in an error.

Label:
    id: main_label
    text: "MainWidget's label \n This cannot be manipulated from outside the scope."
# main_widget = self.ids.main_widget
# main_widget.ids.main_label.text = "Update Sub_label from SubWidget" # This will result in an error.

SubWidget can be referenced from MainWidget because it is placed in the tree of MainWidget as shown in the figure below and is therefore treated as a widget in the same tree. Also, since App subclasses can reference the root widget tree, SubWidget can be referenced from ExampleIds3.

To reference a SubWidget, an id is required so that the namespace can be traced.

SubWidget: sub_widget
    id: sub_widget

Let’s see how this works since each class refers to the ids dictionary.

print(f "MainWidget.ids:{self.ids}")
SubWidget.ids:{'sub_label': <WeakProxy to <kivy.uix.label.Label object at 0x000001E56000DD30>>}

MainWidget.ids:{'sub_widget': <WeakProxy to <__main__.SubWidget object at 0x000001E55FFABB60>>, 'main_label': <WeakProxy to <kivy.uix.label.Label object at 0x000001E56002B690>>}

ExampleIds3.ids:{'sub_widget': <WeakProxy to <__main__.SubWidget object at 0x000001E55FFABB60>>, 'main_label': <WeakProxy to <kivy.uix.label.Label object at 0x000001E56002B690>>}

These indicate the scope of the ids for each class.

  • SubWidget ids: sub_label
  • MainWidget ids: sub_widget, main_label
  • ExampleIds3 ids: (same as ids of MainWidget): sub_widget, main_label

What Happens If Duplicate ID Is Used?

Defining the same id does not cause an error in the rule syntax, but it is recommended to use a unique id since the behavior is different from the expected one. The following is an example of using the same id name.

example_ids4.py

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

class MainWidget(BoxLayout):
    def change_label(self):
        self.ids.same_label.text = "Update label"
        print(self.ids)

class ExampleIds4(App):
    def build(self):
        return MainWidget()

if __name__ == "__main__":
    ExampleIds4().run()

exampleids4.kv

<MainWidget>:
    orientation: 'vertical'

    Label:
        id: same_label
        text: "Label1"

    Label:
        id: same_label
        text: "Label2"

    Button:
        text: "Click"
        on_press: root.change_label()

The same id is defined for the two labels. The result of this code is that only “label 2” is updated. Also, only one id is registered for ids.

{'same_label': <WeakProxy to <kivy.uix.label.Label object at 0x000001E169FD4EC0>>}

This is an unexpected result, so please be very careful about duplicate ids.

Comment

Copied title and URL