© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
J. M. WillmanBeginning PyQthttps://doi.org/10.1007/978-1-4842-7999-1_13

13. Working with Qt Quick

Joshua M Willman1  
(1)
Sunnyvale, CA, USA
 

Graphical user interfaces nowadays can be found on a multitude of devices, including desktop computers, mobile devices, and small touchscreens controlled by microcontrollers. While UIs are designed to fit the functional and technical needs of each device, the demands of these platforms have inspired The Qt Company to continue to build a set of scalable, polished, dynamic, and visually stunning tools for building user interfaces.

In this chapter, you will
  • Get an overview of the QtQuick and QtQml modules and the QML programming language

  • Learn how to write and run simple applications using QML and PyQt

  • Find out how to build QML components and use them in other .qml files

  • Utilize different methods for arranging QtQuick elements using QML

  • Experiment with different QML types to create applications

  • Use simple transformations to animate objects

This chapter’s aim is to provide an overview of QtQuick and Qt’s QML language. If your aim is to continue using QtWidgets, then this chapter may not provide information that is beneficial at this current moment in your development stage. However, for new PyQt developers, the hope is that this chapter can provide some useful ideas and insights about what the latest versions of Qt and PyQt have to offer.

Note

For those readers using macOS, you may have issues running the applications in this chapter if you are using the Z shell, also known as zsh. The bash shell used to be the default on macOS until recently. If you run into problems due to zsh, you can switch to the bash shell by entering chsh -s /bin/bash/ in the command line. To switch back to zsh when you are finished, you will need to enter the command chsh -s /bin/zsh. Just be aware that when you do switch between shells, you will either need to install PyQt6 from PyPI or edit the paths in bash to locate PyQt6 and your other Python packages.

Outlining QtQuick and QML

While desktop applications are still a large part of Qt, a considerable amount of work has gone into creating a toolkit in Qt 6 that resembles the more fluid, dynamic, and animated UIs of mobile and embedded devices. As you follow along in this chapter (or explore the links within this chapter that lead to even more information about creating GUIs with QtQuick), you’ll notice a number of similarities with QtWidgets. You’ll notice visual elements such as buttons and combo boxes. You’ll see windows, dialogs, and menus. Layouts, among other methods, are also used in QtQuick to arrange elements. We’ll even take a brief look at animating objects.

What then is QtQuick? Before answering that question, let’s first try and clear up some confusion that might occur early on. To build applications, we’ll need to clarify three important terms: QML, QtQuick, and QtQml.

When using QtQuick, you’ll also hear about the Qt Modeling Language (QML). QML is the declarative, markup language specifically designed for user interfaces that QtQuick is built upon. The language is used to create highly fluid and dynamic interfaces and visual effects similar to those seen on mobile devices. A QML file, also referred to as a document ,1 is comprised of a declarative, hierarchical tree of elements and has support for various JavaScript expressions.2 A QML document can either create a window for an application or build a reusable element called a component. In essence, QML is used to build the UI objects and specify how they relate to one another.

With the relationships built, the QtQuick module is used to describe the look and behaviors of a GUI’s elements. It is worth noting that, similar to what we have seen in QtWidgets, QtQuick also contains the classes for the visual canvas, graphical elements, layouts, data models and views, animations, graphics, and so much more!

In addition to QtQuick, we’ll leverage the QtQml module to provide a QQmlEngine object to access the QML content that we create. An engine, along with a QQmlContext object for passing data to the QML components, is used to expose Python to the QML code that we create. Luckily, QtQml provides a convenience class, QQmlApplicationEngine, that combines the engine and the context. We’ll use QQmlApplicationEngine to load our QML files.

By exposing Python to the QML components, we can use QML to write the front-end code and use Python and PyQt to build the back-end logic.

Note

A quick note about the structure of this chapter. To focus on efficiency, we’ll first cover how to create a basic QML document, followed by adding simple visual elements such as text and images to get you comfortable. From there, we’ll discuss how to organize elements in the GUI. The next step is to find out how to create a QML window, complete with a menu bar and an introduction to signals and slots in QML. Lastly, we’ll take a brief glimpse at animating elements.

If after finishing this chapter you find that QtQuick and QML interest you, or if you have any questions along the way, the following links can provide more context and guidance:

Before creating any code, let’s take a quick look at some common elements and properties we’ll be using to create QtQuick applications.

Elements in QtQuick

Elements , also called types, are the built-in building blocks used to create GUIs in QtQuick. The term “elements” encapsulates both visual and nonvisual types. Visual elements have geometry and can be arranged in the GUI, while nonvisual elements are typically used to control the visual elements.

To create an instance of a type in QML, simply call the element followed by a pair of braces. An example of creating a Rectangle element is shown in the following line:
Rectangle {...}

Object types will always begin with a capital letter. Each element also has a number of particular properties, such as width, height, color, and text, that are used to specify different aspects of the element. Properties can also be used to position elements, specify geometric transformations, and handle state changes. These properties are defined between the braces.

Table 13-1 describes some common and interesting elements found in QtQuick. A full list of QML types is found at https://doc.qt.io/qt-6/qmltypes.html.
Table 13-1

Selection of common QtQuick types

Element

Description

Item

The base element from which all visual elements derive. Does not itself create a visual element. Instead, Item defines the properties for other types. Can also be used as a container for other elements

Rectangle

Inherits Item and adds visual properties, including color, border, and radius

Image

Displays images. Provides the source property to specify the image URL and fillMode for controlling resizing behavior

Text

Displays text. Includes text, font, style, and alignment properties

MouseArea

Nonvisual type that is needed to capture mouse events. MouseArea also includes properties such as width and height

Flickable

Nonvisual type that acts as a draggable and flickable surface for its children. Perfect for showing large numbers of child objects on a scrollable surface

Component

Used along with the Loader type to dynamically create and load components within a component document. Components are typically created and instantiated using separate QML files

Let’s take a look at how to use a few of the elements in Table 13-1 to create a QML document with a .qml extension in the following section.

Introduction to the QML Language and Syntax

One of the best ways to get started understanding a new language is to jump right into some code. In this section, we’ll take a look at a QML document and break apart the tree-like structure of QML. If you have never coded in JavaScript or any other declarative language, there is no need to worry. There is a very simple pattern to writing QML code.

By creating the simple GUI seen in Figure 13-1, you’ll have learned some very important concepts, including how to
  • Import QML object types into a document (it is also possible to import JavaScript resources)

  • Create QML parent and children elements

  • Define properties for different kinds of elements

  • Manually arrange elements using the visual canvas’s coordinate system

  • Create a simple, reusable component

  • Understand simple QML syntax concepts

Figure 13-1

Custom QtQuick component with text

After working with QtWidgets, you may notice that there is no title in the window’s title bar. This is because this example does not actually create a window; instead, we are creating a QML component with Rectangle as the parent object. Not only does Rectangle not have a title property, but by creating a component, we could also instantiate this object in another QML document if we wanted to do so. You’ll see an example of this in the “Layout Handling in QML” section.

Explanation for QML Language and Syntax

The first step is to import the object types in Listing 13-1. If you have ever used QtQuick before, then you’ll know that import statements in Qt 5 also needed a version number. Thankfully, this is no longer necessary in Qt 6,3 and the version of QtQuick will match your version of PyQt6.

Single-line comments in QML use double forward slashes, //. Multi-line comments start with /* and end with */.

To begin, create a new file with the extension .qml.
# qml_intro.qml
// Import necessary modules
import QtQuick
Rectangle {
    id: rect
    width: 155; height: 80
    color: "skyblue"
    Text {
        text: "Small Component"
        x: 10; y: 30
        font.pixelSize: 16
        font.weight: Font.DemiBold
        color: "black"
    }
}
Listing 13-1

Simple QML document that demonstrates basic syntax principles

A QML document contains the hierarchy of objects, where each object can have an id and a name, properties, methods, and even signals and signal handlers. The root object is the top-level item in the document.

For this example, there are two objects. The first is the Rectangle element, which acts as the parent for the second, the Text element. One of the benefits of using braces as well as indentation in QML is that you can comprehend the hierarchy of the objects just by looking at the code. Creating parent-child relationships in QML is simply a matter of instantiating an object within the braces of a parent object.

But what happens if you want a child to access the values of other elements other than the parent? To find out, let’s have a look at how to specify property values.

Defining Properties of an Element

When a value is assigned to a property in QML, the assignment is denoted with a colon, :, where the left side of the colon is the property name and the right side is its value. For example, the width property of the Rectangle is equal to 155.

You’ll notice that the first attribute defined for Rectangle is id, which is an identifier that can be used throughout the QML document to interact with that object. Here, the Rectangle element is identified as rect. Always use lowercase for the first letter of identifiers to avoid confusion between QtQuick elements and other components.

For Rectangle, let’s specify its width and height and give it a color other than the default white. Properties that are defined on the same line are separated using a semicolon. The Text element’s text, font, and color properties are set. Just like in QtWidgets, you can also style text using HTML tags.

The Coordinate System

The visual canvas in QtQuick is a two-dimensional surface for arranging objects. The top-left pixel of the window is (0, 0). While the canvas is 2D, it also has z-ordering to handle the ordering of objects when they overlap.

What is interesting is that child elements are relative to their parents, meaning that a child inherits the coordinate system of the parent and is arranged based on its parent’s top-left corner. An example of this can be found at https://doc.qt.io/qt-6/qtquick-visualcanvas-coordinates.html.

There are a variety of ways to organize elements in the GUI window, many of which will be discussed in the “Layout Handling in QML” section. For this example, we can manually position objects in the window by specifying the x and y values for the Text object.

Tip

If you downloaded Qt Creator back in Chapter 8, then you’ll be able to visualize the document at this point. You could either open the file in Qt Creator, or you could locate the qml executable that is included in the Qt directory on your computer. Next, run the following command in the shell:

$ <Qt_dir>/Qt/<path-to-qml>/qml qml_intro.qml.

Your <path-to-qml> may be similar to 6.0.0/clang_64/bin, where 6.0.0 denotes the Qt version. (Your path and version may also be different.) You can replace qml_intro.qml with any QML document you want to run.

Using what we have learned, let’s find out how to continue adding more features to QtQuick UIs and learn how to present documents using Python in the following sections.

Building and Running QML Components

This section is broken down into four major parts:
  1. 1.

    Creating and visualizing QML components using QQuickView

     
  2. 2.

    Building reusable components

     
  3. 3.

    Positioning elements in QML

     
  4. 4.

    Creating and visualizing QtQuick windows using QQmlApplicationEngine

     

Each one of the examples uses one of two classes to load the QML files. The first is QQuickView, which is a convenience class that loads a QML file and provides a window to display QML scenes. QQuickView works for visualizing components.

But what should you do if you want to create a QtQuick application with a window, a menu bar, and other UI elements? That is where QQmlApplicationEngine comes into play. We’ll discover in the “Building and Loading QML Windows” section how to build applications with windows.

Along the way, you’ll also find out how to
  • Add images to your QtQuick applications

  • Position objects using anchors

  • Enable mouse handling with MouseArea

  • Find out how to use JavaScript expressions

  • Use QtQuick Controls for creating windows and adding additional components such as buttons and check boxes to GUIs

  • See how to use signals and signal handlers in QML

For all of the examples in this chapter, we’ll need at least two files: one .qml document for designing the UI and one .py script that handles loading the QML file and possibly the back-end functionality. More complex applications could have multiple components that are called in a main.qml file.

Creating and Loading QML Components

We’ll need to create the following two files to build the GUI seen in Figure 13-2:
  1. 1.

    images_and_text.qml – A QML component composed of images and text

     
  2. 2.

    quick_loader.py – A Python script for quickly loading general QML components

     
Figure 13-2

QtQuick component containing images and text

Make sure to download the images folder from the GitHub repository before getting started.

Explanation for Creating QML Components

First, create a new QML document. The Item element in Listing 13-2 serves as the root for this component. The id identifier has been assigned as such. The width and height properties are then specified. Since Item types do not display visual content, we’ll need to assign the object a child element, perhaps a Rectangle or an Image.
# images_and_text.qml
// Import necessary modules
import QtQuick
Item {
    id: root
    width: 340; height: 420
    // Create an Image that will serve as the background
    Image {
        anchors.fill: root
        source: "images/background.jpg"
        fillMode: Image.PreserveAspectCrop
    }
    // Create a container Rectangle to hold text and images
    Rectangle {
        id: container
        width: 300; height: 120
        y: 40 // Vertical offset
        /* Comment out the following line and uncomment the
        line after to view the Rectangle */
        color: "transparent"
        //color: "lightgrey"
        anchors.horizontalCenter: root.horizontalCenter
        anchors.topMargin: 40
        Image {
            id: image
            anchors.centerIn: container
            source: "images/qtquick_text.png"
            sourceSize.width: container.width
            sourceSize.height: container.height
        }
        Text {
            text: "It's amazing!"
            anchors {
                top: image.bottom
                horizontalCenter: image.horizontalCenter
            }
            font.pixelSize: 24
            font.weight: Font.DemiBold
            color: "#3F5674"
        }
    }
}
Listing 13-2

Creating a QML document with images and text

By adding an Image element to the Item as a child, the image can easily be set as the component’s background. QtQuick makes this process effortless using anchors.

Positioning Elements with Anchors

Anchors are properties that allow you to arrange objects in a GUI by specifying the relation of one element with respect to its parent or sibling objects. Imagine an object having lines along its left, right, top, and bottom sides as well as lines going vertically and horizontally through its middle. We can use the anchors to define the relationship between the elements and those lines.

The following list describes commonly used anchor properties:
  • anchors.fill – Convenience property for one item to have the same geometry as another, thereby filling up the space of the other element (while also preserving aspect ratio and cropping)

  • anchors.centerIn – Positions an object in the center of another object

  • anchors.left, anchors.right – Positions an object to the left or right of another object

  • anchors.top, anchors.bottom – Positions an object on the top or bottom of another object

  • anchors.verticalCenter, anchors.horizontalCenter – Arranges an object to the vertical or horizontal center of an another object

There are also ways to add margins between the objects using anchors.

With the first Image type created in Listing 13-2, anchors.fill: root binds the Image.anchors property to the root object’s size.

A binding specifies the value of a property in QML and is denoted with a colon, :, similar to assigning a regular value. The difference is that binding creates a dependency between the property and the other object. Bindings in QML can be used to access built-in properties, make function calls, and even use built-in JavaScript objects like Math.

More information about anchors can be found at https://doc.qt.io/qt-6/qtquick-positioning-anchors.html.

Adding Images in QtQuick

The source property of Image is used to specify the path to a desired image file. The fillMode property defines what happens to the image when its size does not match that of the item. The value PreserveAspectCrop preserves the image’s aspect ratio while also cropping the image, if necessary. Other fillMode values include Stretch, PreserveAspectFit, Tile, and Pad (which does not transform the image).

The Rectangle type serves as a container for the remaining Image and Text objects. The string "transparent" can be assigned to the color property. This is a neat little trick to remove the background if you are using PNG images with transparent backgrounds. If you would like to see how the Image and Text types fit within the container object, you can switch the comments on the color lines.

The sourceSize can be used to force an image to scale down or up to a certain size. Here, the size of the qtquick_text.png image is forced to remain its original size, but centered in container. This prevents distortion of the text.

One thing to note is that you can group properties. You can see this in the Text object. Grouping the properties could have also been done with the font and sourceSize properties in this example.

With the UI built, we now need a way to load the QML document.

Explanation for Loading QML Components

QQuickView provides a window for displaying a QtQuick user interface where all you need to do is to pass the URL of the .qml file to QQuickView .

In order to make a general Python script that we can pass QML files to as arguments when running the application, we’ll also use the Python argparse module4 in Listing 13-3.

To begin, let’s import a few PyQt6 classes into a new Python script. Since we are not using QtWidgets, there’s no need to import QApplication. Instead, QGuiApplication is used for GUI-related applications that are not using widgets.
# quick_loader.py
# Import necessary modules
import sys, argparse
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQuick import QQuickView
def parseCommandLine():
    """Use argparse to parse the command line for specifying
    a path to a QML file."""
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file", type=str,
        help="A path to a .qml file to be the source.",
        required=True)
    args = vars(parser.parse_args())
    return args
class MainView(QQuickView):
    def __init__(self):
        """ Constructor for loading QML files """
        super().__init__()
        self.setSource(QUrl(args["file"]))
        # Get the Status enum's value and check for an error
        if self.status().name == "Error":
            sys.exit(1)
        else:
            self.show()
if __name__ == "__main__":
    args = parseCommandLine() # Return command line arguments
    app = QGuiApplication(sys.argv)
    view = MainView()
    sys.exit(app.exec())
Listing 13-3

Code for loading a general QML component using QQuickView

The QQuickView method setSource() is used to load the QML file. If no errors are found, then show() is used to display the GUI.

You can load either Listing 13-1 or 13-2 and visualize the components. To load a file, run the following command in your shell:
$ python3 quick_loader.py -f images_and_text.qml

Windows users can use python instead of python3.

To load Listing 13-1, run:
$ python3 quick_loader.py -f qml_intro.qml

We’ve just seen how to build a simple component. Now, let’s start to find out how to make components that are reusable and interactive.

Creating Reusable Components

Being able to create custom and reusable components is an essential part of GUI development. This holds true even in QtQuick. Figure 13-3 displays a simple custom Rectangle that we’re going to build to demonstrate how to use the mouse event handlers.
Figure 13-3

Reusable QtQuick component that changes color when clicked

This is only a taste of the kind of components that you could build. Components can consist of classic UI elements, data views, animations, and more.

Explanation for Creating Custom Components

Listing 13-4 is a new QML document that contains a Rectangle type with a single Text child. What sets this component apart from Listing 13-1 is the addition of the MouseArea type.

For this example, create a file called ColorRect.qml.

Tip

Be sure to use camelCasing when naming components that you plan to reuse.

# ColorRect.qml
import QtQuick
Rectangle {
    id: root
    width: 80; height: 80
    color: "#1FC6DE" // Cyan-like color
    border.color: "#000000"
    border.width: 4
    radius: 5
    Text {
        text: root.color
        anchors.centerIn: root
    }
    // Click on Rectangle to change the color
    MouseArea {
        anchors.fill: parent
        onClicked: {
            color = '#' + (0x1000000 + Math.random()
                * 0xffffff).toString(16).substr(1, 6);
            // Uncomment the following line for Listing 13-9
            //root.clicked()
        }
    }
}
Listing 13-4

Code for the ColorRect component

The current color of the Rectangle is displayed on Text by binding the text property to root.color.

Making an Element Interactive with Mouse Handling

Support for various input devices, including keyboard, mouse, touch, and stylus devices, is possible in QtQuick.

A MouseArea is a nonvisual item that is used in conjunction with visual types. Clicking on an item that also includes a MouseArea object could be used to trigger signals, check the location of the cursor, or drag and drop items if drag and drop is enabled.

The anchors.fill property is used so that the user can click anywhere on the parent object (which is the Rectangle). If the Rectangle is clicked, then the clicked signal is emitted, and the onClicked signal handler is called. (Signal handlers are methods that handle signals. Their names are simply the signal with on added to the front and camelCased.) We’ll explore signals a bit more in the “Signals and Signal Handlers” subsection.

In onClicked, a JavaScript expression is used to select a random color and convert it to a hexadecimal string that represents a new color. The color value is used to update the value of the ColorRect.

Using Listing 13-3 (quick_loader.py), you can run view the ColorRect component by running the following line in the shell:
$ python3 quick_loader.py -f ColorRect.qml

In the following section, ColorRect will be used to build a few example applications that demonstrate how to organize elements in QtQuick.

Layout Handling in QML

Organizing visual elements in a GUI is important for creating cohesion. Objects in QtQuick can be arranged in a few different fashions. In this section, we’ll discuss four of them. A few example documents will also be created to help visualize how to use them in code.

There are a few different approaches for arranging items in QML. The following list talks about each one:
  • Manual positioning can be used to explicitly specify the x and y coordinates of QtQuick types. This method is extremely efficient for GUIs that are not dynamic. This method was demonstrated in Listing 13-1.

  • Anchoring uses the boundaries and relative positions of parent and sibling elements to arrange objects. This topic was covered back in the “Positioning Elements with Anchors” subsection.

  • Positioners are containers that are used to arrange children items in columns, rows, or grids. The “Using Positioners to Position Elements” section gives an overview of positioners.

  • Layout managers are used to organize items in a UI. The main difference between layout managers and positioners is that layouts also handle resizing. Layouts can be imported into a QML document by using import QtQuick.Layouts. More information about layout managers in QtQuick can be found at https://doc.qt.io/qt-6/qtquicklayouts-index.html.

Using Positioners to Position Elements

Positioners share similar behavior with layout managers. Like layout managers, positioners are used to organize items in a specific form like a row or column. Unlike layout managers, however, positioners act like containers for the widgets that become their children and don’t manage the sizes of their children items.

Table 13-2 lists the four commonly used positioners.
Table 13-2

Four of the standard positioner types

Positioner

Description

Column

Positions children elements in a single column

Row

Positions children elements in a single row

Grid

Positions children elements in a grid

Flow

Positions children elements side by side, and children can be wrapped top to bottom or left to right

In addition, positioners contain a few properties for managing the spacing between elements, applying padding, and specifying the direction for laying out the items.

More information about positioners is found at https://doc.qt.io/qt-6/qtquick-positioning-layouts.html.

Explanation for Using Column and Grid Positioners
A Column positioner is used in Figure 13-4 to arrange a few ColorRect components from Listing 13-4, (ColorRect.qml). The elements are stacked on top of each other and the spacing between each ColorRect is set using the spacing property.
Figure 13-4

Elements arranged in a Column positioner

Listing 13-5 creates a simple Rectangle parent in a QML document that contains three ColorRect instances arranged in a Column positioner.
# columns.qml
import QtQuick
Rectangle {
    width: 200; height: 300
    color: "grey"
    Column {
        id: column
        anchors.centerIn: parent
        spacing: 6
        // Add custom components to Column
        ColorRect { }
        ColorRect { }
        ColorRect { color: "pink"}
    }
}
Listing 13-5

Code for the Column positioner example

Also, if you look at the third ColorRect instance, you’ll notice how properties of components can still be modified when instantiating them. Try switching Column to Row or Flow in Listing 13-5 and take a look at the difference in the GUI.

Figure 13-5 is an example of the Grid positioner.
Figure 13-5

Elements arranged in a Grid positioner

Listing 13-6 differs only slightly from Listing 13-5. Notice how for the Grid positioner you’ll need to specify the number of rows and columns in the grid.
# grids.qml
import QtQuick
Rectangle {
    width: 200; height: 200
    color: "grey"
    Grid {
        id: grid
        rows: 2; columns: 2
        anchors.centerIn: parent
        spacing: 6
        // Add custom components to Column
        ColorRect { }
        ColorRect { }
        ColorRect { radius: 20 }
        ColorRect { }
    }
}
Listing 13-6

Code for the Grid positioner example

Even though these two examples use Rectangle types to illustrate how to use positioners, they are more efficiently used when arranging buttons, dials, or other UI elements.

In the following section, you’ll get closer to creating well-rounded classic desktop applications by finding out how to create menu bars and display dialogs in QtQuick.

Building and Loading QML Windows

While QQuickView is useful for displaying components, the QQmlApplicationEngine class is a more convenient way to load a single QML document where the root object is a window. What this means is that instead of using Rectangle or Item types as the root, we’ll be using the ApplicationWindow control . Doing so will provide us with additional tools, such as a menu bar and toolbars.

Note

QQuickView does not support using window types like ApplicationWindow as a root item. To display a scene in a window, you’ll need to use QQmlApplicationEngine.

For this simple application, we’ll create a window that displays local images. The images can be selected through a FileDialog instance that is created when selecting the Open menu option. This is shown in Figure 13-6.
Figure 13-6

An Image Viewer GUI created with QtQuick

Previous QML documents have only imported QtQuick. In order to include common GUI elements in a QtQuick application, we’ll need to import a new class.

QtQuick Controls

Controls are similar to the widgets in QtWidgets. They are the buttons, check boxes, sliders, and other graphical UI elements we have come to expect for interacting with applications.

Table 13-3 lists only a portion of the controls that are available in QtQuick.
Table 13-3

Selection of common QtQuick.Controls types

Controls

Description

Action

Describes the actions that can be assigned to menu items and toolbars

ApplicationWindow

Window with additional functionality for adding a menu bar, header, and footer

Button

Push button that can be clicked by the user to perform an action

CheckBox

Check button that can be toggled on and off

ComboBox

Presents a drop-down list for selecting options

Dial

Circular dial that can be rotated to select a value

Dialog

Pop-up dialog box with standard buttons and title

DialogButtonBox

A button box that is used for specifying buttons in a Dialog

Frame

Provides a visual frame for organizing other controls

GroupBox

Provides a visual frame with a title for organizing other controls

MenuBar

Creates a menu bar in a window

RadioButton

Radio buttons that are autoexclusive and can be toggled on and off

Slider

Used for selecting a value using a sliding handle on a track

TabBar

Creates a tab bar for switching between different views

Tumbler

Wheel that can be spun to select values

For a full list of the Controls types, you can check out https://doc.qt.io/qt-6/qtquick-controls2-qmlmodule.html.

Explanation for Creating QML Windows

For this desktop application, we’ll be using a few of the tools that we have used before for creating windowed applications, namely, a menu bar, actions for the menu items, and dialogs for loading image files to be displayed in the GUI’s window.

To have access to these tools, we’ll need to import some new QtQuick classes into a new QML document like in Listing 13-7. Controls gives us access to the UI elements, while Dialogs is used to create the FileDialog.
# windows_and_controls.qml
// Import necessary modules
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs
ApplicationWindow {
    title: "QtQuick Image Viewer"
    width: 800; height: 500
    visible: true
    // Create the menu bar and its actions
    menuBar: MenuBar {
        Menu {
            title: "&File"
            Action {
                text: "&Open"
                onTriggered: openImage()
            }
            MenuSeparator {}
            Action {
                text: "&Quit"
                onTriggered: Qt.quit()}
        }
    }
    // Define the signal for opening images
    signal openImage()
    // Define the signal handler for opening images
    onOpenImage: {
        fileDialog.open()
    }
    // Define a FileDialog for selecting local images
    FileDialog {
        id: fileDialog
        title: "Choose an image file"
        nameFilters: ["Image files (*.png *.jpg)"]
        onAccepted: {
            // Update displayed image
            image.source = fileDialog.selectedFile
        }
        onRejected: {
            fileDialog.close()
        }
    }
    /* Create a container Rectangle for the image
    in order to add margins around the image’s edges */
    Rectangle {
        id: container
        anchors {
            fill: parent
            margins: 10
        }
        Image {
            id: image
            anchors.fill: container
            source: "images/open_image.png"
            fillMode: Image.PreserveAspectFit
        }
    }
}
Listing 13-7

Creating the QtQuick image viewer GUI to illustrate how to use windows and controls

The ApplicationWindow is the root object of the application and will be loaded in the next section with QQmlApplicationEngine. ApplicationWindow also includes the title property. It is most important that you do not forget to include the visible: true line when using QQmlApplicationEngine. If you forget this property, then your window will remain hidden since, by default, an ApplicationWindow is not visible.

An Image element is used in the ApplicationWindow to display the selected image to the user. While an Image can be directly placed in the window, using the anchors.margins property of Rectangle allows for a subtle border to surround the image.

Creating a Menu Bar

The MenuBar control is used to create the window’s menu bar. Then Menu is used to create the File menu, and finally Action controls are added to the File menu along with a MenuSeparator control to separate the Open and Quit actions.

Signals and Signal Handlers

The controls in QtQuick.Controls, just like widgets, communicate using signals and slots, referred to as signal handlers in QtQuick. It is easy to figure out which signals are connected to which signal handlers by their names. Signal handlers have an additional on tacked onto the front and are camelCased. For example, the Button control has the clicked signal that triggers the onClicked signal handler whenever the button is clicked.

Take a look at the actions in menuBar. To create a custom signal in a QML type, we need to use the signal keyword. Here, we create a new signal called openImage that has no parameters.

For this example, the ApplicationWindow control’s openImage signal is emitted whenever the Open menu item is triggered. This then connects to the onOpenImage signal handler where a fileDialog instance is opened. Just so you know, the onTriggered signal handler could be directly connected to fileDialog.open().

The Quit menu item closes the entire application using Qt.quit().

Using FileDialog to Open Files

Dialogs are used either to gather or to present information to a user. For this example, a FileDialog opens so that the user can select .png or .jpg image files.

The dialog’s lower right corner contains two buttons: OK and Cancel. If the user clicks OK, then the accepted signal is handled by onAccepted. This will update the image.source URL and the image using the value of fileDialog.selectedFile. Otherwise, the rejected signal from the Cancel button will connect to onRejected and close the dialog.

Explanation for Loading QML Windows

Similar to Listing 13-3, Listing 13-8 is a Python script for loading general QML documents. The difference is that Listing 13-8 loads the files using QQmlApplicationEngine. This means you can only pass as arguments the path of QML documents where the top-level item is a window, such as ApplicationWindow.

We’ll start by importing classes from PyQt6 into a new Python script, including QQmlApplicationEngine from the QtQml module.
# qml_loader.py
# Import necessary modules
import sys, argparse
from PyQt6.QtCore import Qt, QUrl
from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine
def parseCommandLine():
    """Use argparse to parse the command line for specifying
    a path to a QML file."""
    parser = argparse.ArgumentParser()
    parser.add_argument("-f", "--file", type=str,
        help="A path to a .qml file to be the source.",
        required=True)
    args = vars(parser.parse_args())
    return args
class MainView(QQmlApplicationEngine):
    def __init__(self):
        super().__init__()
        # Order matters here; need to check if the object was
        # created before loading the QML file
        self.objectCreated.connect(self.checkIfObjectsCreated,
            Qt.ConnectionType.QueuedConnection)
        self.load(QUrl(args["file"]))
    def checkIfObjectsCreated(self, object, url):
        """Check if QML objects have loaded without errors.
        Otherwise, exit the program."""
        if object is None:
            QGuiApplication.exit(1)
if __name__ == "__main__":
    args = parseCommandLine() # Return command line arguments
    app = QGuiApplication(sys.argv)
    engine = MainView()
    sys.exit(app.exec())
Listing 13-8

Code for loading a general QML window using QQmlApplicationEngine

The MainView class inherits QQmlApplicationEngine. Once the QML file is passed to the QQmlApplicationEngine method load(), the objectCreated signal will be emitted when all objects have loaded. The enum Qt.ConnectionType.QueuedConnection ensures that the signal is queued until the event loop can deliver it to the slot. This is done to make sure we can check for any errors before loading the file.

If loading is successful, the window will open. Otherwise, an error will return an object with a value of None in the checkIfObjectsCreated() slot.

To load Listing 13-7, run the following command in the shell:
$ python3 qml_loader.py -f windows_and_controls.qml

In the final section, we’ll have a little fun and make some objects spin and change sizes using transforms.

Using Transformations to Animate Objects

A transformation is the general term that refers to manipulating the shape, size, and/or position of a point, line, or geometric shape. Chapter 11 demonstrated how to use transformations to animate objects in QtWidgets. Now, we’ll start to find out how to perform some basic transformations in QtQuick.

Explanation for Simple Transformations

For this first example seen in Figure 13-7, we’ll demonstrate how to use the Item type’s rotation and scale properties to perform basic transformations on objects.
  • rotation – Values passed are in degrees.

  • scale – Values less than 1.0 cause the object to render at a smaller size, while values greater than 1.0 render a larger object.

Figure 13-7

Rotated and scaled objects

Note

This example reuses the ColorRect component from Listing 13-4. To get this example to work, you’ll first need to return to Listing 13-4 and uncomment the line that says root.clicked(). This will allow the ColorRect to receive the signals from the ApplicationWindow in Listing 13-8, creating a clickable ColorRect component. Be sure to comment out the line again when using ColorRect.qml for other examples.

First, create a new QML file that imports QtQuick and QtQuick.Controls. The top-level object in Listing 13-9 is ApplicationWindow. Be sure that when you load this QML file, you use Listing 13-8. Next, set up the window’s properties and make sure visible is set to true.
# rotate_and_move.qml
import QtQuick
import QtQuick.Controls
ApplicationWindow {
    title: "Simple Transformations"
    width: 300; height: 300
    visible: true
    MouseArea {
        id: windowMouse
        anchors.fill: parent
        onClicked: {
            // Reset the values of the ColorRect objects
            rect1.rotation = 0
            rect2.scale = 1.0
        }
    }
    ColorRect {
        id: rect1
        x: 20; y: 20
        antialiasing: true
        signal clicked
        onClicked:{
            // Rotate the rect 20° when clicked
            rotation += 20
        }
    }
    ColorRect {
        id: rect2;
        x: 200; y: 200
        antialiasing: true
        signal clicked
        onClicked:{
            // Scale the rect when clicked
            scale += .1
        }
    }
}
Listing 13-9

Code for transforming objects using mouse clicks

Next, we’ll create a MouseArea element to handle clicks on the application window. Since order matters in QML code, creating the MouseArea before the other items ensures that the window will also receive clicks and not just the ColorRect items.

The two ColorRect objects are arranged in the window manually. A new signal, clicked, is defined for each of the objects. We can then use the onClicked signal handler in each of the ColorRect elements to rotate or scale the items using the built-in Item properties.

Click anywhere in the window to reset the ColorRect values.

To load Listing 13-9, run the following command in the shell:
$ python3 qml_loader.py -f rotate_and_move.qml

Let’s take a look at one final example that demonstrates how to use transformations and a few other QtQuick classes to animate elements.

Explanation for Using Transformations to Animate Objects

The GUI in Figure 13-8 builds upon the previous section’s concepts of transformations and shows how we can create animations using the Behavior QML type. A Behavior defines the default animation that will occur whenever a particular property value changes.
Figure 13-8

The spin wheel rotates whenever the mouse clicks in the window

We’ll also see how to leverage JavaScript to create functions for adding randomness to our application.

Make sure that you have downloaded the images folder from GitHub before starting this application.

Begin by creating a new QML document. We’ll need to import QtQuick.Controls in Listing 13-10 so that we have access to the ApplicationWindow control. Be sure to set the value of visible to true.
# transforms.qml
import QtQuick
import QtQuick.Controls
ApplicationWindow {
    title: "Spin Wheel Transformations"
    width: 500; height: 500
    visible: true
    /* Get a random number where both the minimum and maximum
    values are inclusive */
    function getRandomIntInclusive(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() *
            (max - min + 1) + min);
    }
    Image {
        id: pointer
        source: "images/pointer.png"
        /* Use the z property to place the pointer above the
        wheel */
        x: parent.width / 2 - width / 2; y: 0; z: 1
    }
    Image {
        id: spinwheel
        anchors.centerIn: parent
        source: "images/spin_wheel.png"
        sourceSize.width: parent.width - 30
        sourceSize.height: parent.height - 30
        // Create a behavior for rotating the spinwheel Image
        Behavior on rotation {
            NumberAnimation {
                duration: getRandomIntInclusive(500, 3000)
                easing.type: Easing.OutSine
            }
        }
        /* Enable mouse handling and define how the image
        rotates when clicked */
        MouseArea {
            anchors.fill: parent
            onClicked: spinwheel.rotation +=
                getRandomIntInclusive(360, 360 * 4)
        }
    }
}
Listing 13-10

Code for the spin wheel QtQuick GUI

Stand-alone JavaScript functions can be added to QML documents to add extra functionality. The keyword function denotes the function called getRandomIntInclusive(), which takes as arguments two integer values that represent maximum and maximum limits. Using the two values, a random integer is returned.

The function getRandomIntInclusive() will be used on two occasions. The first time is in the MouseArea item’s signal handler onClicked. The random value returned will specify the rotation value of spin wheel, adding some realism to the GUI so that no spin appears to rotate the same amount.

The second time getRandomIntInclusive() is used, we want to describe the animation Behavior of the rotation. By clicking on the window, the rotation value of spin wheel will change. The Behavior on rotation line means that whenever the value of rotation changes, the NumberAnimation will run. The duration property value denotes how long the rotation will occur, anywhere between half of a second and three seconds.

Finally, the Easing type and easing.type property denote what kind of easing curve we want to use. Easing curves provide more realistic animations to objects. To find out what kinds of Easing curves are available in QtQuick, you should check out https://doc.qt.io/qt-5/qml-qtquick-propertyanimation.html#easing-prop.

To load Listing 13-10, run the following command in the shell:
$ python3 qml_loader.py -f transforms.qml

We’ve only begun to scratch the surface of the kinds of fluid transformations and animations that exist in QtQuick. It is highly recommended that you try and find other examples and experiment with them.

Summary

With the arrival of Qt 6, more emphasis has been put into creating dynamic GUIs that are cross-platform, scalable, easy to maintain, and designed to get the most out of the graphics hardware on any platform. QtQuick, the toolkit that is based on Qt’s QML language, defines a wide variety of amazing graphical tools that are great for desktop, mobile, and embedded applications.

There is so much more about QtQuick and QML that can be learned. The goal of this chapter was to provide you with the fundamentals to get you started using QtQuick and, from there, point you in the right direction with links and other useful information.

In this chapter, we’ve covered how to create a basic window and add QML elements and controls to an application and even discussed how to create reusable QML components that you can use in other applications. Those components were then used to demonstrate other QtQuick concepts like layouts and transformations. We saw how to load QML files with both QQuickView and QQmlApplicationEngine. The last application that we built demonstrated how to animate objects using both Behavior types and JavaScript.

Frankly, a complete guide on QtQuick is necessary to cover and describe all there is to offer in QML. There is so much content and so many possibilities for using QtQuick and QML to build GUIs.

In Chapter 14, we’ll return to using QtWidgets and find out how to begin building applications that interact with and manage SQL databases.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset