Use QML to build smart graphical applications
Tutorial – Writing QML Apps
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"
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 andQtQuick.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, andmargin
, 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.
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.
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
(incl. VAT)
Buy Linux Magazine
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters
Support Our Work
Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.
News
-
AlmaLinux 10.0 Beta Released
The AlmaLinux OS Foundation has announced the availability of AlmaLinux 10.0 Beta ("Purple Lion") for all supported devices with significant changes.
-
Gnome 47.2 Now Available
Gnome 47.2 is now available for general use but don't expect much in the way of newness, as this is all about improvements and bug fixes.
-
Latest Cinnamon Desktop Releases with a Bold New Look
Just in time for the holidays, the developer of the Cinnamon desktop has shipped a new release to help spice up your eggnog with new features and a new look.
-
Armbian 24.11 Released with Expanded Hardware Support
If you've been waiting for Armbian to support OrangePi 5 Max and Radxa ROCK 5B+, the wait is over.
-
SUSE Renames Several Products for Better Name Recognition
SUSE has been a very powerful player in the European market, but it knows it must branch out to gain serious traction. Will a name change do the trick?
-
ESET Discovers New Linux Malware
WolfsBane is an all-in-one malware that has hit the Linux operating system and includes a dropper, a launcher, and a backdoor.
-
New Linux Kernel Patch Allows Forcing a CPU Mitigation
Even when CPU mitigations can consume precious CPU cycles, it might not be a bad idea to allow users to enable them, even if your machine isn't vulnerable.
-
Red Hat Enterprise Linux 9.5 Released
Notify your friends, loved ones, and colleagues that the latest version of RHEL is available with plenty of enhancements.
-
Linux Sees Massive Performance Increase from a Single Line of Code
With one line of code, Intel was able to increase the performance of the Linux kernel by 4,000 percent.
-
Fedora KDE Approved as an Official Spin
If you prefer the Plasma desktop environment and the Fedora distribution, you're in luck because there's now an official spin that is listed on the same level as the Fedora Workstation edition.