Use QML to build smart graphical applications

Tutorial – Writing QML Apps

Article from Issue 211/2018
Author(s):

QML makes writing desktop applications a breeze, and you can later compile them into standalone programs that work more or less anywhere.

In last month's issue, I looked at how to use a phone's GPS to live-stream a geographic location to a computer that didn't have a GPS [1]. Then, I took the whole thing on the move with an interactive map you could put together with QML [2], Qt's declarative scripting language.

In this article, I'll look at QML in more depth and build a graphical sample application from scratch. To get started, you can install the bits and pieces you need in Debian/Ubuntu-like distros with:

sudo apt install qt5-default qtdeclarative5-qtquick2-plugin

The qt5-default package pulls in all the Qt libraries you need, and qtdeclarative5-qtquick2-plugin brings in the tools you need to write QML applications.

In most distros, installing qtdeclarative5-qtquick2-plugin will also install QML's multimedia package, but if it doesn't, look for something like qml-module-qtmultimedia in your package manager and install that, too. QML's multimedia module contains all the necessary pieces to interface with your webcam and play video in a window.

Webcam

For this tutorial, you'll be making a webcam application with some functionality. Your app will end up being a bit like Cheese [3] or Kamoso [4], but with fewer bells and whistles. (Kamoso, incidentally, is built using Qt.)

The first thing you'll want to do is bring the webcam stream into a window. Listing 1 includes the modules you need for your application to work (lines 1-3). You will always need QtQuick, as well as QtQuick.Window if you are going to create graphical applications. The former contains all the basic code for QtQuick applications, and the latter has the libraries for creating and managing windows. The QtMultimedia module, on the other hand, contains a more specialized library of objects that manage video, audio, and, yes, the webcam on your computer.

Listing 1

WebCam v1

01 import QtQuick 2.9
02 import QtQuick.Window 2.2
03 import QtMultimedia 5.8
04
05 Window {
06  title: "WebCam"
07  visible: true
08
09  Camera {
10   id: camera
11  }
12
13  VideoOutput {
14   source: camera
15   anchors.fill: parent
16    }
17 }

Next, you open a Window (line 5). This construct is a QML type [5] that does what it says: It creates a window. Everything that affects that window goes between its curly brackets (lines 5-17), including the name you want to appear in the window's title bar (line 6) and whether it is visible or not (line 7). Both title and visible are default Window attributes, or properties in QML parlance. Many other properties pertain to the window's size, position, opacity, background color, and so on. You can add your own properties to a QML type, too, as you will see later.

Line 9 declares a Camera type [6], and line 10 gives it a name. The Camera type construct comes with the QtMultimedia module. Unless you tell it otherwise (using a Camera property called deviceId), Camera uses your default webcam.

The VideoOutput type (line 13) also comes with the QtMultimedia QML module [7]. Its job is to show a video stream. The VideoOutput attribute source tells it where to get the stream. In this case, it is getting it from the Camera object (line 14). The source could also come from a video file.

The anchors property, which is common to most graphical elements in QML, makes sure the video feed fits inside the window. Anchors [8] are one of the ways to position graphical objects. They allow you to place one object relative to another and set their size. In this case, you want the video "screen" to adapt (fill from side to side and/or top to bottom) to the size of the parent object (i.e., the object containing the video, or the Window). Because the VideoOutput object is anchored, if you resize the window, the video will resize with it.

Save the code as webcam.qml and then run it with the command:

qmlscene webcam.qml

The video feed does not actually fill the window (Figure 1) because the default fillMode is set to "PreserveAspectFit", which scales the feed to fit the container while preserving its proportions. However, if you were to add the line

fillMode: "Stretch"
Figure 1: Your first (very basic) webcam application.

to the VideoOutput section, the video would distort to fit the window, and resizing the window will distort the feed further.

A third option for the fillMode property is "PreserveAspectCrop", which scales uniformly by preserving the aspect ratio but cropping off the edges.

Impressively, only 17 lines of clear code gets you a window with a video stream from your webcam, which speaks volumes on how beginner-friendly QML is.

However, the window is tiny, and the app isn't very usable – you don't even have a button to close it. You can solve both problems by applying some layout and adding another element: a button.

Multiple Elements

The QML type used to create a button is called, perhaps unsurprisingly, Button. At its most basic, it looks like the Close button built in Listing 2 (lines 33-35).

Listing 2

WebCam v2

01 import QtQuick 2.9
02 import QtQuick.Window 2.2
03 import QtMultimedia 5.8
04
05 import QtQuick.Controls 1.4
06 import QtQuick.Layouts 1.3
07
08 ApplicationWindow {
09  title: "WebCam"
10  visible: true
11
12  property int vpwidth: 640
13  property int margin: 10
14
15  Camera {
16   id: camera
17  }
18
19  ColumnLayout {
20   anchors.fill: parent
21
22   Item{
23    implicitWidth: vpwidth + margin * 2
24    implicitHeight: baseLayout.sourceRect.height * (vpwidth / baseLayout.sourceRect.width) + margin * 2
25
26    VideoOutput {
27     id: baseLayout
28     anchors.fill: parent
29     source: camera
30    }
31   }
32
33   Button {
34    text: "Close"
35   }
36  }
37 }

You will have noticed some other changes, of course: Because you have more than one graphical element in your window, you need to apply some layout elements to make sure everything is in its correct place; for example, so the button isn't placed on top of the video.

When you use ColumnLayout (lines 19-36), elements are stacked one on top of the other, as opposed to the Row or RowLayout types, which place objects next to one another. In this case, you place the VideoOutput element at the top (lines 26-30) and then a simple Button element below that (lines 33-35) with the "Close" label (line 34).

To get everything to work, you need to make a few extra tweaks, including the addition of two more modules: QtQuick.Controls (line 5) contains buttons, text boxes, radio buttons, and other elements, and QtQuick.Layouts (line 6) provides the layout tools used above.

Also notice that the containing graphical element has changed from Window to ApplicationWindow (line 8). The ApplicationWindow type provides more features than the plain old Window type. For example, you can easily implement a menubar and a status bar. More importantly for this project, though, it lets you adjust the window size better to the content.

Talking of size, you are going to set the size of your video screen this time around, rather than let the size of the window affect the content. To do this, you create two new properties: vpwidth (i.e., the video panel width; line 12) and margin (line 13), which will be a border around the video panel.

As for the height of the screen, you don't initially know the proportions of the video to be shown, so you can't hard-code it in. However, you can calculate it, because VideoOutput has a property called sourceRect that contains the dimensions of the video feed. If you establish the width of the feed as 640 pixels, as shown on line 12, you can use that to calculate the height of the container, as shown on line 24.

The container, by the way is an Item type. A QML Item is like an invisible frame in which you can enclose other graphical elements. In this case, it comes in handy because the VideoOutput type does not have the implicitWidth and implicitHeight properties needed to resize the panel – but Item does, which means you can set the size of the "screen" using the Item container (lines 23 and 24) and then use anchors.fill: parent to resize the video within the Item (line 28).

To summarize the changes:

  • Lines 5 and 6 add two new modules: QtQuick.Controls for the button and QtQuick.Layouts for the tools to organize elements within the window.
  • Line 8 defines ApplicationWindow as your main container because it resizes more gracefully to the content.
  • Lines 12 and 13 add two new properties: vpwidth, which holds the width you want for your screen, and margin, which holds the thickness of the border around the screen.
  • Line 19 creates a columnLayout, which you expand to fill the whole window. This layout will hold the screen and the button, one above the other.
  • Line 22 opens an Item container, which is an invisible frame to hold the video screen.
  • Lines 23 and 24 set the size of the Item container.
  • Line 27 names the VideoOutput, so you can refer to it (and get its size) elsewhere in the code.
  • Lines 33-35 implement a button.

When you run the script, you'll see a much more reasonably sized application with a Close button (Figure 2), which doesn't do anything yet.

Figure 2: A proper webcam application with a Close button.

Adding Action

Up to now, you have added all the gears and cogs of the mechanism, but you haven't actually added the thing that makes everything move and work together. QML uses JavaScript to put a project's wheels into motion.

Apart from being declarative, QML is event driven, which means that the JavaScript executes when something happens (e.g., a button is clicked). In fact, that is what you are going to do here: Wait for the Button event onClicked [9] and then call the built-in routine Qt.quit() [10], which closes the current application.

The code could be:

onClicked: Qt.quit()

or:

onClicked: {Qt.quit()}

If you want to show a message on the command line about the application closing, you could do as in Listing 3. The takeaway is that onClicked is the declarative part of your code, and the bit after the colon is the imperative JavaScript.

Listing 3

Close Button

[...]
Button {
 text: "Close"
 onClicked: {
  console.log ("WebCam App is closing...");
  Qt.quit();
 }
}
[...]

As for where to place the code, you also have several options. You might be tempted to bang the code right in the Button section and that, as shown in Listing 3, would work fine.

However, for the sake of tidiness, modularity, and keeping the declarative part of your code separate from the imperative part, you might want to use the QML Connections type [11] (Listing 4).

Listing 4

WebCam v3

01 import QtQuick 2.9
02 import QtQuick.Window 2.2
03 import QtMultimedia 5.8
04
05 import QtQuick.Controls 1.4
06 import QtQuick.Layouts 1.3
07
08 ApplicationWindow {
09  title: "WebCam"
10  visible: true
11
12  property int vpwidth: 640
13  property int margin: 10
14
15  Camera {
16   id: camera
17  }
18
19  ColumnLayout {
20   anchors.fill: parent
21
22   Item{
23    implicitWidth: vpwidth + margin * 2
24    implicitHeight: baseLayout.sourceRect.height * (vpwidth / baseLayout.sourceRect.width) + margin * 2
25
26    VideoOutput {
27     id: baseLayout
28     anchors.fill: parent
29     source: camera
30    }
31   }
32
33   Row {
34    Button {
35     id: snap
36     text: "Snap"
37    }
38
39    Button {
40     id: close
41     text: "Close"
42    }
43   }
44
45  }
46
47  Connections {
48   target: snap
49   onClicked: camera.imageCapture.capture ()
50  }
51
52  Connections {
53   target: close
54   onClicked: Qt.quit()
55  }
56 }

With Connections (lines 47-50), you can give your active component (in this case, your button) an id (line 40) and then refer to it using the Connections property target (line 52). The QML parser now knows to which component an event and its associated JavaScript refers.

You will notice another button in Listing 4: "Snap" (lines 34-37; Figure 3). When you click on Snap, it takes a snapshot of whatever VideoOutput is showing. You can see the code associated with the snap button on lines 47-50. The Camera subtype imageCapture allows you to take snapshots with its capture() method, as you can see on line 49.

Figure 3: WebCam app with buttons that work, including the one that takes snapshots.

The images are saved into the default directory or your system. In the case of Linux, that is the Pictures folder in your home directory.

Another property, videoRecorder [12] lets you record a video from the webcam. You might want to experiment with that, too.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Free Software Projects

    Voice over IP on the Internet gives communication a personal touch, but it takes applications like Cheese and WebcamStudio to exploit the creative potential of Internet telephony.

  • aMSN in Linux

    aMSN lets Linux users communicate with associates on Microsoft instant messaging networks. In this article, we'll show you how to reach out to your friends in the empire.

  • Amsn

    Many Webcam owners use MSN Messenger by Microsoft for video messaging. Linux users can run Amsn to connect eye to eye.

  • Yahoo Chat Tools

    Kopete and Gyachi bring Yahoo videochat services to Linux.

  • Screencasters

    To make audiovisual content easy to understand, you can use screencast programs, of which Linux has a few.

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95

News