Skip to content
Home » Kivy Part 8 – Custom Widgets

Kivy Part 8 – Custom Widgets

Spread the love

In the previous parts we were using different Kivy widgets like label, button, slider, etc. But each time there was only one widget at a time. This is not what even the most trivial apps look like. Even a pretty basic app will have at least a couple of widgets. In this part we’re going to see how to address this issue.

But before we delve into the topic, here’s some info for you.

*****

Book Info

I just published my Kivy book, GUI Programming with Python and Kivy. It’s pretty long (over 800 pages) and comprehensive. And, which also counts, easy to read. The book contains lots of illustrations.

This book covers all the basics that you need to know to start programming GUI applications with Python and Kivy. Throughout the book we are building a GUI application from scratch, a fully functional game using all kinds of tools that Kivy has to offer. It’s our Slugrace project, but covered in a much more in-depth manner.

Each part of the book starts with a theoretical introduction of a topic or idea that we then implement in the project. I assume you have no prior knowledge of the Kivy library, but you should have at least some basic knowledge of the Python programming language, including the object-oriented programming paradigm as this is what we will be using a lot in this book.

Kivy book

The book covers all the basic elements of Kivy that you have to know, like widgets, layouts, Kivy ids and properties, graphics, screens, animation, sound. Finally we’ll deploy the app to Windows. It is pretty comprehensive and after you finish it, I’m sure you’ll be able to create your own awesome GUI apps of any kind, not just games.

I hope you will have at least as much fun reading the book as I had writing it.

As far as this Kivy series is concerned, the following parts will contain the most important elements covered in the book. However, some elements will be presented in a simplified way here on my blog or omitted completely.

____________

If you are interested, you can purchase the book in four versions. Here are the links:

1) ebook – pdf version on my website – in full color

Here you can see the description of the book, sample graphics from the book and the full table of contents.

2) ebook – Kindle version on Amazon – in full color

3) paperback version on Amazon – in black and white

4) paperback version on Amazon – in full color

*****

And Now Let’s Move On…

Defining Custom Widgets in Code

Actually there are two ways we can do it. We can either create our custom widgets or use layouts. In our project we’ll stick to the latter, but still let’s have a look at custom widgets as well for a minute.

We can create our custom widgets by inheriting from the Widget class. Then we can put a couple of the standard widgets you already know into it. So, technically speaking we’re still going to have just one widget, but it will look as if there were more.

Suppose we need a text input and two buttons, so three widgets altogether. So, let’s create a custom widget that contains these three elementary widgets. Here’s the Python code with comments:

# File name: main.py

import kivy
from kivy.app import App

# We're going to inherit from the Widget class, so we must import it first.
from kivy.uix.widget import Widget

# Here is the inheritance. 
class MyCustomWidget(Widget):
    # We're going to implement it in the kv file.
    pass

class HelloWorldApp(App):
    def build(self):
        # Now we want our custom widget to be returned.
        return MyCustomWidget()

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

As you can see, we’re leaving the implementation for the kv file. And here it is:

# File name: helloworld.kv

# We need to implement the MyCustomWidget class here.
<MyCustomWidget>: 
    # There's going to be a text input and two buttons.
    TextInput:
        hint_text: 'Type Something'
    Button:
        text: 'Press Me'
    Button:
        text: 'Press Me Too'

In the code above MyCustomWidget is contained in angled brackets because this is the so-called rule class notation. This is how we use classes in Kivy language. The text input and the two buttons are not contained in angled buttons because these are instances. This is how we make the distinction between class and instance in Kivy.

Now look what happens when I run this code:

custom widgets with overlapping internal widgets

Where is the text input and the first button? Well, they are there, just stacked on top of each other under the second button. Our custom widget fills the whole window and its particular components are all in the lower left corner of the window. We’re now coming to the point where we have to do something with the position and size of our widgets.

Your Panda3D Magazine

Make Awesome Games and Other 3D Apps

with Panda3D and Blender using Python.

Cool stuff, easy to follow articles.

Get the magazine here (PDF).

Widget Position and Size

All widgets have the pos and size properties which you can use to position and scale them appropriately. Each of the two properties is defined as a pair of fixed coordinates in pixels. In Kivy the coordinate (0, 0) is in the bottom left corner, which is just like in math, but unlike in a lot of other frameworks.

Anyway, let’s set the positions of the three components of our custom widget so that we can see them all:

# File name: helloworld.kv

<MyCustomWidget>:     
    TextInput:
        hint_text: 'Type Something'
        # Position the text input 20 pixels from the left 
        # and 150 pixels from the bottom. This is where the 
        # lower left corner of the widget will be.
        pos: 20, 150
    Button:
        text: 'Press Me'
        # Position the button 20 pixels from the left 
        # and 20 pixels from the bottom.
        pos: 20, 20
    Button:
        text: 'Press Me Too'
        # Position the button 200 pixels from the left 
        # and 20 pixels from the bottom.
        pos: 200, 20

If you now run the program, this is the window that you will get:

widget position and size

Now at least you can see all three widgets. I don’t particularly like their dimensions, though, so let me scale them using the size property:

# File name: helloworld.kv

<MyCustomWidget>:     
    TextInput:
        hint_text: 'Type Something'
        pos: 20, 150
        # Make the text input 300 x 40 pixels.
        size: 300, 40
    Button:
        text: 'Press Me'
        pos: 20, 20
        # Make the button 120 x 40 pixels.
        size: 120, 40
    Button:
        text: 'Press Me Too'
        pos: 200, 20
        # Make the button 120 x 40 pixels.
        size: 120, 40

If you run the program again, the widgets will be scaled in a better way, I think:

scaled widgets

Other Properties in Custom Widgets

If you like, you can also change other properties, like the color:

# File name: helloworld.kv

<MyCustomWidget>:     
    TextInput:
        hint_text: 'Type Something'
        pos: 20, 150
        size: 300, 40
    Button:
        text: 'Press Me'
        pos: 20, 20
        size: 120, 40
        color: .7, .6, .4, 1
    Button:
        text: 'Press Me Too'
        pos: 200, 20
        size: 120, 40
        color: .7, .6, .4, 1

Now it looks like so:

other properties in custom widgets - color

Custom Buttons

As you can see, the code in the kv file is quite repetitive. In particular, the two buttons share some properties. So, if you decide to change the color, for example, you have to do it twice. What if there were 10 buttons?

Python Jumpstart Course

Learn the basics of Python, including OOP.

with lots of exercises, easy to follow

The course is available on Udemy.

The kv code is easy to refactor. Let’s just create a custom button and then use it in the custom widget. One more time I’d like to emphasize that you use the angled brackets to define a class, but you don’t use any brackets if you want to instantiate a widget of that class. This will be more visible in the following code:

# File name: helloworld.kv

# First let's define our custom button. It should 
# inherit from the Button class. To make a class
# inherit from another class in kv, you must use
# the following syntax:
###
### <subclass@class>
###
# where subclass inherits from class.

<MyCustomButton@Button>:
    # Here we're going to put all the code that is 
    # repeated in each button. Then you can remove
    # this part of the code from the two instances
    # of the custom button.
    size: 120, 40
    color: .7, .6, .4, 1

<MyCustomWidget>: 
    TextInput:
        hint_text: 'Type Something'
        pos: 20, 150
        size: 300, 40
    # Now you will use the custom button that we
    # just created.
    MyCustomButton:
        text: 'Press Me'
        pos: 20, 20
    MyCustomButton:
        text: 'Press Me Too'
        pos: 200, 20

If you now run the program, it will work just like before. Alternatively, you could define the MyCustomButton class in Python code and inherit it from Button there. Then you don’t need the inheritance in kv anymore. Let’s just do it so that you can see how. Here’s the Python code:

# File name: main.py

import kivy
from kivy.app import App

# We're going to need the Button.
from kivy.uix.button import Button
from kivy.uix.widget import Widget

# Here is the inheritance. 
class MyCustomButton(Button):
    # We're going to implement it in the kv file.
    pass

class MyCustomWidget(Widget):
    # We're going to implement it in the kv file.
    pass

class HelloWorldApp(App):
    def build(self):
        return MyCustomWidget()

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

and the kv file:

# File name: helloworld.kv

# The inheritance is now in Python code, so you can
# remove it from here.
<MyCustomButton>:
    size: 120, 40
    color: .7, .6, .4, 1

<MyCustomWidget>: 
    TextInput:
        hint_text: 'Type Something'
        pos: 20, 150
        size: 300, 40
    MyCustomButton:
        text: 'Press Me'
        pos: 20, 20
    MyCustomButton:
        text: 'Press Me Too'
        pos: 200, 20

If you run the program now, it will again work exactly as it did before.

Blender Jumpstart Course

Learn the basics of 3D modeling in Blender.

step-by-step, easy to follow, visually rich

The course is available on Udemy and on Skillshare.

The Problem with Fixed Positions

If you try resizing the window, you will notice that the widgets are not adjusted accordingly. In most scenarios you want them to be more flexible. The pos property that we’ve been using so far uses fixed positions in pixels, but you can also assign it a relative value. To this end you can use two internal Kivy variables, root and self.

The root and self Variables

What are the two variables then? You may be familiar with the self variable that is used in Python as a reference to the object in which it’s used. It works exactly the same in kv. The other variable, root, is a reference to the widget class at the top of the hierarchy.

Have a look at this code snippet again:

<MyCustomWidget>: 
    TextInput:
        hint_text: 'Type Something'
        pos: 20, 150
        size: 300, 40
    MyCustomButton:
        text: 'Press Me'
        pos: 20, 20
    MyCustomButton:
        text: 'Press Me Too'
        pos: 200, 20

If you use the self variable inside one of the MyCustomButton instances, it’ll be a reference to that very instance. If you use root inside a MyCustomButton instance, it will be a reference to MyCustomWidget, which is at the top of the hierarchy. We use dot notation to access the particular properties of the widgets referenced by self and root.

Having said that, we can now rewrite the kv code so that the positions of the internal widgets (the text input and the two custom buttons) are more flexible. Here’s the kv file:

# File name: helloworld.kv

<MyCustomButton>:
    size: 120, 40
    color: .7, .6, .4, 1

<MyCustomWidget>: 
    TextInput:
        hint_text: 'Type Something'

        # The x coordinate of the text input should be equal
        # to the x coordinate of the whole custom widget (which
        # fills the whole window). Let's add a 20-pixel offset
        # to it so that it doesn't cling to the window.

        # The y coordinate should be relative to the top border
        # of the custom widget (which is referenced by root).
        # If you leave root.top, you won't see the text input
        # widget because its lower side will be on the top
        # border of the window and the rest of it will be outside
        # the custom widget and outside the window. This is why 
        # we have to subtract the text input's height.
        # Additionally let's subtract 20 to give some more room.

        pos: root.x + 20, root.top - self.height - 20

        size: 300, 40
    MyCustomButton:
        text: 'Press Me'
        # The button should be relative to the root's x and y
        # coordinates. The y coordinate is the one at the bottom
        # of a widget. We'll add a 20-pixel offset again.
        pos: root.x + 20, root.y + 20
    MyCustomButton:
        text: 'Press Me Too'
        # The other button will be relative to the right border 
        # of the custom widget. Again, we have to subtract its
        # width in order to see it. We'll add an offset again.
        # The y coordinate of the button will be relative to the
        # bottom border of the root widget, just like before.
        pos: root.right - self.width - 20, root.y + 20

If you run this program, this is what it will look like:

the root and self variables

Now try resizing the window and you’ll see that this time the internal widgets are moving along. This is because their positions are no longer fixed, but rather relative to different parts of the root widget.

And here’s a visualization of all the values that you can see in the code. This should help you understand what is what even more:

root and self visualization

If this way of positioning widgets doesn’t seem very clear and intuitive to you, the good news is that most of the time we’ll be using a more straightforward way of doing it, but first we need to talk about layouts. We’ll be talking about all the properties that you can use to position widgets in more detail when we need them. For now it’s enough to know how the x, y, top and right properties are used, maybe also height and width. And now let’s move on to Kivy layouts.


Spread the love

Leave a Reply