Module 2: Starting a QML project with Clickable

In this module you’ll learn the basics of starting a QML project with Clickable. You’ll learn the different parts of a QML app and how they work together. And at the end of this module you’ve written a simple shopping list app.

You can find this module’s code of the app in the GitLab repository of the course.

1. Introduction

QML (Qt Modeling Language) is a language for designing user interfaces for applications. You describe the layout of your application in a declarative language similar to JSON or CSS. This way you add buttons, labels, lists, images, and so on.

To define the behaviour of your app, you add code. You can write this code in various programming languages, such as JavaScript or Python. In the previous module you already saw what choices Clickable gives you.

In this module you’ll create a QML app with JavaScript for a simple shopping list.

2. Starting from the QML Only template

Create a new Clickable project like you did in the previous module, but this time answer the questions with some more attention:

clickable create

Clickable first asks you to choose a template. These templates are based on the language you want to use. Choose 1 - QML Only for this module, because you’re going to develop a QML app with JavaScript code.

The next question is about the app title. Give it a short but descriptive title, such as Shopping List. After this, enter a description. This can be a bit longer than the title, but keep it relatively short too, such as A simple shopping list app.

Then comes the app name. This should consist of lowercase letters only, for instance shoppinglist. Then comes your name, also in lowercase letters. After this, you can enter your full name with capitals, as well as your email address.

Then you choose the license you want to use to publish your app. You can find more information about some popular open source licenses on the web site of the Open Source Initiative. After this, enter the current year for the copyright notice (you can just confirm the current year with a press on Enter), and just confirm the default choices for Git Tag Versioning and Save as Default.

Enter the directory the Clickable command created and open the project in Visual Studio Code:

cd shoppinglist
code .

Visual Studio Code asks you if you trust the authors of the files in this directory. Confirm with a click on "Yes, I trust the authors".

3. Project structure

At the left, Visual Studio Code shows you some files and directories that Clickable has created from the chosen app template. Click on all directories to reveal their contents. You should see this:

The project structure of a QML Only app created by Clickable
Figure 1. The project structure of a QML Only app created by Clickable
  • The directory assets has a file with an Ubuntu logo by default. You can replace this if you want to give your app another logo.

  • The directory po has a pot file you can use to translate your app.

  • The directory qml has a file Main.qml with the QML code for your app’s user interface.

If you take a look at the other files in the project, you’ll see that Clickable has put all information you have entered after the clickable create command in the right places, sich as the license in the file LICENSE, and the name, title, description and maintainer in the file manifest.json.in. The latter file is used by Open Store if you upload your app to the store later.

4. The Main.qml file

Let’s have a look at the Main.qml file. If you chose 1 - QML Only as the template for your app, Clickable creates this file:

import QtQuick 2.7
import Ubuntu.Components 1.3
//import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import Qt.labs.settings 1.0

MainView {
    id: root
    objectName: 'mainView'
    applicationName: 'shoppinglist.koenvervloesem'
    automaticOrientation: true

    width: units.gu(45)
    height: units.gu(75)

    Page {
        anchors.fill: parent

        header: PageHeader {
            id: header
            title: i18n.tr('Shopping List')
        }

        Label {
            anchors {
                top: header.bottom
                left: parent.left
                right: parent.right
                bottom: parent.bottom
            }
            text: i18n.tr('Hello World!')

            verticalAlignment: Label.AlignVCenter
            horizontalAlignment: Label.AlignHCenter
        }
    }
}

4.1. Import statements

The import statements in the beginning make a number of components available in your application. QtQuick is the standard library for writing QML applications, providing all basic components for creating user interfaces. Ubuntu.Components includes some components to give your application an Ubuntu Touch look and feel. And Qt.labs.settings provides persistent application settings. For instance, this allows your application to remember its window sizes and positions.

If you need any other specific functionality in your QML app, you just add extra import statements in the beginning of your QML file. You can find the right import statement in the functionality’s documentation. Have a look at the API documentation for Ubuntu Touch and more specifically Qt’s documentation.

You can specify the version of the imported component. Generally you just use the versions defined in Clickable’s templates.

4.2. Comments

One of the import statements is preceded by two slashes (//). This is a comment, which means that everyting after the two slashes on this line is ignored by the build tools. In this case the comment is used to 'deactivate' a statement, but you can also use it to add an explanation to your code. We recommend you to always comment more difficult parts of your code.

You can also create multi-line comments. Clickable actually creates one in the beginning of the file, with the app’s license:

/*
 * Copyright (C) 2022  Koen Vervloesem
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 3.
 *
 * shoppinglist is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

Everything between /* and */ is ignored by the build tools.

4.3. The MainView element

MainView is the root element of your user interface. It has a couple of properties. For instance, applicationName sets the name of this application. It should match the name field of the file manifest.json.in.

Other interesting properties are the main view’s width and height, which define the initial size of the app’s main window. The dimensions are not defined in terms of pixels, but as grid units. A grid unit is independent of the device’s screen resolution. This guarantees that your interface elements have the same size on all screens.

So if you define the MainView width as follows:

width: units.gu(45)

The method units.gu returns the number of pixels 45 grid units correspond to. The units property is is a global instance of the Units component from Ubuntu.Components.

If you look at the documentation of the MainView component, you’ll see that it has some other properties that aren’t defined in the template. For instance, you can change the window’s background color by adding the following property:

backgroundColor: UbuntuColors.graphite

If you build your app with clickable desktop, you’ll see that the background color has changed to grey:

You can change the background color of the MainView element
Figure 2. You can change the background color of the MainView element

If you want to know which colors you can use this way, consult the documentation of Ubuntu.Components UbuntuColors. This component offers a color palette of a dozen of colors following the Ubuntu brand guidelines. Graphite is recommended for a darker background.

4.4. The Page element

The MainView element contains a Page element. This represents a single view in an Ubuntu Touch application.

The first line you see in this page is:

anchors.fill: parent

Anchors are an important concept in Qt Quick, used to position layout items. What anchors.fill does is to set the left, right, top and bottom to the left, right, top and bottom of the target item, in this case parent. The latter refers to the element that contains this element, in this case MainView. So essentially this line says: let the page fill the whole main view.

Next the page defines a header property. This is a PageHeader element. The id property is used to refer to this page header later. And the title property defines the title to display in the page header.

You could define the title just as follows:

title: 'Shopping List'

However, Clickable’s template is already prepared for internationalization (i18) support, so it defines it like this:

title: i18n.tr('Shopping List')

Ubuntu.Components i18 is a property that provides internationalization support. Its method tr translates the specified text to the user’s language.

This means that you don’t have to change your QML code if you want to translate your app to another language, such as French or Dutch. You only have to create .po file based on the .pot file in the po directory, with the string after msgstr as the translation for the string after the corresponding msgid. The .po file should be named after your language code, for instance fr.po for French and nl.po for Dutch.

The page header can also have a subtitle:

subtitle: i18n.tr('Never forget what to buy')

If you rebuild the app, you see that the page header now has a subtitle in a slightly smaller font and other color than the title:

A page header can also have a subtitle
Figure 3. A page header can also have a subtitle

Under the hood, Clickable has also regenerated the .pot file and added the new translatable string 'Never forget what to buy' to it:

#: ../qml/Main.qml:39 shoppinglist.desktop.in.h:1
msgid "Shopping List"
msgstr ""

#: ../qml/Main.qml:40
msgid "Never forget what to buy"
msgstr ""

#: ../qml/Main.qml:53
msgid "Hello World!"
msgstr ""
Each translatable string in the .pot file is provided with the location of that string in your code. This is helpful for translators if they’re not sure in which context the string is used.

If you want to change the colors of the page header, you need to add a StyleHints element:

header: PageHeader {
	id: header
	title: i18n.tr('Shopping List')
	subtitle: i18n.tr('Never forget what to buy')
	StyleHints {
		foregroundColor: UbuntuColors.orange
	}
}

This foregroundColor property of the StyleHints element gives the title an orange foreground color:

The foreground color of the page header is set using style hints
Figure 4. The foreground color of the page header is set using style hints

Other useful properties of the StyleHints element are backgroundColor and dividerColor.

4.5. The Label element

The Page element also contains a Label element, which is showing the text "Hello World!". First come some anchors:

anchors {
    top: header.bottom
    left: parent.left
    right: parent.right
    bottom: parent.bottom
}

We bind the top of this label to the bottom of the element with id header, and set the left, right and bottom to the left, right and bottom of the parent element (the Page element). The result is that the label comes right under the page header and fills the rest of the page.

Then the text property specifies the text to show in the label, and the verticalAlignment and horizontalAlignment properties center the label vertically (Label.AlignVCenter) and horizontally (Label.AlignHCenter). That’s why the text "Hello World!" is shown in the center of the part under the page header.

You can also change the label’s font size. For a larger text, this becomes:

font.pixelSize: FontUtils.sizeToPixels("large")
The default text size is "medium". Have a look at Ubuntu.Components FontUtils for all available values.

5. Adding a button

So now that you know the meaning of all elements in the default QML-only template, it’s time to add some other elements.

One of the most interesting components is a button. In Ubuntu Touch apps, you can create this as a Ubuntu.Components Button. So let’s remove the Label element in your QML file and replace it by the following button:

Button {
    id: buttonAdd
	anchors {
		top: header.bottom
		right: parent.right
		topMargin: units.gu(2)
		rightMargin: units.gu(2)
	}
	text: i18n.tr('Add')
}

This anchors the top of the button to the bottom of the page header with id header and the right of the button to the right of the parent (the page). It also adds a margin of two grid units at the top and to the right. The result looks like this:

The button is anchored to the top right
Figure 5. The button is anchored to the top right

Now a button isn’t worth anything if nothing happens when you click on it, so let’s add some behavior to the button. You do this by adding an onClicked property to the button:

onClicked: console.log(text + i18n.tr(' button clicked'))

If you build this app and run it on your desktop with clickable desktop, click on the button. You’ll see the following message in the console where you typed the Clickable command:

qml: Add button clicked

So every time you click on the button, the JavaScript code after onClicked is executed.

6. Adding a text field

A button is an interesting component to trigger a simple action, but another useful component is a TextField. This is used to enter the text you want to add to your shopping list.

So add a TextField element like this to the page:

TextField {
	id: textFieldInput
	anchors {
		top: header.bottom
		left: parent.left
		topMargin: units.gu(2)
		leftMargin: units.gu(2)
	}
	placeholderText: i18n.tr('Shopping list item')
}

This shows a text field with the placeholder text "Shopping list item", positioned at the top left with a margin of 2 grid units to the page header and the left side of the page:

The text field is anchored to the top left
Figure 6. The text field is anchored to the top left

If you now start typing text into this text field, the placeholder text disappears.

The next step is doing something with the text you’ve typed in when you click on the button. Let’s first just show your text in the console. This is done by replacing the onClicked handler in the Button element by:

onClicked: console.log(i18n.tr('Shopping list item: ' + textFieldInput.text))

7. Adding a list

In the last part of this course, we’re going to add the text you typed into the text field to a shopping list instead of just showing it on the console.

A list is a bit more complex than the components we’ve seen before, because it actually consists of three parts:

  • a model, which contains the data

  • a view, which displays the data

  • a delegate, which dictates how the data should appear in the view

You can read more about the important concepts of a model, a view and a delegate in the document Models and Views in Qt Quick.

If we apply this approach to a list, this means we need a ListModel, a ListView, and a ListItem as a delegate.

Let’s first create a model showing some static data, right before the Page element:

ListModel {
	id: shoppinglistModel

	ListElement {
		name: "apples"
	}
	ListElement {
		name: "milk"
	}
	ListElement {
		name: "bread"
	}
}

A ListElement represents an item in a list, containing role definitions. The name of a role has to begin with a lowercase letter, and the value should be a constant, such as a string in this case. We chose name as the name of the role, but you could also name it otherwise. You could also add other roles in the same list element, such as a price, but in our simple shopping list app we only need one role.

Contrary to all the other elements we’ve seen, the ListModel element is not in the Page element because it doesn’t display anything.

Then create the view, right under the TextField element:

ListView {
	id: shoppinglistView
	anchors {
		top: textFieldInput.bottom
		bottom: parent.bottom
		left: parent.left
		right: parent.right
		topMargin: units.gu(2)
	}
	model: shoppinglistModel

	delegate: ListItem {
		width: parent.width
		height: units.gu(3)
		Text {
			text: name
            anchors {
                left: parent.left
		        leftMargin: units.gu(2)
                verticalCenter: parent.verticalCenter
            }
		}
	}
}

This ListView element refers to the element with ID shoppinglistModel as its model. You also specify that this view contains ListItem elements as a delegate, with a Text element containing name as its text. This name comes from the model you defined earlier.

If you now build this application, it will show a list of the three items you defined in the list model:

The ListView shows the items from the ListModel
Figure 7. The ListView shows the items from the ListModel

But what if you want to add your own items dynamically to the list? Just remove the hardcoded list elements from the list model, so the latter is reduced to the following:

ListModel {
	id: shoppinglistModel
}

Then in the Button element, change the handler for the onClicked signal to:

onClicked: shoppinglistModel.append({"name": textFieldInput.text})

So when you click on the button, it adds a name role with the contents of the text field as its value to the shopping list model. This name is then shown in the Text element of the ListItem delegate.

In the next module, we’re going deeper into some other QML components.