QML Modules in Qt 6.2

QML Modules

With Qt 6.2 there is, for the first time, a comprehensive build system API that allows you to specify a QML module as a complete, encapsulated unit. This is a significant improvement, but as the concept of QML modules was rather under-developed in Qt 5, even seasoned QML developers might now ask "What exactly is a QML module". In our previous post we have scratched the surface by introducing the CMake API used to define them. We'll take a closer look in this post.

Introduction to the QML CMake API

When Qt 6 migrated to CMake, we also wanted to provide a nicer experience for setting up QML projects. With the initial Qt 6.0 release, we did however only provide some tech preview API, which did not do much more than what was available in qmake since Qt 5.15.

[updated for PySide6] Creating your first app with PySide — A simple Hello World! application with Python and Qt

PySide, also known as Qt for Python, is a Python library for creating GUI applications using the Qt toolkit. PySide is the official binding for Qt on Python and is now developed by The Qt Company itself.

There are two major versions available: PySide2 based on Qt5 and PySide6 based on Qt6. Both versions are almost completely compatible aside from imports, and lack of support for some advanced modules in Qt6.

In this tutorial we'll learn how to use PySide to create desktop applications with Python.

First we'll create a series of simple windows on your desktop to ensure that PySide is working and introduce some of the basic concepts. Then we'll take a brief look at the event loop and how it relates to GUI programming in Python. Finally we'll look at Qt's QMainWindow which offers some useful common interface elements such as toolbars and menus. These will be explored in more detail in the subsequent tutorials.

The entire PySide tutorial features both PySide2 and PySide6 code examples. You can use whichever you prefer.

Creating an application

Let's create our first application! To start create a new Python file — you can call it whatever you like (e.g. app.py) and save it somewhere accessible. We'll write our simple app in this file.

We'll be editing within this file as we go along, and you may want to come back to earlier versions of your code, so remember to keep regular backups.

The source code for the application is shown below. Type it in verbatim, and be careful not to make mistakes. If you do mess up, Python will let you know what's wrong.

python
from PySide2.QtWidgets import QApplication, QWidget

# Only needed for access to command line arguments
import sys

# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

# Create a Qt widget, which will be our window.
window = QWidget()
window.show()  # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()


# Your application won't reach here until you exit and the event
# loop has stopped.

python
from PySide6.QtWidgets import QApplication, QWidget

# Only needed for access to command line arguments
import sys

# You need one (and only one) QApplication instance per application.
# Pass in sys.argv to allow command line arguments for your app.
# If you know you won't use command line arguments QApplication([]) works too.
app = QApplication(sys.argv)

# Create a Qt widget, which will be our window.
window = QWidget()
window.show()  # IMPORTANT!!!!! Windows are hidden by default.

# Start the event loop.
app.exec_()

# Your application won't reach here until you exit and the event
# loop has stopped.

First, launch your application. You can run it from the command line like any other Python script, for example --

bash
python3 app.py

Run it! You will now see your window. Qt automatically creates a window with the normal window decorations and you can drag it around and resize it like any window.

What you'll see will depend on what platform you're running this example on. The image below shows the window as displayed on Windows, macOS and Linux (Ubuntu).

Our window, as seen on Windows, macOS and Linux. Our window, as seen on Windows, macOS and Linux.

Stepping through the code

Let's step through the code line by line, so we understand exactly what is happening.

First, we import the PySide classes that we need for the application. Here we're importing QApplication, the application handler and QWidget, a basic empty GUI widget, both from the QtWidgets module.

python
from PySide2.QtWidgets import QApplication, QWidget

python
from PySide6.QtWidgets import QApplication, QWidget

The main modules for Qt are QtWidgets, QtGui and QtCore.

You could do from <module> import * but this kind of global import is generally frowned upon in Python, so we'll avoid it here.

Next we create an instance of QApplication, passing in sys.arg, which is Python list containing the command line arguments passed to the application.

python
app = QApplication(sys.argv)

If you know you won't be using command line arguments to control Qt you can pass in an empty list instead, e.g.

python
app = QApplication([])

Next we create an instance of a QWidget using the variable name window.

python
window = QWidget()
window.show()

In Qt all top level widgets are windows -- that is, they don't have a parent and are not nested within another widget or layout. This means you can technically create a window using any widget you like.

Widgets without a parent are invisible by default. So, after creating the window object, we must always call .show() to make it visible. You can remove the .show() and run the app, but you'll have no way to quit it!

What is a window? - Holds the user-interface of your application - Every application needs at least one (...but can have more) - Application will (by default) exit when last window is closed

Finally, we call app.exec_() to start up the event loop.

What's the event loop?

Before getting the window on the screen, there are a few key concepts to introduce about how applications are organised in the Qt world. If you're already familiar with event loops you can safely skip to the next section.

The core of every Qt Applications is the QApplication class. Every application needs one — and only one — QApplication object to function. This object holds the event loop of your application — the core loop which governs all user interaction with the GUI.

The event loop in Qt.

Each interaction with your application — whether a press of a key, click of a mouse, or mouse movement — generates an event which is placed on the event queue. In the event loop, the queue is checked on each iteration and if a waiting event is found, the event and control is passed to the specific event handler for the event. The event handler deals with the event, then passes control back to the event loop to wait for more events. There is only one running event loop per application.

The QApplication class - QApplication holds the Qt event loop - One QApplication instance required - You application sits waiting in the event loop until an action is taken - There is only one event loop at any time

The underscore is there because exec was a reserved word in Python 2.7. PySide handles this by appending an underscore to the name used in the Qt library. You'll also see .print_() methods on widgets for example.

QMainWindow

As we discovered in the last part, in Qt any widgets can be windows. For example, if you replace QtWidget with QPushButton. In the example below, you would get a window with a single push-able button in it.

python
import sys
from PySide2.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec_()

python
import sys
from PySide6.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec_()


This is neat, but not really very useful -- it's rare that you need a UI that consists of only a single control! But, as we'll discover later, the ability to nest widgets within other widgets using layouts means you can construct complex UIs inside an empty QWidget.

But, Qt already has a solution for you -- the QMainWindow. This is a pre-made widget which provides a lot of standard window features you'll make use of in your apps, including toolbars, menus, a statusbar, dockable widgets and more. We'll look at these advanced features later, but for now, we'll add a simple empty QMainWindow to our application.

python
import sys
from PySide2.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Start the event loop.
app.exec_()


python
import sys
from PySide6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Start the event loop.
app.exec_()


Run it! You will now see your main window. It looks exactly the same as before!

So our QMainWindow isn't very interesting at the moment. We can fix that by adding some content. If you want to create a custom window, the best approach is to subclass QMainWindow and then include the setup for the window in the __init__ block. This allows the window behavior to be self contained. We can add our own subclass of QMainWindow — call it MainWindow to keep things simple.

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


For this demo we're using a QPushButton. The core Qt widgets are always imported from the QtWidgets namespace, as are the QMainWindow and QApplication classes. When using QMainWindow we use .setCentralWidget to place a widget (here a QPushButton) in the QMainWindow -- by default it takes the whole of the window. We'll look at how to add multiple widgets to windows in the layouts tutorial.

When you subclass a Qt class you must always call the super __init__ function to allow Qt to set up the object.

In our __init__ block we first use .setWindowTitle() to change the title of our main window. Then we add our first widget — a QPushButton — to the middle of the window. This is one of the basic widgets available in Qt. When creating the button you can pass in the text that you want the button to display.

Finally, we call .setCentralWidget() on the window. This is a QMainWindow specific function that allows you to set the widget that goes in the middle of the window.

Run it! You will now see your window again, but this time with the QPushButton widget in the middle. Pressing the button will do nothing, we'll sort that next.

Our QMainWindow with a single QPushButton on Windows, macOS and Linux. Our QMainWindow with a single QPushButton on Windows, macOS and Linux.

We'll cover more widgets in detail shortly but if you're impatient and would like to jump ahead you can take a look at the http://doc.qt.io/qt-5/widget-classes.html#basic-widget-classes[QWidget documentation]. Try adding the different widgets to your window!

Sizing windows and widgets

The window is currently freely resizable -- if you grab any corner with your mouse you can drag and resize it to any size you want. While it's good to let your users resize your applications, sometimes you may want to place restrictions on minimum or maximum sizes, or lock a window to a fixed size.

In Qt sizes are defined using a QSize object. This accepts width and height parameters in that order. For example, the following will create a fixed size window of 400x300 pixels.

python
import sys

from PySide2.QtCore import QSize, Qt
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


python
import sys

from PySide6.QtCore import QSize, Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Set the central widget of the Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


Run it! You will see a fixed size window -- try and resize it, it won't work.

Our fixed-size window, notice that the _maximize_ control is disabled on Windows & Linux. On macOS you _can_ maximize the app to fill the screen, but the central widget will not resize. Our fixed-size window, notice that the _maximize control is disabled on Windows & Linux. On macOS you can maximize the app to fill the screen, but the central widget will not resize._

As well as .setFixedSize() you can also call .setMinimumSize() and .setMaximumSize() to set the minimum and maximum sizes respectively. Experiment with this yourself!

You can use these size methods on any widget.

In this section we've covered the QApplication class, the QMainWindow class, the event loop and experimented with adding a simple widget to a window. In the next section we'll take a look at the mechanisms Qt provides for widgets and windows to communicate with one another and your own code.

For more, see the complete PySide6 tutorial.

[updated for PyQt6] Widgets — Using Qt5's library of built-in widgets to build your applications

In Qt (and most User Interfaces) ‘widget’ is the name given to a component of the UI that the user can interact with. User interfaces are made up of multiple widgets, arranged within the window.

Qt comes with a large selection of widgets available, and even allows you to create your own custom and customized widgets.

A quick demo

First let's have a look at some of the most common PyQt widgets. The following code creates a range of PyQt widgets and adds them to a window layout so you can see them together.

We'll cover how layouts work in Qt in the next tutorial.

python
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDateEdit,
    QDateTimeEdit,
    QDial,
    QDoubleSpinBox,
    QFontComboBox,
    QLabel,
    QLCDNumber,
    QLineEdit,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QRadioButton,
    QSlider,
    QSpinBox,
    QTimeEdit,
    QVBoxLayout,
    QWidget,
)


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Widgets App")

        layout = QVBoxLayout()
        widgets = [
            QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit,
        ]

        for w in widgets:
            layout.addWidget(w())

        widget = QWidget()
        widget.setLayout(layout)

        # Set the central widget of the Window. Widget will expand
        # to take up all the space in the window by default.
        self.setCentralWidget(widget)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec()


python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDateEdit,
    QDateTimeEdit,
    QDial,
    QDoubleSpinBox,
    QFontComboBox,
    QLabel,
    QLCDNumber,
    QLineEdit,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QRadioButton,
    QSlider,
    QSpinBox,
    QTimeEdit,
    QVBoxLayout,
    QWidget,
)


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Widgets App")

        layout = QVBoxLayout()
        widgets = [
            QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit,
        ]

        for w in widgets:
            layout.addWidget(w())

        widget = QWidget()
        widget.setLayout(layout)

        # Set the central widget of the Window. Widget will expand
        # to take up all the space in the window by default.
        self.setCentralWidget(widget)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec()


Run it! You'll see a window appear containing all the widgets we've created.

Big ol' list of widgets on Windows, Mac & Ubuntu Linux. Big ol' list of widgets on Windows, Mac & Ubuntu Linux.

Lets have a look at all the example widgets, from top to bottom:

Widget What it does
QCheckbox A checkbox
QComboBox A dropdown list box
QDateEdit For editing dates and datetimes
QDateTimeEdit For editing dates and datetimes
QDial Rotatable dial
QDoubleSpinbox A number spinner for floats
QFontComboBox A list of fonts
QLCDNumber A quite ugly LCD display
QLabel Just a label, not interactive
QLineEdit Enter a line of text
QProgressBar A progress bar
QPushButton A button
QRadioButton A toggle set, with only one active item
QSlider A slider
QSpinBox An integer spinner
QTimeEdit For editing times

There are far more widgets than this, but they don’t fit so well! You can see them all by checking the Qt documentation.

Next, we'll step through some of the most commonly used widgets and look at them in more detail. To experiment with the widgets we'll need a simple application to put them in. Save the following code to a file named app.py and run it to make sure it's working.

python
import sys
from PyQt5.QtWidgets import (
    QMainWindow, QApplication,
    QLabel, QCheckBox, QComboBox, QListBox, QLineEdit,
    QLineEdit, QSpinBox, QDoubleSpinBox, QSlider
)
from PyQt5.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()


python
import sys
from PyQt6.QtWidgets import (
    QMainWindow, QApplication,
    QLabel, QCheckBox, QComboBox, QListBox, QLineEdit,
    QLineEdit, QSpinBox, QDoubleSpinBox, QSlider
)
from PyQt6.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

In the code above we've imported a number of Qt widgets. Now we'll step through each of those widgets in turn, adding them to our application and seeing how they behave.

QLabel

We'll start the tour with QLabel, arguably one of the simplest widgets available in the Qt toolbox. This is a simple one-line piece of text that you can position in your application. You can set the text by passing in a str as you create it:

python
widget = QLabel("Hello")

Or, by using the .setText() method:

python
widget = QLabel("1")  # The label is created with the text 1.
widget.setText("2")   # The label now shows 2.

You can also adjust font parameters, such as the size of the font or the alignment of text in the widget.

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLabel("Hello")
        font = widget.font()
        font.setPointSize(30)
        widget.setFont(font)
        widget.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        self.setCentralWidget(widget)

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLabel("Hello")
        font = widget.font()
        font.setPointSize(30)
        widget.setFont(font)
        widget.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)

        self.setCentralWidget(widget)

QLabel on Windows, Mac & Ubuntu Linux. QLabel on Windows, Mac & Ubuntu Linux.

Font tip Note that if you want to change the properties of a widget font it is usually better to get the current font, update it and then apply it back. This ensures the font face remains in keeping with the desktop conventions.

The alignment is specified by using a flag from the Qt. namespace. The flags available for horizontal alignment are:

PyQt5 flag / PyQt6 flag (long name) Behavior
Qt.AlignLeft / Qt.AlignmentFlag.AlignLeft Aligns with the left edge.
Qt.AlignRight / Qt.AlignmentFlag.AlignRight Aligns with the right edge.
Qt.AlignHCenter / Qt.AlignmentFlag.AlignHCenter Centers horizontally in the available space.
Qt.AlignJustify / Qt.AlignmentFlag.AlignJustify Justifies the text in the available space.

The flags available for vertical alignment are:

PyQt5 flag / PyQt6 flag (long name) Behavior
Qt.AlignTop / Qt.AlignmentFlag.AlignTop Aligns with the top.
Qt.AlignBottom / Qt.AlignmentFlag.AlignBottom Aligns with the bottom.
Qt.AlignVCenter / Qt.AlignmentFlag.AlignVCenter Centers vertically in the available space.

You can combine flags together using pipes (|), however note that you can only use vertical or horizontal alignment flag at a time.

python
align_top_left = Qt.AlignLeft | Qt.AlignTop

Note that you use an OR pipe (`|`) to combine the two flags (not A &amp; B). This is because the flags are non-overlapping bitmasks. e.g. Qt.AlignmentFlag.AlignLeft has the hexadecimal value 0x0001, while Qt.AlignmentFlag.AlignBottom is 0x0040. By ORing together we get the value 0x0041 representing 'bottom left'. This principle applies to all other combinatorial Qt flags. If this is gibberish to you, feel free to ignore and move on. Just remember to use |

Finally, there is also a shorthand flag that centers in both directions simultaneously:

PyQt5 Flag PyQt5 Flag Behavior
Qt.AlignCenter Qt.AlignmentFlag.AlignCenter Centers horizontally and vertically

Weirdly, you can also use QLabel to display an image using .setPixmap(). This accepts an pixmap, which you can create by passing an image filename to QPixmap. In the example files provided with this book you can find a file otje.jpg which you can display in your window as follows:

python
widget.setPixmap(QPixmap('otje.jpg'))

"Otje" the cat. "Otje" the cat.

What a lovely face. By default the image scales while maintaining its aspect ratio. If you want it to stretch and scale to fit the window completely you can set .setScaledContents(True) on the QLabel.

python
widget.setScaledContents(True)

QCheckBox

The next widget to look at is QCheckBox() which, as the name suggests, presents a checkable box to the user. However, as with all Qt widgets there are number of configurable options to change the widget behaviors.

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QCheckBox()
        widget.setCheckState(Qt.Checked)

        # For tristate: widget.setCheckState(Qt.PartiallyChecked)
        # Or: widget.setTriState(True)
        widget.stateChanged.connect(self.show_state)

        self.setCentralWidget(widget)


    def show_state(self, s):
        print(s == Qt.Checked)
        print(s)

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QCheckBox()
        widget.setCheckState(Qt.CheckState.Checked)

        # For tristate: widget.setCheckState(Qt.PartiallyChecked)
        # Or: widget.setTriState(True)
        widget.stateChanged.connect(self.show_state)

        self.setCentralWidget(widget)


    def show_state(self, s):
        print(s == Qt.CheckState.Checked)
        print(s)

QCheckBox on Windows, Mac & Ubuntu Linux. QCheckBox on Windows, Mac & Ubuntu Linux.

You can set a checkbox state programmatically using .setChecked or .setCheckState. The former accepts either True or False representing checked or unchecked respectively. However, with .setCheckState you also specify a particular checked state using a Qt. namespace flag:

PyQt5 flag / PyQt6 flag (long name) Behavior
Qt.Unchecked / Qt.CheckState.Unchecked Item is unchecked
Qt.PartiallyChecked / Qt.CheckState.PartiallyChecked Item is partially checked
Qt.Checked / Qt.CheckState.Checked Item is unchecked

A checkbox that supports a partially-checked (Qt.CheckState.PartiallyChecked) state is commonly referred to as 'tri-state', that is being neither on nor off. A checkbox in this state is commonly shown as a greyed out checkbox, and is commonly used in hierarchical checkbox arrangements where sub-items are linked to parent checkboxes.

If you set the value to Qt.CheckState.PartiallyChecked the checkbox will become tristate. You can also .setTriState(True) to set tristate support on a You can also set a checkbox to be tri-state without setting the current state to partially checked by using .setTriState(True)

You may notice that when the script is running the current state number is displayed as an int with checked = 2, unchecked = 0, and partially checked = 1. You don’t need to remember these values, the Qt.Checked namespace variable == 2 for example. This is the value of these state's respective flags. This means you can test state using state == Qt.Checked.

QComboBox

The QComboBox is a drop down list, closed by default with an arrow to open it. You can select a single item from the list, with the currently selected item being shown as a label on the widget. The combo box is suited to selection of a choice from a long list of options.

You have probably seen the combo box used for selection of font faces, or size, in word processing applications. Although Qt actually provides a specific font-selection combo box as QFontComboBox.

You can add items to a QComboBox by passing a list of strings to .addItems(). Items will be added in the order they are provided.

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QComboBox()
        widget.addItems(["One", "Two", "Three"])

        # Sends the current index (position) of the selected item.
        widget.currentIndexChanged.connect( self.index_changed )

        # There is an alternate signal to send the text.
        widget.textChanged.connect( self.text_changed )

        self.setCentralWidget(widget)


    def index_changed(self, i): # i is an int
        print(i)

    def text_changed(self, s): # s is a str
        print(s)

QComboBox on Windows, Mac & Ubuntu Linux. QComboBox on Windows, Mac & Ubuntu Linux.

The .currentIndexChanged signal is triggered when the currently selected item is updated, by default passing the index of the selected item in the list. There is also a .currentTextChanged signal which instead provides the label of the currently selected item, which is often more useful.

QComboBox can also be editable, allowing users to enter values not currently in the list and either have them inserted, or simply used as a value. To make the box editable:

python
widget.setEditable(True)

You can also set a flag to determine how the insert is handled. These flags are stored on the QComboBox class itself and are listed below:

PyQt5 flag / PyQt6 flag (long name) Behavior
QComboBox.NoInsert / QComboBox.InsertPolicy.NoInsert No insert
QComboBox.InsertAtTop / QComboBox.InsertPolicy.InsertAtTop Insert as first item
QComboBox.InsertAtCurrent / QComboBox.InsertPolicy.InsertAtCurrent Replace currently selected item
QComboBox.InsertAtBottom / QComboBox.InsertPolicy.InsertAtBottom Insert after last item
QComboBox.InsertAfterCurrent / QComboBox.InsertPolicy.InsertAfterCurrent Insert after current item
QComboBox.InsertBeforeCurrent / QComboBox.InsertPolicy.InsertBeforeCurrent Insert before current item
QComboBox.InsertAlphabetically / QComboBox.InsertPolicy.InsertAlphabetically Insert in alphabetical order

To use these, apply the flag as follows:

python
widget.setInsertPolicy(QComboBox.InsertPolicy.InsertAlphabetically)

You can also limit the number of items allowed in the box by using .setMaxCount, e.g.

python
widget.setMaxCount(10)

For a more in-depth look at the QComboBox take a look at my QComboBox documentation.

QListWidget

This widget is similar to QComboBox, except options are presented as a scrollable list of items. It also supports selection of multiple items at once. A QListWidget offers an currentItemChanged signal which sends the QListItem (the element of the list widget), and a currentTextChanged signal which sends the text of the current item.

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        widget.currentItemChanged.connect(self.index_changed)
        widget.currentTextChanged.connect(self.text_changed)

        self.setCentralWidget(widget)


    def index_changed(self, i): # Not an index, i is a QListItem
        print(i.text())

    def text_changed(self, s): # s is a str
        print(s)

QListWidget on Windows, Mac & Ubuntu Linux. QListWidget on Windows, Mac & Ubuntu Linux.

QLineEdit

The QLineEdit widget is a simple single-line text editing box, into which users can type input. These are used for form fields, or settings where there is no restricted list of valid inputs. For example, when entering an email address, or computer name.

python
class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLineEdit()
        widget.setMaxLength(10)
        widget.setPlaceholderText("Enter your text")

        #widget.setReadOnly(True) # uncomment this to make readonly

        widget.returnPressed.connect(self.return_pressed)
        widget.selectionChanged.connect(self.selection_changed)
        widget.textChanged.connect(self.text_changed)
        widget.textEdited.connect(self.text_edited)

        self.setCentralWidget(widget)


    def return_pressed(self):
        print("Return pressed!")
        self.centralWidget().setText("BOOM!")

    def selection_changed(self):
        print("Selection changed")
        print(self.centralWidget().selectedText())

    def text_changed(self, s):
        print("Text changed...")
        print(s)

    def text_edited(self, s):
        print("Text edited...")
        print(s)

QLineEdit on Windows, Mac & Ubuntu Linux. QLineEdit on Windows, Mac & Ubuntu Linux.

As demonstrated in the above code, you can set a maximum length for the text in a line edit.

The QLineEdit has a number of signals available for different editing events including when return is pressed (by the user), when the user selection is changed. There are also two edit signals, one for when the text in the box has been edited and one for when it has been changed. The distinction here is between user edits and programmatic changes. The textEdited signal is only sent when the user edits text.

Additionally, it is possible to perform input validation using an input mask to define which characters are supported and where. This can be applied to the field as follows:

python
widget.setInputMask('000.000.000.000;_')

The above would allow a series of 3-digit numbers separated with periods, and could therefore be used to validate IPv4 addresses.

QSpinBox and QDoubleSpinBox

QSpinBox provides a small numerical input box with arrows to increase and decrease the value. QSpinBox supports integers while the related widget QDoubleSpinBox supports floats.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSpinBox()
        # Or: widget = QDoubleSpinBox()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Or: widget.setRange(-10,3)

        widget.setPrefix("$")
        widget.setSuffix("c")
        widget.setSingleStep(3)  # Or e.g. 0.5 for QDoubleSpinBox
        widget.valueChanged.connect(self.value_changed)
        widget.textChanged.connect(self.value_changed_str)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def value_changed_str(self, s):
        print(s)

Run it and you'll see a numeric entry box. The value shows pre and post fix units, and is limited to the range +3 to -10.

QSpinBox on Windows, Mac & Ubuntu Linux. QSpinBox on Windows, Mac & Ubuntu Linux.

The demonstration code above shows the various features that are available for the widget.

To set the range of acceptable values you can use setMinimum and setMaximum, or alternatively use setRange to set both simultaneously. Annotation of value types is supported with both prefixes and suffixes that can be added to the number, e.g. for currency markers or units using .setPrefix and .setSuffix respectively.

Clicking on the up and down arrows on the widget will increase or decrease the value in the widget by an amount, which can be set using .setSingleStep. Note that this has no effect on the values that are acceptable to the widget.

Both QSpinBox and QDoubleSpinBox have a .valueChanged signal which fires whenever their value is altered. The raw .valueChanged signal sends the numeric value (either an int or a float) while .textChanged sends the value as a string, including both the prefix and suffix characters.

QSlider

QSlider provides a slide-bar widget, which functions internally much like a QDoubleSpinBox. Rather than display the current value numerically, it is represented by the position of the slider handle along the length of the widget. This is often useful when providing adjustment between two extremes, but where absolute accuracy is not required. The most common use of this type of widget is for volume controls.

There is an additional .sliderMoved signal that is triggered whenever the slider moves position and a .sliderPressed signal that emits whenever the slider is clicked.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSlider()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Or: widget.setRange(-10,3)

        widget.setSingleStep(3)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Run this and you'll see a slider widget. Drag the slider to change the value.

QSlider on Windows, Mac & Ubuntu Linux. QSlider on Windows, Mac & Ubuntu Linux.

You can also construct a slider with a vertical or horizontal orientation by passing the orientation in as you create it. The orientation flags are defined in the Qt. namespace. For example --

python
widget.QSlider(Qt.Vertical)

python
widget.QSlider(Qt.Orientiation.Vertical)

Or --

python
widget.QSlider(Qt.Horizontal)

python
widget.QSlider(Qt.Orientiation.Horizontal)

QDial

Finally, the QDial is a rotatable widget that functions just like the slider, but appears as an analogue dial. This looks nice, but from a UI perspective is not particularly user friendly. However, they are often used in audio applications as representation of real-world analogue dials.

python
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QDial()
        widget.setRange(-10, 100)
        widget.setSingleStep(0.5)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Run this and you'll see a circular dial, rotate it to select a number from the range.

QDial on Windows, Mac & Ubuntu Linux. QDial on Windows, Mac & Ubuntu Linux.

The signals are the same as for QSlider and retain the same names (e.g. .sliderMoved).

Conclusion

This concludes our brief tour of the common widgets used in PyQt applications. To see the full list of available widgets, including all their signals and attributes, take a look at the Qt documentation.

For more, see the complete PyQt5 tutorial.

Fast Duplicate Tracking

In 2019, I optimized QStringList::removeDuplicates() by using std::pmp::unordered_set with a std::pmr::monotonic_buffer_resource, when available. The class that I wrote to encapsulate this optimization has since been re-implemented three times. The latest iteration has recently landed in KDToolBox.

If you have code that looks a bit like this:

SomeLocalContainer seen;
~~~~
    if (seen.contains(x))
        continue;
    seen.insert(x);

then you should read on.

Before my change, the QStringList::removeDuplicates() function used a QSet to keep track of “seen” strings, so it could remove already-seen strings from the list the second time they are encountered, thereby removing all duplicate strings from the list.

There are quite a few problems with this approach.

Double Lookup

The contains() + insert() anti-pattern performs two look-ups for the same key, because the position that contains() internally has calculated is lost by the time contains() returns and thus has to be re-calculated in insert() again. We know that the position will be the same, but the compiler doesn’t. The STL associative containers return a pair (boooooo!) of iterator and bool, with the latter indicating whether the item was inserted (true) or was found to pre-exist (false). Since we want to insert anyway, we can just attempt the insert() and check whether it changed something:

std::unordered_set<~~~> seen;
~~~~
    if (auto [it, inserted] = seen.insert(x); !inserted)
        continue; // was already present
    // no need for seen.insert(x): already done

While this is still quite a mouthful, we’ve managed to reduce the look-ups by half.

The Qt containers lack this convenient API, of course, instead forcing the user to check whether the size of the container changed, like this:

QSet<~~~~> seen;
~~~~
    const auto oldSize = seen.size();
    seen.insert(x);
    if (seen.size() == oldSize)
        continue; // was already present
    // no need for seen.insert(x): already inserted

It’s odd, but works, too.

Allocations, Allocations

While QSet or std::unordered_set are fast once constructed, they both allocate a new node per element inserted. So in the usual case where you don’t have (many) duplicates, you’re performing O(N) memory allocations, N being the number of candidates to check.

How nice it would be if we could combine the speed of a hash table with the cache-friendliness of, say, a QVarLengthArray. Oh, wait. We can!

In C++17, we finally got some of the Bloomberg Allocators merged and, while quite a few implementations are still behind implementing them, that shouldn’t deter us, thanks to feature test macros.

Taking a look at the std::pmr namespace, we find a few template aliases that appear to implant a non-standard allocator, std::pmr::polymorphic_allocator, into the usual range of STL containers, including vector and unordered_set.

If you want to know all the gory details of that class, book one of KDAB’s Modern C++ trainings. Suffice to say that std::pmr::polymorphic_allocator implements an allocator that gets memory from a std::pmr::memory_resource, which is an interface you can inherit to implement your own custom allocation strategies.

Enter monotonic_buffer_resource

But we don’t even need to do that. We just need to plug one of the existing memory_resources, std::pmr::monotonic_buffer_resource, into std::pmr::unordered_set.

The monotonic_buffer_resource class implements a grow-only memory pool over a static char buffer where deallocation is a no-op and allocation just ups a pointer. In case the initial buffer is exhausted, monotonic_buffer_resource allocates a new block and continues to allocate from there. If that’s exhausted too, it allocates an even larger block, etc.

This allocator is as fast as it gets, but it isn’t thread-safe and it never frees memory (until destroyed itself). But that’s perfect for building temporary containers whose lifetimes are bound by a function.

The setup is a bit verbose, due to the flexibility of the std::pmr framework. But it’s always the same:

char buffer[1024]; // or whatever size fits
std::pmr::monotonic_buffer_resource res{buffer, sizeof buffer};
std::unordered_set<~~~> seen(N, &res); // N: expected number of elements
~~~~
    if (auto [it, ins] = seen.insert(x); !ins)
        continue;
    // already inserted

Now the first 1 KiB of memory is allocated on the stack, and only then do we go to the heap. Passing the expected number of elements to the unordered_set constructor (that’s like a reserve(), not a resize()!) is very important here because, while a re-hash wouldn’t take the nodes out of buffer, it would waste memory for the new bucket list because monotonic_buffer_resource doesn’t re-use freed-up memory again, thus creating gaps in our precious, buffer.

Tying It All Together

So, we have three things that we need to keep in mind:

  1. Avoid double lookups,
  2. Use std::pmr::monotonic_buffer_resource to avoid memory allocations,
  3. …but fall back to QSet or std::unordered_set when std::pmr isn’t available.

Then, it’s just a question of writing a good hash function (which has recently become much simpler).

QDuplicateTracker (private API) bound these together for the first time, with a much nicer API:

QDuplicateTracker<~~~> seen;
seen.reserve(N);
~~~~
    if (seen.hasSeen(x))
        continue;
    // already inserted

Once you know which patterns to look for, the use-cases seem endless: Qt changes in topic “qduplicatetracker”.

I don’t recommend using QDuplicateTracker from Qt 5 anymore though, since, as I mentioned, I have taken this class through another two iterations in customer projects, finally culminating in the addition to KDToolBox. That’s the version you should be using.

Alternatively, it should be possible, even in Qt projects, to use the Qt 6 version, which I have recently improved for the Qt 6.3 release next year, simply by copying it’s header file into your project and replacing the QHasher internal struct (which depends on implementation details of Qt 6’s QHash container) with a template argument.

I invite you to study the source-code and post any questions that you may have in the comments below.

Happy duplicate tracking!

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post Fast Duplicate Tracking appeared first on KDAB.

QComboBox — Drop-down selection widget

The QComboBox is a simple widget for presenting a list of options to your users in PyQt, taking up the minimum amount of screen space. The widget can have a single selected item, which is displayed in the widget area. When selected a QComboBox pops out a list of possible values from which you can select. A QComboBox can also - optionally - be made editable, so your users can enter their own values onto the list of possible values.

QComboBox QComboBox in its closed and open state

Populating a QComboBox

Items can be added or inserted to a QComboBox, where adding appends the item to the end of the current list, while insert inserts them in a specific index in the existing list. There are convenience methods for adding multiple items to a combobox and also for adding icons to the list. The following example will demonstrate each of these using a series of comboboxes.

python
from PyQt5.QtWidgets import QComboBox, QMainWindow, QApplication, QWidget, QVBoxLayout
from PyQt5.QtGui import QIcon
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        combobox1 = QComboBox()
        combobox1.addItem('One')
        combobox1.addItem('Two')
        combobox1.addItem('Three')
        combobox1.addItem('Four')

        combobox2 = QComboBox()
        combobox2.addItems(['One', 'Two', 'Three', 'Four'])

        combobox3 = QComboBox()
        combobox3.addItems(['One', 'Two', 'Three', 'Four'])
        combobox3.insertItem(2, 'Hello!')

        combobox4 = QComboBox()
        combobox4.addItems(['One', 'Two', 'Three', 'Four'])
        combobox4.insertItems(2, ['Hello!', 'again'])

        combobox5 = QComboBox()
        icon_penguin = QIcon('animal-penguin.png')
        icon_monkey = QIcon('animal-monkey.png')
        icon_bauble = QIcon('bauble.png')
        combobox5.addItem(icon_penguin, 'Linux')
        combobox5.addItem(icon_monkey, 'Monkeyix')
        combobox5.insertItem(1, icon_bauble, 'Baublix')

        layout = QVBoxLayout()
        layout.addWidget(combobox1)
        layout.addWidget(combobox2)
        layout.addWidget(combobox3)
        layout.addWidget(combobox4)
        layout.addWidget(combobox5)

        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)

    def current_text_changed(self, s):
        print("Current text: ", s)


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

Run the example and compare the result with each series of add and insert steps.

Adding items to a QComboBox Adding items to a QComboBox

You can replace the text at a specific index in the list by calling .setItemText() for example.

python
widget.setItemText(3, 'new text')

Finally, you can clear a QComboBox -- removing all items in it -- by calling .clear().

python
widget.clear()

QComboBox signals

The QComboBox emits a number of signals on state changes. When the currently selected item changes, the widget emits .currentIndexChanged() and .currentTextChanged() signals. The first receives the index of the selected entry while the second receives the text of that item. There is a further signal .activated() which is emitted for user-selections whether the index is changed or not: selecting the same entry again will still emit the signal. Highlighting an entry in the QComboBox popup list will emit the .highlighted() signal.

The following example demonstrates these signals in action.

python
from PyQt5.QtWidgets import QComboBox, QMainWindow, QApplication
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        combobox = QComboBox()
        combobox.addItems(['One', 'Two', 'Three', 'Four'])

        # Connect signals to the methods.
        combobox.activated.connect(self.activated)
        combobox.currentTextChanged.connect(self.text_changed)
        combobox.currentIndexChanged.connect(self.index_changed)

        self.setCentralWidget(combobox)

    def activated(Self, index):
        print("Activated index:", index)

    def text_changed(self, s):
        print("Text changed:", s)

    def index_changed(self, index):
        print("Index changed", index)

app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

If you run the example the signals emitted as you interact with the QComboBox will be shown in the console, giving output like that shown below. Note that when re-selecting the same entry, only the .activated() signal is emitted.

python
Index changed 1
Text changed: Two
Activated index: 1
Index changed 2
Text changed: Three
Activated index: 2
Index changed 3
Text changed: Four
Activated index: 3
Activated index: 3
Activated index: 3

Getting the current state

In addition to the signals, QComboBox has a few methods for getting the current state at any time. For example, you can use .currentIndex() to get the index of the currently selected item in the combobox, or use .currentText() to get the text. With the index you can also look up the text of a specific item using .itemText(). Finally, you can use .count() to get the total number of items in the combobox list.

In the example below, we use the activated signal we've already seen to hook up a number of methods which get information about the combobox and display this in the console. As you select any item in the combobox every method will run printing a message to the console.

python
from PyQt5.QtWidgets import QComboBox, QMainWindow, QApplication
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        # Keep a reference to combobox on self, so we can access it in our methods.
        self.combobox = QComboBox()
        self.combobox.addItems(['One', 'Two', 'Three', 'Four'])

        # Connect signal to our methods.
        self.combobox.activated.connect(self.check_index)
        self.combobox.activated.connect(self.current_text)
        self.combobox.activated.connect(self.current_text_via_index)
        self.combobox.activated.connect(self.current_count)

        self.setCentralWidget(self.combobox)

    def check_index(self, index):
        cindex = self.combobox.currentIndex()
        print(f"Index signal: {index}, currentIndex {cindex}")

    def current_text(self, _): # We receive the index, but don't use it.
        ctext = self.combobox.currentText()
        print("Current text", ctext)

    def current_text_via_index(self, index):
        ctext = self.combobox.itemText(index)  # Get the text at index.
        print("Current itemText", ctext)

    def current_count(self, index):
        count = self.combobox.count()
        print(f"Index {index+1}/{count}")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

Running this and selecting different items in the combobox will show the outputs.

python
Current text Two
Current itemText Two
Index 1/4
Index signal: 2, currentIndex 2
Current text Three
Current itemText Three
Index 2/4
Index signal: 3, currentIndex 3
Current text Four
Current itemText Four
Index 3/4

Editable comboboxes

You can set a QComboBox to be editable allowing the user to type enter the values not currently in the list. Entered values can be added to the list, or just used as a value which is not inserted. To enable editing you can call .setEditable(True) on the widget, while control of how inserts operate is handled through the insert policy. This is set by calling .setInsertPolicy() passing one of the following flags:

Flag Value Behavior
QComboBox.NoInsert 0 No insert
QComboBox.InsertAtTop 1 Insert as first item
QComboBox.InsertAtCurrent 2 Replace currently selected item
QComboBox.InsertAtBottom 3 Insert after last item
QComboBox.InsertAfterCurrent 4 Insert after current item
QComboBox.InsertBeforeCurrent 5 Insert before current item
QComboBox.InsertAlphabetically 6 Insert in alphabetical order

For PyQt6 use the fully-qualified flag name, i.e. QComboBox.InsertPolicy.NoInsert

For example, to insert new values alphabetically, you would use.

python
widget.setInsertPolicy(QComboBox.InsertAlphabetically)

If the combobox is set to be editable, but QComboBox.NoInsert is set as the insert policy, users will be able to enter their own custom values, but they will not be added to the list. In the following example we have two QComboBox widgets: one which is our editable box and the second which controls the insert policy of the first. Note that since the values of the flags (see above) are just numbers from 0 to 6, we can use the index to determine the flag.

python
from PyQt5.QtWidgets import QComboBox, QMainWindow, QApplication, QWidget, QVBoxLayout
import sys


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()

        # Keep a reference to combobox on self, so we can access it in our methods.
        self.combobox = QComboBox()
        self.combobox.addItems(['One', 'Two', 'Three', 'Four'])
        self.combobox.setEditable(True)

        self.combobox.currentTextChanged.connect(self.current_text_changed)

        # Insert policies
        self.insertpolicy = QComboBox()
        self.insertpolicy.addItems([
            'NoInsert',
            'InsertAtTop',
            'InsertAtCurrent',
            'InsertAtBottom',
            'InsertAfterCurrent',
            'InsertBeforeCUrrent',
            'InsertAlphabetically'
        ])
        # The index in the insertpolicy combobox (0-6) is the correct flag value to set
        # to enable that insert policy.
        self.insertpolicy.currentIndexChanged.connect(self.combobox.setInsertPolicy)

        layout = QVBoxLayout()
        layout.addWidget(self.combobox)
        layout.addWidget(self.insertpolicy)

        container = QWidget()
        container.setLayout(layout)

        self.setCentralWidget(container)

    def current_text_changed(self, s):
        print("Current text: ", s)


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()

If you run this example you'll notice you can now type into the (top) editable combobox and submit them by pressing Enter. The .currentTextChanged() signal will be emitted as you type into the field.

python
Current text:  w
Current text:  wq
Current text:  wqe
Current text:  wqew
Current text:  wqeww
Current text:  wqewwq
Current text:  wqewwqe

By default the insert policy is set to QComboBox.NoInsert so entered values will not be added, just set as the value of the combobox. By selecting the insert policy in the second combobox you can change this behavior, having new entries added to the list.

Editing a QComboBox with insert policy Editing a QComboBox with insert policy

When you allow users to add values to the list, you may wish to constrain the maximum number of entries. This can be done using .setMaxCount, e.g.

python
widget.setMaxCount(10)

For more, see the complete PyQt5 tutorial.

Qt 6.2 vs. Qt 5.15 – The Feature Parity Comparison

The Qt Company recently compared the latest Long Term Releases Qt 5.15 and Qt 6.2 of its software development platform consisting of design, development, and quality assurance applications plus various software libraries, referred to in this post as modules. This blog post, intended particularly for product and R&D leaders, summarizes the main findings of the comparison.

Comparing two major releases is never trivial, especially when there are eight years between their initial releases, the comparison highlights that there is enough feature parity for most customers to move to the Qt 6.2 LTS release., It’s important to stress that we at Qt have been laser-focused on providing Qt 6 with as much source compatibility as feasible. In addition, Qt projects can find plenty of resources in the Qt online documentation helping with the source code migration. We also offer professional services helping with the transition to Qt 6.

The Qt 6.2 Long-Term-Supported (LTS) release includes several innovative improvements allowing customers to adopt Qt as a future productivity platform, designing beautiful next-generation user experiences and scaling their product portfolio without limits. The Qt 6.2 LTS release does include also all-new functionality such as advanced 3D UX capabilities and hardware-accelerated graphics for Vulkan and Metal technologies. Qt 6.2 includes also the Qt Shader Tool module, which enables advanced graphics experiences.

Out of the 77 components that comprise the Qt 5.15 LTS release, 72 are either available in Qt 6.2 LTS out-of-the-box, the functionality has been merged into other components such as the Qt OpenGL Module, or they have been already deprecated during the lifetime of Qt 5 such as the Qt Script modules.

Five out of 50 add-on modules have not been included yet. For example, the Qt Location add-on module providing capabilities to draw maps in applications has not been used in many products using Qt. The same is true for the Qt Speech add-on module, which provides a single text-to-speech functionality. The add-on modules Qt PDF and Qt Gamepad had also only few adoptions among developers, making the justification to port them to the re-architected Qt 6 release challenging. The future of the Qt WebGL module has not been decided yet and, therefore, it is not included in the Qt 6 series. We are evaluating concepts and technologies that allow Qt applications to be operated remotely. WebGL might be a part of this in the future, but we don't want to commit to any particular technology at this point. Let us know if and how you are using any of these 5 modules for planning the future roadmap. The Qt license allows customers to embed relevant code from the Qt 5 software stack to Qt 6.

A comparison of individual features or API level is also important. We at Qt try to focus on describing the differences on feature level when there have been many changes (Check out the related posts on Qt Multimedia and the Qt Extras modules on this blog!). For example, the Qt Multimedia module will deliver on the cross-platform promise unlike Qt 5.15 reducing the development effort with Qt 6 but might miss a few capabilities. We also added some all-new functionality to the Qt Multimedia module such as rendering of subtitles. Then again, some modules such as the Qt Quick 3D module have been getting an increase of functionality required for the all-new 3D experiences such as mesh morphing and particles effects.

Moving forward, the Qt Product Management Team’s goal is to fill any gaps that may prevent projects from being successful also with Qt 6 by using a customer-centric approach. We’ll keep our radars fully powered for any change in priorities. Do get in touch through our online support channels, the open-source contribution channels, or talk directly to your customer success contact.

If you want to know the details of the comparison do check on the Qt 6.2 comparison web page web page or watch the webinar on the same topic.

Q&A: How to show a custom cursor on a PyQtGraph plot? — Changing the OS cursor and implementing a custom crosshair

When working with plots changing the cursor can help with pointing accuracy. For example, if you need to isolate particular points in a scatter plot a cross-hair will give you a more accurate view of where you are pointing than the standard arrow cursor.

Changing the mouse cursor

Changing the mouse cursor in a PyQtGraph is fairly simple and uses Qt's built-in support for custom cursors over a widget. Since the PyQtGraph PlotWidget is a subclass of QGraphicsWidget (and therefore QWidget) you can use the standard methods.

python
    cursor = Qt.CrossCursor
    self.graphWidget.setCursor(cursor)

For the complete list of cursor types, see the Qt documentation. The example below shows this in action on a simple PyQtGraph plot.

python
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMainWindow, QApplication
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
import sys  # We need sys so that we can pass argv to QApplication
import os

class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        hour = [1,2,3,4,5,6,7,8,9,10]
        temperature = [30,32,34,32,33,31,29,32,35,45]

        #Add Background colour to white
        self.graphWidget.setBackground('w')
        # Add Title
        self.graphWidget.setTitle("Your Title Here", color="b", size="30pt")
        # Add Axis Labels
        styles = {"color": "#f00", "font-size": "20px"}
        self.graphWidget.setLabel("left", "Temperature (°C)", **styles)
        self.graphWidget.setLabel("bottom", "Hour (H)", **styles)
        #Add legend
        self.graphWidget.addLegend()
        #Add grid
        self.graphWidget.showGrid(x=True, y=True)
        #Set Range
        self.graphWidget.setXRange(0, 10, padding=0)
        self.graphWidget.setYRange(20, 55, padding=0)

        pen = pg.mkPen(color=(255, 0, 0))

        self.graphWidget.plot(hour, temperature, name="Sensor 1",  pen=pen, symbol='+', symbolSize=30, symbolBrush=('b'))

        # Set the cursor for the plotwidget. The mouse cursor will change when over the plot.
        cursor = Qt.CrossCursor
        self.graphWidget.setCursor(cursor)


app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec_()

If you run this you'll see a cross-hair cursor appear when you hover your mouse over the plot.

Mouse cursor as crosshair Crosshair cursor on plot

Drawing a custom cursor

If you want to show a completely custom cursor, for example an full-size crosshair over the plot, things are a little trickier. In this case you need to draw the cursor yourself by adding elements to the plot and then listening for mouse events and updating their position.

The code below is an updated version of the plot above, with two crosshair lines added using PyQtGraphs InfiniteLine objects. The lines are drawn in the __init__ method, and then updated in the mouseMoved method.

You could also add any other QGraphicsScene item here instead.

The important part is the SignalProxy which forwards mouse movements to our custom method update_crosshair. The rateLimit parameter sets the maximum number of signals per second which will be sent, to avoid overloading our handler/app with excessive events.

python
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import Qt
from pyqtgraph import PlotWidget, plot
import pyqtgraph as pg
import sys  # We need sys so that we can pass argv to QApplication
import os


class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        hour = [1,2,3,4,5,6,7,8,9,10]
        temperature = [30,32,34,32,33,31,29,32,35,45]

        #Add Background colour to white
        self.graphWidget.setBackground('w')
        # Add Title
        self.graphWidget.setTitle("Your Title Here", color="b", size="30pt")
        # Add Axis Labels
        styles = {"color": "#f00", "font-size": "20px"}
        self.graphWidget.setLabel("left", "Temperature (°C)", **styles)
        self.graphWidget.setLabel("bottom", "Hour (H)", **styles)
        #Add legend
        self.graphWidget.addLegend()
        #Add grid
        self.graphWidget.showGrid(x=True, y=True)
        #Set Range
        self.graphWidget.setXRange(0, 10, padding=0)
        self.graphWidget.setYRange(20, 55, padding=0)

        pen = pg.mkPen(color=(255, 0, 0))
        self.graphWidget.plot(hour, temperature, name="Sensor 1",  pen=pen, symbol='+', symbolSize=30, symbolBrush=('b'))

        # Add crosshair lines.
        self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
        self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
        self.graphWidget.addItem(self.crosshair_v, ignoreBounds=True)
        self.graphWidget.addItem(self.crosshair_h, ignoreBounds=True)

        self.proxy = pg.SignalProxy(self.graphWidget.scene().sigMouseMoved, rateLimit=60, slot=self.update_crosshair)

    def update_crosshair(self, e):
        pos = e[0]
        if self.graphWidget.sceneBoundingRect().contains(pos):
            mousePoint = self.graphWidget.getPlotItem().vb.mapSceneToView(pos)
            self.crosshair_v.setPos(mousePoint.x())
            self.crosshair_h.setPos(mousePoint.y())


app = QApplication(sys.argv)
main = MainWindow()
main.show()
app.exec_()



If you run this example, in addition to the mouse cursor you'll also see a faint yellow line overlaying the plot.

Drawing the custom cursor lines The custom cursor with default mouse cursor hidden

To hide the default mouse cursor over the plot set the curser type to Qt.BlankCursor. For example.

python
class MainWindow(QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        # ... rest of __init__ as before

        # Hide the cursor over the plot.
        cursor = Qt.BlankCursor
        self.graphWidget.setCursor(cursor)


This will give the following result when hovering over the plot.

Drawing the custom cursor lines The custom cursor with default mouse cursor hidden

For more, see the complete PyQt5 tutorial.

Q&A: How to fix widgets appearing as separate windows? — Understanding Qt parents and layouts and the effect on widget position

One very common issue when you start creating GUI applications with Python & Qt is having your widgets either disappear or start popping out of your interface as independent windows. This is pretty confusing if you don't understand why it happens, but once you do it's usually very easy to fix.

The key points --

  1. Any widget without a parent is a window. If you create a widget but don't set a parent it will become a separate window.
  2. Widgets without parents (i.e. any windows) are hidden by default and remain hidden until they are shown either by you, or automatically by Qt (for example when selecting tabs in a QTabWidget).
  3. Adding widgets to layouts sets their parent. Widgets not in layouts must have parents set explicitly.
  4. Removing a widget from a layout doesn't remove its parent.
  5. You can remove the parent from a widget by setting the parent to None. This will turn it into a window!

Below is a small example to demonstrate these effects. We create a window, add a layout and a widget.

python
import sys
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication

class Window(QWidget):

    def __init__(self):
        super().__init__()

        # This is the widget that we will experiment with: note that we don't
        # set a parenet, and don't add it to the layout.
        self.mywidget = QLabel("Hello")
        self.mywidget.show()  # We need to show it (or focus it) to make it visible.

        add_parent = QPushButton("Add parent")
        add_parent.clicked.connect(self.add_parent)

        remove_parent = QPushButton("Remove parent")
        remove_parent.clicked.connect(self.remove_parent)

        add_layout = QPushButton("Add layout")
        add_layout.clicked.connect(self.add_layout)

        remove_layout = QPushButton("Remove layout")
        remove_layout.clicked.connect(self.remove_layout)

        self.vlayout = QVBoxLayout()

        self.vlayout.addWidget(add_parent)
        self.vlayout.addWidget(remove_parent)
        self.vlayout.addWidget(add_layout)
        self.vlayout.addWidget(remove_layout)

        self.setLayout(self.vlayout)

    def add_parent(self):
        self.mywidget.setParent(self)
        self.mywidget.move(0,0)
        self.mywidget.show()
        print("Added parent, parent is:", self.mywidget.parent())

    def remove_parent(self):
        self.mywidget.setParent(None)
        self.mywidget.show()
        print("Removed parent, parent is:", self.mywidget.parent())

    def add_layout(self):
        self.vlayout.addWidget(self.mywidget)
        print("Added layout, parent is:", self.mywidget.parent())

    def remove_layout(self):
        self.vlayout.removeWidget(self.mywidget)
        print("Removed layout, parent is:", self.mywidget.parent())

app = QApplication(sys.argv)

w = Window()
w.show()

app.exec()

python
import sys
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication


class Window(QWidget):

    def __init__(self):
        super().__init__()

        # This is the widget that we will experiment with: note that we don't
        # set a parenet, and don't add it to the layout.
        self.mywidget = QLabel("Hello")
        self.mywidget.show()  # We need to show it (or focus it) to make it visible.

        add_parent = QPushButton("Add parent")
        add_parent.clicked.connect(self.add_parent)

        remove_parent = QPushButton("Remove parent")
        remove_parent.clicked.connect(self.remove_parent)

        add_layout = QPushButton("Add layout")
        add_layout.clicked.connect(self.add_layout)

        remove_layout = QPushButton("Remove layout")
        remove_layout.clicked.connect(self.remove_layout)

        self.vlayout = QVBoxLayout()

        self.vlayout.addWidget(add_parent)
        self.vlayout.addWidget(remove_parent)
        self.vlayout.addWidget(add_layout)
        self.vlayout.addWidget(remove_layout)

        self.setLayout(self.vlayout)

    def add_parent(self):
        self.mywidget.setParent(self)
        self.mywidget.move(0,0)
        self.mywidget.show()
        print("Added parent, parent is:", self.mywidget.parent())

    def remove_parent(self):
        self.mywidget.setParent(None)
        self.mywidget.show()
        print("Removed parent, parent is:", self.mywidget.parent())

    def add_layout(self):
        self.vlayout.addWidget(self.mywidget)
        print("Added layout, parent is:", self.mywidget.parent())

    def remove_layout(self):
        self.vlayout.removeWidget(self.mywidget)
        print("Removed layout, parent is:", self.mywidget.parent())


app = QApplication(sys.argv)

w = Window()
w.show()

app.exec()

python
import sys
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication


class Window(QWidget):

    def __init__(self):
        super().__init__()

        # This is the widget that we will experiment with: note that we don't
        # set a parenet, and don't add it to the layout.
        self.mywidget = QLabel("Hello")
        self.mywidget.show()  # We need to show it (or focus it) to make it visible.

        add_parent = QPushButton("Add parent")
        add_parent.clicked.connect(self.add_parent)

        remove_parent = QPushButton("Remove parent")
        remove_parent.clicked.connect(self.remove_parent)

        add_layout = QPushButton("Add layout")
        add_layout.clicked.connect(self.add_layout)

        remove_layout = QPushButton("Remove layout")
        remove_layout.clicked.connect(self.remove_layout)

        self.vlayout = QVBoxLayout()

        self.vlayout.addWidget(add_parent)
        self.vlayout.addWidget(remove_parent)
        self.vlayout.addWidget(add_layout)
        self.vlayout.addWidget(remove_layout)

        self.setLayout(self.vlayout)

    def add_parent(self):
        self.mywidget.setParent(self)
        self.mywidget.move(0,0)
        self.mywidget.show()
        print("Added parent, parent is:", self.mywidget.parent())

    def remove_parent(self):
        self.mywidget.setParent(None)
        self.mywidget.show()
        print("Removed parent, parent is:", self.mywidget.parent())

    def add_layout(self):
        self.vlayout.addWidget(self.mywidget)
        print("Added layout, parent is:", self.mywidget.parent())

    def remove_layout(self):
        self.vlayout.removeWidget(self.mywidget)
        print("Removed layout, parent is:", self.mywidget.parent())

app = QApplication(sys.argv)

w = Window()
w.show()

app.exec_()

python
import sys
from PySide6.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel, QApplication


class Window(QWidget):

    def __init__(self):
        super().__init__()

        # This is the widget that we will experiment with: note that we don't
        # set a parenet, and don't add it to the layout.
        self.mywidget = QLabel("Hello")
        self.mywidget.show()  # We need to show it (or focus it) to make it visible.

        add_parent = QPushButton("Add parent")
        add_parent.clicked.connect(self.add_parent)

        remove_parent = QPushButton("Remove parent")
        remove_parent.clicked.connect(self.remove_parent)

        add_layout = QPushButton("Add layout")
        add_layout.clicked.connect(self.add_layout)

        remove_layout = QPushButton("Remove layout")
        remove_layout.clicked.connect(self.remove_layout)

        self.vlayout = QVBoxLayout()

        self.vlayout.addWidget(add_parent)
        self.vlayout.addWidget(remove_parent)
        self.vlayout.addWidget(add_layout)
        self.vlayout.addWidget(remove_layout)

        self.setLayout(self.vlayout)

    def add_parent(self):
        self.mywidget.setParent(self)
        self.mywidget.move(0,0)
        self.mywidget.show()
        print("Added parent, parent is:", self.mywidget.parent())

    def remove_parent(self):
        self.mywidget.setParent(None)
        self.mywidget.show()
        print("Removed parent, parent is:", self.mywidget.parent())

    def add_layout(self):
        self.vlayout.addWidget(self.mywidget)
        print("Added layout, parent is:", self.mywidget.parent())

    def remove_layout(self):
        self.vlayout.removeWidget(self.mywidget)
        print("Removed layout, parent is:", self.mywidget.parent())

app = QApplication(sys.argv)

w = Window()
w.show()

app.exec_()

In the initial state the widget isn't added to any layout and has no parent, so when the application is run you'll actually see two floating windows. Push the control buttons to experiment with adding the widget to the layout, setting a parent, clearing the parent and removing it from the layout.

The starting view

Each time the state changes the current parent will be printed to the console. Notice that when you add the widget to the layout, the widget receives the Window as it's parent. This is the normal behavior in Qt: adding a widget to a layout will set it's parent to the widget on which the layout is applied (the container widget). In this case this is the Window itself.

python
Added layout, parent is: <__main__.Window(0x15413656090) at 0x000001542CB98688>

If you remove the widget from the layout, it's parent is not automatically cleared. The widget will remain inside the window, near it's original position. But the layout will re-lay the other items over it.

The widget is removed from the layout, but remains in place

If you remove the parent the widget will return to it's floating state.

Note that when setting the parent on the window we also call .move(0, 0) to move it to a specific point relative to the parent window. This is necessary to ensure we can see the widget: if you remove this, setting the parent will just make the widget disappear. If you have widgets disappearing in your apps, this is a good that they have a parent set but are not properly positioned or added to a layout. As a general rule, use layouts to avoid hitting these kinds of issues!

For an in-depth guide to building Python GUIs with PyQt5 see my book, Create GUI Applications with Python & Qt5.

KDE Frameworks – Part 1: KConfig

The KDE Community has been developing a variety of Free Software products using Qt for 25 years now. Among them, the Plasma Desktop Environment, creativity tools like Krita and Kdenlive, educational applications like GCompris, groupware suites like Kontact and countless other applications, utilities, and widgets.

Qt is famous for its rich set of high-quality, cross-platform APIs. However, it does not cover every single use case. Indeed, that would be impossible. So, to fill in the gaps, over time, KDE has created code that has been incorporated into many KDE projects. To foster reusing these battle-tested solutions outside KDE projects, we share this code in the form of modular libraries.

We call these libraries KDE Frameworks.

Currently, there are 83 KDE Frameworks available that offer a wide range of features. For example, KNotifications allows you to create popup notifications on Windows, macOS, Linux, and Android without having to write platform-specific code. Other Frameworks provide wrappers for specialized libraries or interfaces, making them easier to use by Qt programmers. The bluez-qt framework, for example, provides a Qt-style interface to the bluez D-Bus API. Some Frameworks are a collection of useful classes like KWidgetsAddons, which contains a number of useful widgets that are not part of QtWidgets.

As a Qt developer, you likely have used software built using KDE Frameworks without even knowing it. The syntax-highlighting framework that powers KDE applications like Kate and KDevelop is also used in Qt Creator.

A text editor showing source code with syntax highlighting

Syntax highlighting in Kate is powered by the Syntax Highlighting Framework

There are many advantages to leveraging KDE Frameworks. In this series, we will examine some of them, providing practical and real-world examples that will help you learn how to incorporate KDE Frameworks into your own products.

In this first blog in the series, I’d like to introduce you to KConfig.

KConfig is one of the most used frameworks. It allows developers to store and fetch configuration data in the filesystem. Its basic functionality is similar to Qt’s own QSettings, however it provides several additional features.

Before we can use KConfig in our application, we need to add it to our build system. For CMake, this is done as follows:

find_package(KF5Config)

...

target_link_libraries(myapp PRIVATE KF5::ConfigCore)

If your application uses QMake, all you need is:

QT += KConfigCore

The following code shows the basic usage of KConfig:

#include <KConfig>
#include <KConfigGroup>
#include <QDebug>

int main() {

    KConfig config("myappsettings");

    KConfigGroup general = config.group("General");

    qDebug() << general.readEntry("someSetting", "A default value");

    general.writeEntry("someSetting", "A new value");

    qDebug() << general.readEntry("someSetting", "A default value");

}

First, a KConfig object is created. By default, the config is saved to a file with the specified name in QStandardPaths::GenericConfigLocation, however the exact location can be tweaked.

The config entries are organized in groups. Each KConfig object can contain a number of groups and each group holds a number of key-value pairs containing the config data.

To read a config entry, first create a KConfigGroup from the KConfig object and then use readEntry to query a specific key. readEntry takes an optional default value that is used when no data for that key is stored.

To write a setting, writeEntry is used. The data is not immediatley written to the disk. When the KConfigGroup object is destructed, all pending write operations are executed. It is possible to force writing to the disk by using the sync() method.

So far, all of this is possible to do with QSettings as well. So, what’s the benefit of using KConfig?

Both QSettings and KConfig allow for config cascading. Here, config values are read from two locations: a system-wide one and a per-user one. This allows the defining of system-wide defaults and gives users the ability to override the value for them. However, in an enterprise setup, this may be undesirable. KConfig allows system administrators to mark settings as immutable to prevent users from overriding the provided default. This does not require any code changes in the application. The application can query whether a certain key is marked as immutable to disable the relevant UI pieces.

Sometimes two processes access the same config file. Here, it’s important that a process is notified when the other process changes the config so it can react accordingly. KConfigWatcher allows notifying another process about a config change. It does this via D-Bus. So, it works only on systems where D-Bus is available (i.e. Linux).

This plain usage of KConfig (and QSettings) has a number of drawbacks. The library/compiler has no information about the structure of the configuration data. Most of the access is done using string identifiers. These are prone to typing errors and the compiler cannot verify those at build time. There is also no information about the data type of a configuration entry, e.g., whether an entry is a single string, a list of strings, or an integer. Another problem is that KConfig cannot directly be used in a QML context.

KConfig offers the KConfigXT mechanism that solves both of these problems. It is based on a XML description of the configuration data structure. At compile time this information is used to generate a C++ class that is used to access the config. The class can also have the entries exposed as properties so it can be consumed by QML directly.

The above example expressed as XML description looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
      http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
  <kcfgfile name="myappsettings" />
  <group name="General">
    <entry name="someSetting" type="String">
      <label>A setting</label>
      <default>A default value</default>
    </entry>
  </group>
</kcfg>

This is stored in the myappsettings.kcfg file.

The behavior of KConfigXT is controlled by a separate config file, myappsettings.kcfgc:

File=myappsettings.kcfg
ClassName=MyAppSettings
Mutators=true
DefaultValueGetters=true
GenerateProperties=true

Then the above code example becomes:

#include "myappsettings.h"
#include <QDebug>

int main() {

    MyAppSettings settings;

    qDebug() << settings.someSetting();

    settings.setSomeSetting("A new value");

    qDebug() << settings.someSetting();
}

You can also expose the settings to QML by doing:

qmlRegisterSingletonInstance<MyAppSettings>("myapp", 1, 0, "Settings", &settings);

and use them via properties:

import myapp 1.0

console.log(Settings.someSetting)

The settings code is generated by the kconfig_compiler_kf5 executable. As a developer you usually don’t interact with it directly though. Instead there is a CMake macro that takes care of the details.

kconfig_add_kcfg_files(myapp myappsettings.kcfgc)

For a full introduction to KConfigXT, please consult its documentation.

Stay tuned for more posts introducing interesting KDE Frameworks!

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post KDE Frameworks – Part 1: KConfig appeared first on KDAB.

New Release: KD Reports 2.0.0

KD ReportsVersion 2.0.0 of KD Reports has just been released!

KD Reports creates all kinds of reports from within Qt applications. These reports are printable and exportable from code and XML descriptions. KD Reports is a developer tool used in source code, but it allows the use of templates that are created by design staff. Reports may contain text paragraphs, tables, headlines, charts, headers and footers and more. Read more about KD Reports here.

KD Reports 2.0.0 is the first version that fully supports Qt 6 as well as Qt 5. Both the Qt 6 version and Qt 5 version can be installed at the same time. Additionally, the QMake buildsystem has been removed in this version of KD Reports in favour of CMake. Last but not least, Python bindings for KD Reports have been added.

The release highlights can be found at: https://github.com/KDAB/KDReports/releases/tag/kdreports-2.0.0

About KDAB

If you like this article and want to read similar material, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

The post New Release: KD Reports 2.0.0 appeared first on KDAB.

Qt Multimedia has a new friend...

 QtMultimedia just got a new platform to run on - Qt for WebAssembly!


I am happy to announce a new platform contribution for Qt Multimedia.

As of change https://codereview.qt-project.org/c/qt/qtmultimedia/+/348150 and very much thanks to Qt contributor, Raffaele Pertile, we now have audio play and record on the WebAssembly platform for Qt 6.2.

This is possible  as Emscripten has built-in support for OpenAL, which this patch uses to push audio data around. So now, all your Qt webAssembly apps can have sound effects! \0/