Kivy Tutorials

Using Dynamic Class Rules Templates in Kivy KV Language

This article can be read in about 29 minutes.

This page introduces how to write dynamic class rules and sample code that can be used in Kivy’s kv language. Dynamic class rules can be customized by inheriting widgets, and can be used as templates. In addition, since multiple inheritance of widgets is possible, you can use kv files flexibly depending on how you use them.

How to Write Dynamic Class Rules

Dynamic class rules are a mechanism that allows you to create custom widgets by inheriting from widgets. The concept is the same as class inheritance. In terms of usage, a custom widget can be defined as a template or called as a template for dynamically generated widgets.

The way to write it is similar to the class rule syntax, but ‘@’ separates the widget name from the base widget name.

<ClassName@BaseWidget>:

ClassName:
Specify an arbitrary name here. It is the same as class inheritance, so it follows the class naming conventions.
@: Separator
BaseWidget
Specify the name of the widget from which to inherit.

Next, define the properties.

<CustomButton@Button>:
    font_size: 20
    background_color: (1, 0, 0, 1)
    color: (1, 1, 1, 1)

Since this is just a definition, place it in a tree structure with the name specified in ClassName, just as you would for a normal widget.

The dynamic class rule can be written in Python code as follows, and you can see that it is the same as the class definition. This same thing is written in the kv file.

class CustomButton(Button):
    pass

class RootWidget(BoxLayout):
    pass

Let’s look at some sample code to see how dynamic class rules are actually used.

Using Dynamic Class Rules With Templates (1)

This is simply used to customize a widget. It is useful, for example, to set the same style for multiple widgets. In the case of a button inheriting from a widget, it is written as follows.

<CustomButton@Button>:
    font_size: 20
    background_color: (1, 0, 0, 1)
    color: (1, 1, 1, 1)

Here we have defined a custom widget, CustomButton. To make it work, place it in the widget tree just like a normal widget.

# kv file Example(1) Line up the same buttons.
<CustomButton@Button>:
    text: "Button"
    font_size: 20
    background_color: (1, 0, 0, 1)
    color: (1, 1, 1, 1)

<RootWidget>:
    CustomButton:
    CustomButton:
    CustomButton:
    CustomButton:
        

property can also be added.

# kv file Example(2)
<CustomButton@Button>:
    font_size: 20
    background_color: (1, 0, 0, 1)
    color: (1, 1, 1, 1)

<RootWidget>:
    CustomButton:
        text: "CustomButton is placed."
    CustomButton:
        text: "Another CustomButton is placed."
# py file
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout

class RootWidget(BoxLayout):  
    pass

class MyApp(App):
    def build(self):
        return RootWidget()

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

Using Dynamic Class Rules With Templates (2)

It can be used to apply the same property to multiple widgets.

# kvファイル
<CustomLabel@Label>:
    font_name: "../fonts/file.ttf"
<CustomButton@Button>:
    font_name: "../fonts/file.ttf"

<RootWidget>:
    orientation: 'vertical'
    
    CustomLabel:
        text: "Specify fonts in dynamic classes"
    CustomButton:
        text: "font specified"

Use as a Template for Dynamically Generated Widgets

It can also be used as a template for dynamically generating widgets in Python code.

Since dynamic class rules are only defined, they will not work without instance creation. This is where the Factory class comes in: it is a mechanism for registering custom classes and widgets and dynamically creating instances of them. Normally, Factory requires registration of classes and modules using Factory.register(), but in the case of dynamic classes, they can be used without registering them using register().

Factory.<ClassName specified in the dynamic class rule>

For example, to instantiate a CustomButton in the Factory, it can be written as follows.

from kivy.factory import Factory

button = Factory.CustomButton()

Once registered in the Factory, add the widget with add_widget().

self.add_widget(button)

IDs When Using Factory

Note that if an instance is created in the Factory, it will not be registered in ids even if the id is set using a dynamic class rule.

Example to Dynamically Add Button (1)

dynamic_class1.py

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

class RootWidget(BoxLayout):
    def add_custom_button(self):
        # Create an instance in the Factory class.
        button = Factory.CustomButton()
        self.add_widget(button)

class DynamicClass1(App):
    def build(self):
        return RootWidget()

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

dynamicclass1.kv

<CustomButton@Button>:
    text: "Dynamic button"
    font_size: 20
    background_color: (0.3, 0.6, 0.9, 1)

<RootWidget>:
    orientation: "vertical"
    Button:
        text: "Click"
        on_press: root.add_custom_button()
# Create an instance in the Factory class
button = Factory.CustomButton()
self.add_widget(button)

Add the Button widget instantiated in Factory with add_widget().

button = Factory.CustomButton(text="changed text")

It is also possible to change properties dynamically.

Multiple Inheritance of Dynamic Class Rules

Multiple widgets can be inherited by placing a “+” between two base widgets. Only two widgets may be inherited.

<ClassName@BaseWidget1+BaseWidget2>

ClassName:
Specify an arbitrary name here. This is the same as class inheritance, so follow the class naming conventions.
@: Separator
BaseWidget1:
Specifies the name of the widget from which it is inherited.
+: The delimiter of the widget from which it is inherited.
BaseWidget2:
Specifies the name of the widget from which it is inherited.

Personally, I don’t see much usefulness in multiple inheritance, but I think it is good to use in Behavior mixin.

<MyWidget@ButtonBehavior+Label>:

This is a way of writing using the Behavior class.

<DraggableButton@DragBehavior+Label>
    Label: # This doesn't need to be written.

There is no need to define Label here.

<DraggableButton@DragBehavior+Label>
    text:"Multiple Inheritance of Dynamic Classes."
    font_size: 20

Describes the properties to be defined for the widget.

Please refer to the following article that uses Behavior mixin.

Setting id’s and Events in Dynamic Class Rules

Setting an id in a dynamic class rule and referencing it in Python, or in an event that calls a callback function, can be difficult depending on the complexity of the tree structure. This requires an understanding of how keywords and id’s work and the lifecycle of a Kivy app. If it seems too difficult, there are alternatives such as not using dynamic class rules or writing them in Python code.

Code Example

Let’s look at two more sample codes that are just a bit more practical.

  1. Sample code for displaying five buttons that randomly change color when a button is pressed; learn how to instantiate multiple dynamic class rules in a single Factory.
  2. You can learn how to transition between three screens pseudo-generated by a dynamic class rule inheriting from a layout.
Application to randomly change button colors using Kivy’s Dynamic class.

Example for Dynamically Add Buttons (2)

This sample randomly displays five colored buttons each time a button is pressed. you can learn how to generate multiple dynamic class rules with a single Factory.

dynamic_class2.py

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.factory import Factory
from random import choice

class RootWidget(BoxLayout):
    def add_custom_button(self):
        # Name the first button to retain.
        initial_button = self.ids.initial_button
        # Clear the widget on the screen.
        self.clear_widgets()
        # Add the first button again.
        self.add_widget(initial_button)
        # Add new button.
        for _ in range(5):
            button_class = choice(["RedButton", "GreenButton"])
            button = Factory.get(button_class)()
            self.add_widget(button)

class DynamicClass2(App):
    def build(self):
        return RootWidget()

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

dynamicclass2.kv

<RedButton@Button>:
    text: "Red Button"
    background_color: (1, 0, 0, 1)

<GreenButton@Button>:
    text: "Green Button"
    background_color: (0, 1, 0, 1)

<RootWidget>:
    Button:
        id: initial_button
        text: "Click here"
        on_press: root.add_custom_button()

Code Explanation

This code consists of two screens.
First screen (button only) -> first screen with button + 5 buttons.

<RootWidget>
+--- Button

<RedButton@Button>
<GreenButton@Button>
initial_button = self.ids.initial_button

Variables are assigned to hold the initial screen.

self.clear_widgets()

The widgets displayed on the screen are deleted to make it look like a pseudo screen transition. Here, the button displayed on the first screen is deleted.

self.add_widget(initial_button)

Since the button on the initial screen has been removed, nothing is displayed on the screen, so we add the button that we have assigned to a variable.

       for _ in range(5):
            button_class = choice(["RedButton", "GreenButton"])
            button = Factory.get(button_class)()
            self.add_widget(button)

This loop creates 5 buttons, and the temporary variables in the for statement can be set to “_” to make them function as variables that do not retain their values.

from random import choice

button_class = choice(["RedButton", "GreenButton"])

choice() is a method included in Python’s random module that randomly selects an element from a list. In this code, either ” RedButton ” or ” GreenButton ” is randomly selected. This string will be the ClassName of the dynamic class rule in the kv file.

button = Factory.get(button_class)()

The Factory.get method retrieves the widget corresponding to the specified class name. By adding empty parentheses ‘()’, an instance of that class is created. For example, in the case of Factory.get(“RedButton”)(), the widget of the RedButton class is retrieved and a new instance is created.

Code Example for Inheriting Layout

Application that uses Kivy’s Dynamic class for screen transitions.

Inheriting a layout can be used as a template for pseudo screen transitions or as a screen template in ScreenManager (a class that manages multiple screens). Here is a sample of pseudo screen transition.

If you want to use ScreenManager for screen transitions, please refer to the following article as well.

dynamic_class3.py

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

class RootWidget(BoxLayout):
    def ScreenTransition(self, screen):
        self.clear_widgets()
        if screen == 1:
            layout = Factory.Screen1()
            layout.root_widget = self # Passing the root widget.
            self.add_widget(layout)
        elif screen == 2:
            layout = Factory.Screen2()
            layout.root_widget = self # Passing the root widget.
            self.add_widget(layout)
        elif screen == 0:
            initial_layout = Factory.InitialScreen()
            initial_layout.root_widget = self # Passing the root widget.
            self.add_widget(initial_layout)

class DynamicClass3(App):
    def build(self):
        root = RootWidget()
        initial_layout = Factory.InitialScreen()
        initial_layout.root_widget = root # Passing the root widget.
        root.add_widget(initial_layout)
        return root

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

dynamicclass3.kv

<Screen1@BoxLayout>:
    orientation: 'horizontal'
    Button:
        text: "Go to Screen 2"
        font_size: 20
        background_color: (0, 1, 0, 1)
        on_press: root.root_widget.ScreenTransition(2)  # Refer to the root widget.
    Label:
        text: "Screen 1"
        color: (1, 1, 1, 1)

<Screen2@BoxLayout>:
    orientation: 'horizontal'
    Button:
        text: "Back to Main"
        font_size: 20
        background_color: (1, 0, 0, 1)
        on_press: root.root_widget.ScreenTransition(0)  # Refer to the root widget.
    Label:
        text: "Screen 2"
        color: (1, 1, 1, 1)

<InitialScreen@BoxLayout>:
    orientation: 'vertical'
    Button:
        text: "Go to Screen 1"
        on_press: root.root_widget.ScreenTransition(1)  # Refer to the root widget.
    Label:
        text: "Main Screen"
        color: (1, 1, 1, 1)

<RootWidget>:
    id: container
    orientation: 'vertical'

Code Explanation

This code consists of three pseudo screens, each of which inherits from BoxLayout with dynamic class rules. Since dynamic class rules can only be inherited by two widgets, if you inherit a UI widget, you can only define that widget. However, if you inherit a layout such as BoxLayout, you can define any UI widget, which is useful for constructing a screen as a template.

<RootWidget>
+--- BoxLayout

<InitialScreen@BoxLayout>
+--- Botton
+--- Label
<Screen2@BoxLayout>
+--- Botton
+--- Label
<Screen1@BoxLayout>
+--- Botton
+--- Label

Pressing the button on each screen causes the screen to transition from InitialScreenScreen1Screen2.

In each screen, the root widget is assigned to root_widget so that an instance of RootWidget() can be referenced; the instantiation of the Factory can configure the screen, but the widget is deleted each time the screen transitions, We need to pass the root widget to root_widget so that it can reference an instance of RootWidget(). By referencing the root widget for each screen, we can transition between screens and pass data between screens. Failure to do so will result in an error as screen transitions cannot be performed.

(1) InitialScreen → Pass the root widget to root_widget.
(2) Delete widget → Screen1 → Pass root_widget to root_widget.
(3) Delete widget → Screen2 → Pass root_widget to root_widget.
(4) Delete widget → InitialScreen → pass root_widget to root_widget.
    def build(self):
        root = RootWidget()
        initial_layout = Factory.InitialScreen()
        initial_layout.root_widget = root  # Passing the root widget.
        root.add_widget(initial_layout)
        return root

The build method defines the screen that will be displayed when the application is launched. (i.e., InitialScreen ).

  1. Assign RootWidget() of the root widget to root
  2. Register InitialScreen() of the first screen in Factory and assign it to initial_layout
  3. Assign root (instance of RootWidget() ) to initial_layout.root_widget
  4. Add the instance registered with Factory to the widget
  5. Return an instance of root of the root widget

In the kv file, the ScreenTransition() method is called on the on_press() event of

on_press: root.root_widget.ScreenTransition(2)

The root becomes a keyword that refers to the root widget and root_widget is assigned an instance of the root widget. The root_widget part of the namespace can normally be referenced without the need to write this, but here the namespace is set like this to reference the root widget.

    ScreenTransition(self, screen):
        if screen == 1: # Screen1
            pass
        elif screen == 2: # Screen2
            pass
        elif screen == 0: # InitialScreen
            pass

The argument to ScreenTransition() is a flag that causes the screen to branch.

When ScreenTransition(self, screen) is called, it will branch according to the flag passed to screen,

self.clear_widgets()

Removes all widgets drawn on the screen, leaving the screen empty.

        if screen == 1:
            layout = Factory.Screen1()
            layout.root_widget = self  # Passing the root widget.
            self.add_widget(layout)

By registering Screen1 in the Factory, the widget defined by the Screen1 dynamic class rule is constructed. root_widget is passed its own Screen1 and is tied to the root widget. Now the Screen1 screen can reference the RootWidget() methods and properties of the root widget. The same thing is done for the other screens.

The point of this code is that screen transitions can be performed by linking each Screen instance to an instance of RootWidget(), the root widget, so that the methods and properties of RootWidget() can be referenced.

Comment

Copied title and URL