This article continues the tutorial we started in Part 1. In the first part, we set up a Kivy project and developed a very basic Kivy application. We also discussed version control to aid in project management. In this part, we’ll be designing an actual user interface for a Jabber client and begin to implement it in the KV language.
Make sure you have completed part 1, or if you want to skip the preliminaries, you can clone the end_part_one tag of the Orkiv git repository and start from there. It’s probably a good idea to name a new branch for your working changes so your master branch doesn’t diverge from my upstream repository. Remember to activate the virtualenv you created in Part 1 (or create one if you’re starting from the git clone.)
git clone https://github.com/buchuki/orkiv.git git checkout end_part_one git checkout -b working_changes source venv/bin/activate
Table Of Contents
Here are links to other parts of the tutorial. I’m sorry for the navigation faux pas; blogs are not the best interface for multi-part articles.
- Part 1: Introduction to Kivy
- Part 2: Creating a form
- Part 3: Interacting with widgets and the user
- Part 4: Code and Interface Improvements
- Part 5: Rendering a Buddy List
- Part 6: ListView Interaction
- Part 7: Receiving messages and interface fixes
- Part 8: Width based layout
- Part 9: Deploying your Kivy application
In general, before developing a user interface, it is a good idea to know what you want it to look like. I see this project having three main views. When the user first opens the application, they’ll see an Account Details screen to provide their jabber server and login credentials. We won’t bother with saving these details since this tutorial series is focused on Kivy interface development, not persistence. Once they have logged in, the other two views are a Buddy List and the individual Chat Window.
I would like to develop the app to show the buddy list and chat windows side by side if the screen is large enough (eg: a laptop screen or large tablet in horizontal orientation), but only one window at a time on small screens. It is very important to evaluate screen size when developing cross platform applications; it is generally not desirable or feasible to render the same view on large monitors and small phones. We’ll look at this feature in later tutorials, but knowing that we want to plan for it now will affect how we implement our design.
All of these windows are pretty simple. In this second part, we will focus on the account details screen. It will be a simple form with labels on the left and fields for entering the various details on the right. The fields covered will include server, username, and password. Below these fields will be a Login button. Normally I’d sketch the interface on a piece of paper, but since this looks like every other Jabber client on the market, I don’t think it’s necessary.
The Account Details Form Widget
In Kivy, virtually every object that gets displayed on the screen is a widget. Just a few examples include:
- The label we rendered in the first tutorial
- The text input fields and buttons we’ll render in this tutorial
- Layout classes that contain other widgets and decide where they should be displayed
- Complicated tree views such as file pickers
- Movie and photo renderers
- Tabbed boxes that display different widgets depending on the selected tab
It is easy to make custom widgets, and to access all widgets, whether custom or otherwise, from KV language files. We’ll be creating a new widget to contain our entire Account Details form. Start by adding a new import and class to
from kivy.uix.boxlayout import BoxLayout class AccountDetailsForm(BoxLayout): pass
Running the code with this addition won’t change anything in the output, since the newly created widget hasn’t been added to the root window. However, there are a few things we need to discuss before we change the KV language file to use this widget. You might be asking yourself why we created a new widget instead of adding a
BoxLayout widget (whatever that is!) directly to the root of the KV language file
orkiv.kv. While it would have been trivial to do this, it would have made putting a new widget (such as the Buddy List) on the root screen more difficult. Further, when it comes time to attach events to the Login button, we can make it a method of this new class, where it makes the most sense.
BoxLayout is a very simple widget that simply takes the available space and places all the child widgets in it from left to right or top to bottom. By default, each widget gets an equal amount of space, but it’s also possible to make certain widgets have specific sizes or percentages of space or to expand to fill available area.
BoxLayout is one of several kinds of layouts available in Kivy. A layout is simply a container widget that holds other widgets and knows how to position those child widgets in a specific pattern. We’ll be seeing a
GridLayout shortly. You can read about other Kivy layouts in the Kivy API Reference.
orkiv.kv to render this new widget as the child form instead of the label we used before. We’ll also add a couple labels to the
AccountDetailsForm class so you can see the relationship between root widgets and classes in the KV Language:
AccountDetailsForm: <AccountDetailsForm>: Label: text: 'Hello World' Label: text: 'Another World'
There’s a couple things going on here. The root widget of the
Orkiv app is defined as a
AccountDetailsForm. This is followed by a colon in case we wanted to add other child widgets to the object, but in this case, it’s just followed by a blank line. Then we define the structure of the child widgets of the
AccountDetailsForm class itself. We know it’s a class and not a root widget because the class name is surrounded in angle brackets (< and >). Further, one app can only have one root widget. We just defined that root to be pointing at a single
AccountDetailsForm. However, note that a class can be styled once and added in more than one place. Just as our
AccountDetailsForm has two labels, it would be possible (though silly, from an interface perspective) to create a container widget that contains two
AccountDetailsForms. The point is, an
AccountDetailsForm, once its layout has been defined, can be used just like any of the built-in widgets supplied with Kivy.
If you run
python orkiv now, it will render the two labels side by side, each taking a full half the window. Note that there are actually three widgets being rendered; first, the
AccountDetailsForm (which is a
BoxLayout by inheritance) is rendered to fill the whole screen, and then it lays out the two labels as child widgets.
However, that’s not the AccountDetailsForm we were looking for! Let’s create our first nontrivial Kivy Language class. Replace the
class in your
orkiv.kv with the following:
<AccountDetailsForm>: orientation: "vertical" GridLayout: cols: 2 Label: text: "Server" TextInput: Label: text: "Username" TextInput: Label: text: "Password" TextInput: password: True Button: text: "Login"
It’s time to discuss just how the Kivy language lays things out. In my mind, it uses a box model similar to HTML, except instead of ugly HTML tags, indentation is used to specify what widgets go inside other widgets. Also, unlike HTML, style information is included in the
.kv file instead of an external
.css file. Though it’s not a hard and fast rule, style information (called properties in Kivy) typically begins with a lowercase letter, while child widgets start with a capital.
Look at the first level of indentation under the
<AccountDetailsForm> declaration. There are three items;
Button. The former counts as “style information”. Remember that the
AccountDetailsForm class extends BoxLayout. One of the attributes on BoxLayout is “orientation”; we are telling BoxLayout here that we want a vertical layout.
This vertical box layout then contains two widgets, a
GridLayout and a
Button. The GridLayout spaces all it’s children equally. We just have to tell it that there are 2 columns (the
cols property) and then start adding our objects, which are displayed from left to right and wrap to the next row in the grid after every two widgets. We add
TextInputs. Each label has a
text property that we set using a further level of indent. The
TextInput objects don’t require any extra properties except the password.
Later, we’ll have to give these objects some identifiers so we can refer to them in code. For this initial prototyping stage, we can start with the bare minimum. If we render the above example, we end up with a recognizable, but rather ugly form:
Let’s try to make the form a little less ugly:
<AccountDetailsForm>: orientation: "vertical" height: "200dp" size_hint_y: None GridLayout: cols: 2 row_default_height: "40dp" row_force_default: True spacing: "10dp" padding: "10dp" Label: text: "Server" TextInput: Label: text: "Username" TextInput: Label: text: "Password" TextInput: password: True Button: size_hint_y: None height: "40dp" text: "Login"
All we’ve done here is add some styling information to make the layout a little more friendly. This can be complicated as we want the app to look good on a variety of devices at a variety of resolutions and screen sizes. The main thing is to ensure all the widgets have a known height. As you might guess, this can be accomplished by adding a
height property to each of the widgets. However, doing this alone will not work because
BoxLayout likes to think of itself as in charge of deciding what size a widget should be. Without any styling, heights in a vertical
BoxLayout are calculated as the number of widgets divided by the available screen space. However, it’s possible to tell
BoxLayout to make certain widgets take a higher percentage of available space by giving it a
size_hint_y attribute, which should be a float and defaults to 1.0. Unfortunately, for textboxes and buttons, percentages don’t really make a lot of sense, since we have no idea what size the window or screen of a particular advice is going to be, but we do know we want them to be about the same height on each screen.
So in addition to setting the
height attribute, we also have to set
None for those widgets to prevent
BoxLayout from ignoring the
height property and laying things out by percentages instead.
You’ll also not that we are specifying the heights using strings that end in the suffix “dp”. It’s possible to provide integer values here, which would be interpreted as the number of pixels high the button is. However, due to the massive variance in screen resolutions on modern devices, it’s not really feasible to use pixels as a measurement. A login button that is 40 pixels high and looks good on a desktop monitor would look very small, indeed, on a “retina” display tablet.
Therefore, Kivy provides us with the concept of “display pixels”; hence the “dp” in the strings. Display pixels should be about the same size on every device; on a standard desktop monitor they generally map to a single pixel, but on higher density displays, they will be multiplied by a scaling factor. There are other metric properties such as
in that you can use, but I generally find it most convenient to think in display pixels.
Note also that instead of supplying heights for each of the
TextInput widgets inside the grid layout, we cheated and used the
row_default_height attribute so all rows have the same height. For this to work, you also have to set
row_force_default to True.
Finally we added
padding properties to the
GridLayout to add a 10dp space between child widgets and a “frame” around the whole
GridLayout as well. If we run this example, it looks like this:
There’s one more thing I’d like to do before ending this part of the tutorial. As you can see above, the form is showing up at the bottom of the window, no matter what size the window is. That might look a bit bizarre on a tablet or even phone screen. It would be better if the entire form was anchored to the top of the window.
As an exercise, take a few moments to experiment (remember to use a different git branch!) and see if you can figure out how to move the form to the top by yourself. The next paragraph contains some hints if you get stuck, and the working code is linked after that. (I’m not about to tell you not to cheat, since it really depends what your personal goals are in reading this tutorial.)
There are probably multiple ways to do this, and I’m not even certain my solution is the best one. But I suggest making the
AccountDetailsForm into an AnchorLayout and anchor a child
BoxLayout to the top of the form. Note that you’ll have to change both files to make this happen.
If you’re still confused, have a look at the full changeset at the changeset on github.
That’s it for part 2 of this tutorial, which focused on laying out widgets in the KV Language. In part 3, we’ll figure out how to log into an XMPP library and render a buddy list. Note that I don’t actually know how to do this, so we’ll have to figure it out together!
I don’t expect the collected parts of this tutorial to be big enough to publish as a book, so I’m hosting them for free here on my blog. If you liked the tutorial and would like to see similar work published in the future, please support me. I plan on one day reducing my working hours at my day job to devote more time to open source development and technical writing. If you think this article was valuable enough that you would have paid for it, please consider supporting me in one or more of the following ways:
- Purchase my books: Python 3 Object Oriented Programming and Hacking Happy
- Tip me on Gittip
- E-mail dusty at archlinux dot ca to let me know you would fund a book about Kivy via crowd funding
If you aren’t in the mood or financial position to help fund this work, at least share it on your favorite social platforms!