Create Your Apps Faster With Qt

If you could create your app in 5 instead of 6 months, would it matter? If you could build the first prototype within 4 hours instead of one week, would it matter?

We as Product Managers always have had three dimensions to play with when steering a software development project: Scope, Time, and Quality. The number of developers is most of the time fixed for multiple reasons. The amount of quality issues customers are willing to tolerate is limited. That leaves us typically with two dimensions to manage the expectations of management and customers: Scope and Time. But there is another way to increase R&D velocitywithout cutting down the Minimum Viable Product to a bare minimum.

You might be wondering if you already squeezed everything out of your product creation workflow. I might have a suggestion for you if you are looking beyond agile methodologies, automated unit testing, and continuous deployment to optimize R&D efficiency:
Switch to an Integrated Software Development Platform which breaks up organizational silos. Switch your UI framework to a Cross-Platform framework that brings Designers, Technical Artists, Software Developers, and Testers together as one team. Bringing these people together as one team working on one codebase removes waste in every single development iteration and improves, therefore, your time to market. Let me give you an example:

In my previous job, I used to be the Product Manager of a B2B cloud solution. Every time we added a major feature, the UI Designers – having interpreted my ugly hand-drawn sketch of what customers wanted – came up first with a wireframe and then in another iteration with the visual design. Once a handful of strategic customers gave their input to the first visual design, the UI Designer reiterated the design and then gave their work product to the software developer. The work product of the UI Designer was a PDF file or a PNG image. The Front-End Developer had to start from scratch to implement a functional UI.

Qt Charts in Qt 6.2

The Qt Charts module provides the possibility to display various types of charts in order to visualize data. It is available in the Qt framework already for a quite long time, but the framework itself constantly evolves and it’s being improved all the time, so there were changes made to this framework as well. The newest, long-term support (LTS) version of the Qt framework is Qt 6.2 and this version includes most of the changes and improvements. Take a look at our blog post to learn more about improvements made to the Qt Charts in Qt 6.2.

Qt WebAssembly clipboard

Clipboard use on desktop platforms is ubiquitous. Most people use it without thinking. Copy, Paste, and Cut keyboard strokes are in-grained into muscle memory. 

Qt Developer Conference

We are very happy to announce that the Qt Developer Conference is back on track after having been postponed last September. Mark your calendars and save the date on 13th-15th June 2022. This will be our first in-person event since the pandemic started. Do not miss out on this wonderful experience!

We opened the ticket shop for the Qt DevCon again. Get your early-bird tickets for a temporarily reduced price here: https://www.qtdevcon.com/tickets/

About the Qt Developer Conference

The Qt Developer Conference is a new addition to our catalogue of events, covering any aspect of software development with Qt: embedded, widgets, Qt Quick — you name it!  If you’re a developer building applications with Qt, this conference is for you. The event will take place at Radialsystem in Berlin, a beautiful, industrial event location at the banks of River Spree, not far away from Alexanderplatz and Berlin Ostbahnhof. On the agenda, there will be 1 day of training and 2 days of technical talks during the main event.

Here, you can find a full list of all the trainings that will be offered during the training day on 13th June: https://www.qtdevcon.com/training-day/

For a list of talks and their speakers during the main event on 14th and 15th June, look here: https://www.qtdevcon.com/speakers/

More information is available on the Qt Dev Con website, where you can also sign up to receive updates.

We are aware, that under the current circumstances even thinking to attend a live event seems a bit out of place. Nevertheless, we have chosen the date in June to minimize the odds of being affected by another Covid wave. Measures will be taken to make the event as safe as possible. Also, if we would have to cancel the event after all, tickets will be refunded.

We are looking forward to meeting you in Berlin! Stay safe!

The post Qt Developer Conference appeared first on KDAB.

The new Qt Quick Compiler - get QML to run at a speed close to native

As most of you know, QML is an interpreted language. The flexibility of any interpreted language always comes with a potential decrease in performance. As we are very convinced of many other potentials of QML, we strive to reduce - if not to completely eliminate - this unpleasant potential. We implemented changes in the last Qt5 releases and especially in Qt6 helping to take a significant step towards our long term goal: make QML run at a speed close to native. This blog post explains what is new. The upcoming two blog posts will elaborate the technology and its development history.



Today, we are proud to announce that we will introduce the new Qt Quick Compiler™ with Qt 6.3. The new Qt Quick Compiler will consist of two components: the QML Type Compiler and QML Script Compiler. The QML Type Compiler will compile QML object structures into C++ classes. The QML Script Compiler will compile functions and expressions in QML files of an application into C++ code. As much as possible, since there will be some limits set by the nature of JavaScript. If certain statements cannot be compiled, regular interpretation and caching will be used. Some changes in the code of the application might be needed to achieve the best possible results over time. New QML tools will help you detect these. For example, the new QML Lint, for example, will tell you which parts of the code cause fallbacks into interpretation. We will expand and improve QML tools in the future in our aim of making them a guide to Best Practices for all components in Qt Quick. The closer you follow the recommendations reported by these tools, the more QML code can be compiled and, thus, run faster. Speaking of "faster": one of the future blog posts will also show the first benchmark results.

As an initial step we are providing a Technology Preview of the QML Script Compiler for commercial customers. It is available as of today in the "Qt for Device Creation" package released with Qt 6.2.1. This Technology Preview will continue in 6.2.x releases of "Qt for Device Creation". Starting with Qt 6.3, the base components of the Qt Quick Compiler will also be integrated in the QtDeclarative module. This is an important step for us to ensure the creation of better and faster QML code by all users in the Qt community. While the QML Script Compiler might pass its Technology Preview already in 6.3, the Technology Preview for the QML Type Compiler will only start with 6.3. Depending on the results of these preview phases, we will later decide about a first general release.

Starting with Qt 6.3, we will additionally make Qt Quick Compiler Extensions available for Qt commercial users. Qt Quick Compiler Extensions will decrease the refactoring effort to improve coverage of compilation. Qt Quick Compiler Extensions are also planned to offer additional tooling or integrations for existing tools meant to provide a better understanding of how specific parts of the code influence execution performance of the application in the project and which changes in the code are needed to make these parts compile. More extensions are in development. In total, we expect that up to 30% speed-up can be achieved in the startup and execution over time.

Top Contributors to Qt Project in 2021

2021 was a successful year for the Qt - we managed to do important releases like Qt 6.2 and Qt Creator 6 on time and with the planned content. That is however not only because of my colleagues at The Qt Company. A lot of community members are also contributing, be it by writing diligent bug reports, contributing patches, giving technical advise, or helping out other users in forums and mailing lists. Thanks to all of you - you are an important part of what makes Qt so great!

Qbs 1.21 released

The Qbs build tool version 1.21.0 is available.

Qbs is a community-driven language-agnostic build automation system. It is fast and offers an easy-to-learn language based upon QML. 

Using Layouts to Position Widgets in PySide6 — Use layouts to effortlessly position widgets within the window (updated for PySide6)

So far we've successfully created a window, and we've added a widget to it. However we normally want to add more than one widget to a window, and have some control over where it ends up. To do this in Qt we use layouts. There are 4 basic layouts available in Qt, which are listed in the following table.

Layout Behaviour
QHBoxLayout Linear horizontal layout
QVBoxLayout Linear vertical layout
QGridLayout In indexable grid XxY
QStackedLayout Stacked (z) in front of one another

You can also design and lay out your interface graphically using the Qt designer. Here we're using code, so you can understand the underlying system.

As you can see, there are three positional layouts available in Qt. The VBoxLayout, QHBoxLayout and QGridLayout. In addition there is also QStackedLayout which allows you to place widgets one on top of the other within the same space, yet showing only one layout at a time.

Before we start we need a simple application outline. Save the following code in a file named app.py -- we'll modify this application to experiment with different layouts.

python
import sys
from PySide6.QtWidgets import QApplication, QMainWindow, QWidget
from PySide6.QtGui import QPalette, QColor

class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

To make it easier to visualize the layouts, we'll first create a simple custom widget that displays a solid color of our choosing. This will help to distinguish widgets that we add to the layout. Add the following code to your file as a new class at the top level --

python
class Color(QWidget):

    def __init__(self, color):
        super(Color, self).__init__()
        self.setAutoFillBackground(True)

        palette = self.palette()
        palette.setColor(QPalette.Window, QColor(color))
        self.setPalette(palette)

In this code we subclass QWidget to create our own custom widget Color. We accept a single parameter when creating the widget — color (a str). We first set .setAutoFillBackground to True to tell the widget to automatically fill its background with the window cooler. Next we get the current palette (which is the global desktop palette by default) and change the current QPalette.Window color to a new QColor described by the value color we passed in. Finally we apply this palette back to the widget. The end result is a widget that is filled with a solid color, that we specified when we created it.

If you find the above confusing, don't worry too much. We'll cover custom widgets in more detail later. For now it's sufficient that you understand that calling you can create a solid-filled red widget by doing the following:

python
Color('red')

First let's test our new Color widget by using it to fill the entire window in a single color. Once it’s complete we can add it to the QMainWindow using .setCentralWidget and we get a solid red window.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        widget = Color('red')
        self.setCentralWidget(widget)

Run it! The window will appear, filled completely with the color red. Notice how the widget expands to fill all the available space.

Next we'll look at each of the available Qt layouts in turn. Note that to add our layouts to the window we will need a dummy QWidget to hold the layout.

QVBoxLayout vertically arranged widgets

With QVBoxLayout you arrange widgets one above the other linearly. Adding a widget adds it to the bottom of the column.

A QVBoxLayout, filled from top to bottom. A QVBoxLayout, filled from top to bottom.

Let’s add our widget to a layout. Note that in order to add a layout to the QMainWindow we need to apply it to a dummy QWidget. This allows us to then use .setCentralWidget to apply the widget (and the layout) to the window. Our colored widgets will arrange themselves in the layout, contained within the QWidget in the window. First we just add the red widget as before.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout = QVBoxLayout()

        layout.addWidget(Color('red'))

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

Run it! Notice the border now visible around the red widget. This is the layout spacing — we'll see how to adjust that later.

If you add a few more colored widgets to the layout you’ll notice that they line themselves up vertical in the order they are added.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout = QVBoxLayout()

        layout.addWidget(Color('red'))
        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QHBoxLayout horizontally arranged widgets

QHBoxLayout is the same, except moving horizontally. Adding a widget adds it to the right hand side.

A QHBoxLayout, filled from left to right. A QHBoxLayout, filled from left to right.

To use it we can simply change the QVBoxLayout to a QHBoxLayout. The boxes now flow left to right.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout = QHBoxLayout()

        layout.addWidget(Color('red'))
        layout.addWidget(Color('green'))
        layout.addWidget(Color('blue'))

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

Nesting layouts

For more complex layouts you can nest layouts inside one another using .addLayout on a layout. Below we add a QVBoxLayout into the main QHBoxLayout. If we add some widgets to the QVBoxLayout, they’ll be arranged vertically in the first slot of the parent layout.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout1 = QHBoxLayout()
        layout2 = QVBoxLayout()
        layout3 = QVBoxLayout()

        layout2.addWidget(Color('red'))
        layout2.addWidget(Color('yellow'))
        layout2.addWidget(Color('purple'))

        layout1.addLayout( layout2 )

        layout1.addWidget(Color('green'))

        layout3.addWidget(Color('red'))
        layout3.addWidget(Color('purple'))

        layout1.addLayout( layout3 )

        widget = QWidget()
        widget.setLayout(layout1)
        self.setCentralWidget(widget)

Run it! The widgets should arrange themselves in 3 columns horizontally, with the first column also containing 3 widgets stacked vertically. Experiment!

You can set the spacing around the layout using .setContentMargins or set the spacing between elements using .setSpacing.

python
layout1.setContentsMargins(0,0,0,0)
layout1.setSpacing(20)

The following code shows the combination of nested widgets and layout margins and spacing. Experiment with the numbers til you get a feel for them.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout1 = QHBoxLayout()
        layout2 = QVBoxLayout()
        layout3 = QVBoxLayout()

        layout1.setContentsMargins(0,0,0,0)
        layout1.setSpacing(20)

        layout2.addWidget(Color('red'))
        layout2.addWidget(Color('yellow'))
        layout2.addWidget(Color('purple'))

        layout1.addLayout( layout2 )

        layout1.addWidget(Color('green'))

        layout3.addWidget(Color('red'))
        layout3.addWidget(Color('purple'))

        layout1.addLayout( layout3 )

        widget = QWidget()
        widget.setLayout(layout1)
        self.setCentralWidget(widget)

QGridLayout widgets arranged in a grid

As useful as they are, if you try and using QVBoxLayout and QHBoxLayout for laying out multiple elements, e.g. for a form, you’ll find it very difficult to ensure differently sized widgets line up. The solution to this is QGridLayout.

A QGridLayout showing the grid positions for each location. A QGridLayout showing the grid positions for each location.

QGridLayout allows you to position items specifically in a grid. You specify row and column positions for each widget. You can skip elements, and they will be left empty.

Usefully, for QGridLayout you don't need to fill all the positions in the grid.

A QGridLayout with unfilled slots. A QGridLayout with unfilled slots.

python
class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")

        layout = QGridLayout()

        layout.addWidget(Color('red'), 0, 0)
        layout.addWidget(Color('green'), 1, 0)
        layout.addWidget(Color('blue'), 1, 1)
        layout.addWidget(Color('purple'), 2, 1)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QStackedLayout multiple widgets in the same space

The final layout we’ll cover is the QStackedLayout. As described, this layout allows you to position elements directly in front of one another. You can then select which widget you want to show. You could use this for drawing layers in a graphics application, or for imitating a tab-like interface. Note there is also QStackedWidget which is a container widget that works in exactly the same way. This is useful if you want to add a stack directly to a QMainWindow with .setCentralWidget.

QStackedLayout — in use only the uppermost widget is visible, which is by default the first widget added to the layout. QStackedLayout — in use only the uppermost widget is visible, which is by default the first widget added to the layout.

QStackedLayout, with the 2nd (1) widget selected and brought to the front. QStackedLayout, with the 2nd (1) widget selected and brought to the front.

python
from PySide6.QtWidgets import QStackedLayout  # add this import


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

        self.setWindowTitle("My App")

        layout = QStackedLayout()

        layout.addWidget(Color("red"))
        layout.addWidget(Color("green"))
        layout.addWidget(Color("blue"))
        layout.addWidget(Color("yellow"))

        layout.setCurrentIndex(3)

        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

QStackedWidget is exactly how tabbed views in applications work. Only one view ('tab') is visible at any one time. You can control which widget to show at any time by using .setCurrentIndex() or .setCurrentWidget() to set the item by either the index (in order the widgets were added) or by the widget itself.

Below is a short demo using QStackedLayout in combination with QButton to to provide a tab-like interface to an application:

python
import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QHBoxLayout,
    QLabel,
    QMainWindow,
    QPushButton,
    QStackedLayout,
    QVBoxLayout,
    QWidget,
)

from layout_colorwidget import Color


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

        self.setWindowTitle("My App")

        pagelayout = QVBoxLayout()
        button_layout = QHBoxLayout()
        self.stacklayout = QStackedLayout()

        pagelayout.addLayout(button_layout)
        pagelayout.addLayout(self.stacklayout)

        btn = QPushButton("red")
        btn.pressed.connect(self.activate_tab_1)
        button_layout.addWidget(btn)
        self.stacklayout.addWidget(Color("red"))

        btn = QPushButton("green")
        btn.pressed.connect(self.activate_tab_2)
        button_layout.addWidget(btn)
        self.stacklayout.addWidget(Color("green"))

        btn = QPushButton("yellow")
        btn.pressed.connect(self.activate_tab_3)
        button_layout.addWidget(btn)
        self.stacklayout.addWidget(Color("yellow"))

        widget = QWidget()
        widget.setLayout(pagelayout)
        self.setCentralWidget(widget)

    def activate_tab_1(self):
        self.stacklayout.setCurrentIndex(0)

    def activate_tab_2(self):
        self.stacklayout.setCurrentIndex(1)

    def activate_tab_3(self):
        self.stacklayout.setCurrentIndex(2)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


A custom tab-like interface implemented using QStackedLayout. A custom tab-like interface implemented using QStackedLayout.

Helpfully. Qt actually provide a built-in TabWidget that provides this kind of layout out of the box - albeit in widget form. Below the tab demo is recreated using QTabWidget:

python
import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QTabWidget,
    QWidget,
)

from layout_colorwidget import Color


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

        self.setWindowTitle("My App")

        tabs = QTabWidget()
        tabs.setTabPosition(QTabWidget.West)
        tabs.setMovable(True)

        for n, color in enumerate(["red", "green", "blue", "yellow"]):
            tabs.addTab(Color(color), color)

        self.setCentralWidget(tabs)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

A tabbed interface using the QTabWidget. A tabbed interface using the QTabWidget.

As you can see, it's a little more straightforward — and a bit more attractive! You can set the position of the tabs using the cardinal directions, toggle whether tabs are moveable with .setMoveable. You'll notice that the macOS tab bar looks quite different to the others -- by default on macOS tabs take on a pill or bubble style. On macOS this is typically used for tabbed configuration panels. For documents, you can turn on document mode to give slimline tabs similar to what you see on other platforms. This option has no effect on other platforms.

python
    tabs = QTabWidget()
    tabs.setDocumentMode(True)

QTabWidget in document mode on macOS. QTabWidget in document mode on macOS.

We'll encounter more of these advanced widgets later.

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

Qt WebAssembly clipboard

Clipboard use on the desktop platforms is ubiquitous. Most people use it without thinking. Copy, Paste and Cut keyboard strokes are in-grained into muscle memory. 

On the web it can present security issues as someone could read or write to your clipboard without you knowing.

Up until now, Qt for WebAssembly's clipboard was text only and only within the app itself. Qt 6.3 will have better clipboard support between host and app but also adds copy/pasting of images.

WebAssembly is a sandboxed platform like javascript. There are some extra security hurdles in doing some common things such as copy and paste of binary data such as images. One issue is clipboard use between the host platform and the browser sandbox. Allowing the web app to have access to the clipboard in which it could send arbitrary data without the user knowing could be dangerous for the user.

Browsers generally allow clipboard during user generated events such as when a user makes common key sequences such as [ctrl | command] c - the copy keys.

Qt itself has support for programmatically copying text and binary data such as images and works great on the desktop, but which presents issues for web browsers. There are work-arounds, like using a hidden javascript element and the javascript function execCommand to "copy". However, this function has been depreciated. 

By using the asynchronous Clipboard API and making use of javascript clipboard events where possible, we can bring image clipboard support to Qt WebAssembly. The Clipboard API requires a secure context (https) for full feature use. Among other things, the Clipboard API allows image and arbitrary data to be copied and pasted. Whereas before, only text mime types were supported. 

This API is of course implemented in the different browsers in different ways. On Firefox, read() and write() are only partially implemented and is hidden behind about:config settings. As well, copy/paste of arbitrary binary data does not seem to be supported and mostly silently fails.

Here are the ways browsers support Clipboard API:

Firefox 

  • write() is available without permission in secure contexts and browser extensions, but only from user-initiated event callbacks.
  • Clipboard API
    • secure context (https or localhost)
    • dom.events.asyncClipboard
    • dom.events.asyncClipboard.clipboardItem
    • dom.events.asyncClipboard.read
    • dom.events.testing.asyncClipboard
Safari
  • Clipboard API
    • secure context    
Chrome
  • Clipboard API
    • secure context
    • user permissions
  • read() only supports
    • text/html
    • text/png

Also included in the now merged commit f0be152896471aa392bb1b2b649b66feb31480cc  is a clipboard manual test app that can be used on both desktop and webassembly to test clipbpoard use.

You can use the clipboard without a secure https context, but you won't get interaction between host and web app.

Drag & drop widgets with PyQt — Sort widgets visually with drag and drop in a container

This week I had an interesting question from a reader of my PyQt6 book, about how to handle dragging and dropping of widgets in a container showing the dragged widget as it is moved.

I'm interested in managing movement of a QWidget with mouse in a container. I've implemented the application with drag & drop, exchanging the position of buttons, but I want to show the motion of QPushButton, like what you see in Qt Designer. Dragging a widget should show the widget itself, not just the mouse pointer.

First, we'll implement the simple case which drags widgets without showing anything extra. Then we can extend it to answer the question.

Drag & drop widgets

We'll start with this simple application which creates a window using QWidget and places a series of QPushButton widgets into it.

You can substitute QPushButton for any other widget you like, e.g. QLabel

python
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton


class Window(QWidget):

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

        self.blayout = QHBoxLayout()
        for l in ['A', 'B', 'C', 'D']:
            btn = QPushButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)


app = QApplication([])
w = Window()
w.show()

app.exec_()


If you run this you should see something like this.

Widgets in a layout The series of QPushButton widgets in a horizontal layout.

Here we're creating a window, but the Window widget is subclassed from QWidget, meaning you can add this widget to any other layout. See later for an example of a generic object sorting widget.

QPushButton objects aren't usually draggable, so to handle the mouse movements and initiate a drag we need to implement a subclass. We can add the following to the top of the file.

python
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag

class DragButton(QPushButton):

    def mouseMoveEvent(self, e):

        if e.buttons() == Qt.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec_(Qt.MoveAction)

We implement a mouseMoveEvent which accepts the single e parameter of the event. We check to see if the left mouse button is pressed on this event -- as it would be when dragging -- and then initiate a drag. To start a drag, we create a QDrag object, passing in self to give us access later to the widget that was dragged. We also must pass in mime data. This is used for including information about what is dragged, particularly for passing data between applications. However, as here, it is fine to leave this empty.

Finally, we initiate a drag by calling drag.exec_(Qt.MoveAction). As with dialogs exec_() starts a new event loop, blocking the main loop until the drag is complete. The parameter Qt.MoveAction tells the drag handler what type of operation is happening, so it can show the appropriate icon tip to the user.

You can update the main window code to use our new DragButton class as follows.

python
class Window(QWidget):

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

        self.blayout = QHBoxLayout()
        for l in ['A', 'B', 'C', 'D']:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)


If you run the code now, you can drag the buttons, but you'll notice the drag is forbidden.

Drag forbidden Dragging of the widget starts but is forbidden.

What's happening? The mouse movement is being detected by our DragButton object and the drag started, but the main window does not accept drag & drop.

To fix this we need to enable drops on the window and implement dragEnterEvent to actually accept them.

python
class Window(QWidget):

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

        self.blayout = QHBoxLayout()
        for l in ['A', 'B', 'C', 'D']:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

If you run this now, you'll see the drag is now accepted and you see the move icon. This indicates that the drag has started and been accepted by the window we're dragging onto. The icon shown is determined by the action we pass when calling drag.exec_().

Drag accepted Dragging of the widget starts and is accepted, showing a move icon.

Releasing the mouse button during a drag drop operation triggers a dropEvent on the widget you're currently hovering the mouse over (if it is configured to accept drops). In our case that's the window. To handle the move we need to implement the code to do this in our dropEvent method.

The drop event contains the position the mouse was at when the button was released & the drop triggered. We can use this to determine where to move the widget to.

python
    def dropEvent(self, e):
        pos = e.pos()
        widget = e.source()

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x():
                # We didn't drag past this widget.
                # insert to the left of it.
                self.blayout.insertWidget(n-1, widget)
                break

        e.accept()

To determine where to place the widget, we iterate over all the widgets in the layout, until we find one who's x position is greater than that of the mouse pointer. If so then when insert the widget directly to the left of this widget and exit the loop.

The effect of this is that if you drag 1 pixel past the start of another widget, which might be a bit confusing. You adjust this the cut off to use the middle of the widget using if pos.x() < w.x() + w.size().width() // 2: -- that is x + half of the width.

python
    def dropEvent(self, e):
        pos = e.pos()
        widget = e.source()

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                self.blayout.insertWidget(n-1, widget)
                break

        e.accept()

The complete working drag-drop code is shown below.

python
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag


class DragButton(QPushButton):

    def mouseMoveEvent(self, e):

        if e.buttons() == Qt.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)
            drag.exec_(Qt.MoveAction)


class Window(QWidget):

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

        self.blayout = QHBoxLayout()
        for l in ['A', 'B', 'C', 'D']:
            btn = DragButton(l)
            self.blayout.addWidget(btn)

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.pos()
        widget = e.source()

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if pos.x() < w.x() + w.size().width() // 2:
                # We didn't drag past this widget.
                # insert to the left of it.
                self.blayout.insertWidget(n-1, widget)
                break

        e.accept()


app = QApplication([])
w = Window()
w.show()

app.exec_()

Visual drag & drop

So now we have our working drag & drop implementation we can move on to showing the drag visually. What we want to achieve here is showing the button being dragged next to the mouse point as it is dragged.

Qt's QDrag handler natively provides a mechanism for showing dragged objects which we can use. We can update our DragButton class to pass a pixmap image to QDrag and this will be displayed under the mouse pointer as the drag occurs. To show the widget, we just need to get a QPixmap of the widget we're dragging.

python
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QPushButton
from PyQt5.QtCore import Qt, QMimeData
from PyQt5.QtGui import QDrag, QPixmap


class DragButton(QPushButton):

    def mouseMoveEvent(self, e):

        if e.buttons() == Qt.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec_(Qt.MoveAction)

To create the pixmap we create a QPixmap object passing in the size of the widget this event is fired on with self.size(). This creates an empty pixmap which we can then pass into self.render to render -- or draw -- the current widget onto it. That's it. Then we set the resulting pixmap on the drag object.

If you run the code with this modification you'll see something like the following --

Drag visual Dragging of the widget showing the dragged widget.

Generic drag & drop container

We can take this a step further and implement a generic drag drop widget which allows us to sort arbitrary objects. In the code below we've created a new widget DragWidget which can be added to any window. You can add items -- instances of DragItem -- which you want to be sorted, as well as setting data on them. When items are re-ordered the new order is emitted as a signal orderChanged.

python
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget, QLabel, QMainWindow, QVBoxLayout
from PyQt5.QtCore import Qt, QMimeData, pyqtSignal
from PyQt5.QtGui import QDrag, QPixmap


class DragItem(QLabel):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setContentsMargins(25, 5, 25, 5)
        self.setAlignment(Qt.AlignmentFlag.AlignCenter)
        self.setStyleSheet("border: 1px solid black;")
        # Store data separately from display label, but use label for default.
        self.data = self.text()

    def set_data(self, data):
        self.data = data

    def mouseMoveEvent(self, e):

        if e.buttons() == Qt.LeftButton:
            drag = QDrag(self)
            mime = QMimeData()
            drag.setMimeData(mime)

            pixmap = QPixmap(self.size())
            self.render(pixmap)
            drag.setPixmap(pixmap)

            drag.exec_(Qt.MoveAction)


class DragWidget(QWidget):
    """
    Generic list sorting handler.
    """

    orderChanged = pyqtSignal(list)

    def __init__(self, *args, orientation=Qt.Orientation.Vertical, **kwargs):
        super().__init__()
        self.setAcceptDrops(True)

        # Store the orientation for drag checks later.
        self.orientation = orientation

        if self.orientation == Qt.Orientation.Vertical:
            self.blayout = QVBoxLayout()
        else:
            self.blayout = QHBoxLayout()

        self.setLayout(self.blayout)

    def dragEnterEvent(self, e):
        e.accept()

    def dropEvent(self, e):
        pos = e.pos()
        widget = e.source()

        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            if self.orientation == Qt.Orientation.Vertical:
                # Drag drop vertically.
                drop_here = pos.y() < w.y() + w.size().height() // 2
            else:
                # Drag drop horizontally.
                drop_here = pos.x() < w.x() + w.size().width() // 2

            if drop_here:
                # We didn't drag past this widget.
                # insert to the left of it.
                self.blayout.insertWidget(n-1, widget)
                self.orderChanged.emit(self.get_item_data())
                break

        e.accept()

    def add_item(self, item):
        self.blayout.addWidget(item)

    def get_item_data(self):
        data = []
        for n in range(self.blayout.count()):
            # Get the widget at each index in turn.
            w = self.blayout.itemAt(n).widget()
            data.append(w.data)
        return data


class MainWindow(QMainWindow):

    def __init__(self):
        super().__init__()
        self.drag = DragWidget(orientation=Qt.Orientation.Vertical)
        for n, l in enumerate(['A', 'B', 'C', 'D']):
            item = DragItem(l)
            item.set_data(n)  # Store the data.
            self.drag.add_item(item)

        # Print out the changed order.
        self.drag.orderChanged.connect(print)

        container = QWidget()
        layout = QVBoxLayout()
        layout.addStretch(1)
        layout.addWidget(self.drag)
        layout.addStretch(1)
        container.setLayout(layout)

        self.setCentralWidget(container)


app = QApplication([])
w = MainWindow()
w.show()

app.exec_()

Generic drag drop horizontal Generic drag-drop sorting in horizontal orientation.

You'll notice that when creating the item, you can set the label by passing it in as a parameter (just like for a normal QLabel which we've subclassed from). But you can also set a data value, which is the internal value of this item -- this is what will be emitted when the order changes, or if you call get_item_data yourself. This separates the visual representation from what is actually being sorted, meaning you can use this to sort anything not just strings.

In the example above we're passing in the enumerated index as the data, so dragging will output (via the print connected to orderChanged) something like:

python
[1, 0, 2, 3]
[1, 2, 0, 3]
[1, 0, 2, 3]
[1, 2, 0, 3]

If you remove the item.set_data(n) you'll see the labels emitted on changes.

python
['B', 'A', 'C', 'D']
['B', 'C', 'A', 'D']

We've also implemented orientation onto the DragWidget using the Qt built in flags Qt.Orientation.Vertical or Qt.Orientation.Horizontal. This setting this allows you sort items either vertically or horizontally -- the calculations are handled for both directions.

Generic drag drop vertical Generic drag-drop sorting in vertical orientation.

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

QML 3D: First impressions

If we think about it for a minute, it is impossible to imagine an app without a UI. We need buttons, checkboxes, dropdown menus and much more. Almost all of these controls are created using 2D elements. However, sometimes we need to put some depth into the UI.

How tu customise installer behaviour with Qt Installer Framework

In previous posts of this series, we briefly touched on the topic of scripting, while describing how to add custom pages to the installer UI. In this entry we will go much deeper. You will be able to learn what types of scripts are available and how to create them in the Qt Installer Framework – all of that with some handy examples. Let’s go!

How to use NFC in Qt/Qml application?

Have you ever wondered how to use NFC in Qt/Qml application? This blog post will give you a complex overview of the concept of near-field communication technology with examples of its usage in real life. Then you will discover how to implement NFC reading and writing in Qt Qml application.

How to generate barcode in Qt/QML application

Nowadays, everywhere we look, no matter if it’s a real-life shop or web page, we can see these little, simple and useful, black and white rectangles. Barcodes & QR codes are everywhere, with their usefulness proven over the years. That is why we decided to create a custom Qt & QML wrapper for barcode & QR code processing. In this post, we will show how easy it is to generate and display a barcode and what’s going on behind the scenes in our code.

How to customize installer UI with Qt Installer Framework

In the previous entry you learned the basics of the app deployment process and installer generation. However Qt Installer Framework offers much more than that! What more can you do with this tool? For example, you can customize an installer UI in many different ways and this is what you will learn in this post.

Deploying app and generating offline installers for Windows Qt Installer Framework tutorial

Development is not an only part of product delivering – deployment and maintenance are both equally important parts of the product lifecycle. That is why Qt lend us a hand by providing Installer Framework. It is a set of tools that allows not only to create good-looking and functional installers but also update the app, provide tools for maintaining it, and much more. In this tutorial, you will learn the basics of Qt Installer Framework and find out how to generate your first offline installer for Windows.

How to interface Qt with Android Java code

Qt and QML provide solutions to almost every problem. However there are some platform-specific edge cases when using native code can make things nice and easy – all thanks to Qt tools allowing you to use native code in your project. In this post, we will take a closer look at how to interface Android Java code with Qt.

Creating Dialogs and Alerts in PySide6 — Notify your users and ask for their input (updated for PySide6)

Dialogs are useful GUI components that allow you to communicate with the user (hence the name dialog). They are commonly used for file Open/Save, settings, preferences, or for functions that do not fit into the main UI of the application. They are small modal (or blocking) windows that sit in front of the main application until they are dismissed. Qt provides a number of 'special' built-in dialogs for the most common use-cases, allowing you to provide a platform-native user experience.

Standard GUI features — A search dialog Standard GUI features — A search dialog

Standard GUI features — A file Open dialog Standard GUI features — A file Open dialog

In Qt dialog boxes are handled by the QDialog class. To create a new dialog box simply create a new object of QDialog type passing in another widget, e.g. QMainWindow, as its parent.

Let's create our own QDialog. We'll start with a simple skeleton app with a button to press hooked up to a slot method.

python
import sys

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton


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

        self.setWindowTitle("My App")

        button = QPushButton("Press me for a dialog!")
        button.clicked.connect(self.button_clicked)
        self.setCentralWidget(button)

    def button_clicked(self, s):
        print("click", s)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

In the slot button_clicked (which receives the signal from the button press) we create the dialog instance, passing our QMainWindow instance as a parent. This will make the dialog a modal window of QMainWindow. This means the dialog will completely block interaction with the parent window.

python
import sys

from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton


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

        self.setWindowTitle("My App")

        button = QPushButton("Press me for a dialog!")
        button.clicked.connect(self.button_clicked)
        self.setCentralWidget(button)

    def button_clicked(self, s):
        print("click", s)

        dlg = QDialog(self)
        dlg.setWindowTitle("HELLO!")
        dlg.exec_()


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()


Run it! Click the button and you'll see an empty dialog appear.

Once we have created the dialog, we start it using .exec_() - just like we did for QApplication to create the main event loop of our application. That’s not a coincidence: when you exec the QDialog an entirely new event loop - specific for the dialog - is created.

The QDialog completely blocks your application execution. Don't start a dialog and expect anything else to happen anywhere else in your app. We'll see later how you can use threads & processes to get you out of this pickle.

Our empty dialog overlaying the window. Our empty dialog overlaying the window.

Like our very first window, this isn't very interesting. Let's fix that by adding a dialog title and a set of OK and Cancel buttons to allow the user to accept or reject the modal.

To customize the QDialog we can subclass it.

python
class CustomDialog(QDialog):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("HELLO!")

        QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.layout = QVBoxLayout()
        message = QLabel("Something happened, is that OK?")
        self.layout.addWidget(message)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)


In the above code, we first create our subclass of QDialog which we've called CustomDialog. As for the QMainWindow we apply our customizations in the class __init__ block so our customizations are applied as the object is created. First we set a title for the QDialog using .setWindowTitle(), exactly the same as we did for our main window.

The next block of code is concerned with creating and displaying the dialog buttons. This is probably a bit more involved than you were expecting. However, this is due to Qt's flexibility in handling dialog button positioning on different platforms.

You could of course choose to ignore this and use a standard QButton in a layout, but the approach outlined here ensures that your dialog respects the host desktop standards (OK on left vs. right for example). Messing around with these behaviors can be incredibly annoying to your users, so I wouldn't recommend it.

The first step in creating a dialog button box is to define the buttons want to show, using namespace attributes from QDialogButtonBox. The full list of buttons available is below:

  • QDialogButtonBox.Ok
  • QDialogButtonBox.Open
  • QDialogButtonBox.Save
  • QDialogButtonBox.Cancel
  • QDialogButtonBox.Close
  • QDialogButtonBox.Discard
  • QDialogButtonBox.Apply
  • QDialogButtonBox.Reset
  • QDialogButtonBox.RestoreDefaults
  • QDialogButtonBox.Help
  • QDialogButtonBox.SaveAll
  • QDialogButtonBox.Yes
  • QDialogButtonBox.YesToAll
  • QDialogButtonBox.No
  • QDialogButtonBox.Abort
  • QDialogButtonBox.Retry
  • QDialogButtonBox.Ignore
  • QDialogButtonBox.NoButton

These should be sufficient to create any dialog box you can think of. You can construct a line of multiple buttons by OR-ing them together using a pipe (|). Qt will handle the order automatically, according to platform standards. For example, to show an OK and a Cancel button we used:

python
buttons = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

The variable buttons now contains an integer value representing those two buttons. Next, we must create the QDialogButtonBox instance to hold the buttons. The flag for the buttons to display is passed in as the first parameter.

To make the buttons have any effect, you must connect the correct QDialogButtonBox signals to the slots on the dialog. In our case we've connected the .accepted and .rejected signals from the QDialogButtonBox to the handlers for .accept() and .reject() on our subclass of QDialog.

Lastly, to make the QDialogButtonBox appear in our dialog box we must add it to the dialog layout. So, as for the main window we create a layout, and add our QDialogButtonBox to it (QDialogButtonBox is a widget), and then set that layout on our dialog.

Finally, we launch the CustomDialog in our MainWindow.button_clicked slot.

python
class MainWindow(QMainWindow):

    # ... add the following method after the __init__

    def button_clicked(self, s):
        print("click", s)

        dlg = CustomDialog()
        if dlg.exec_():
            print("Success!")
        else:
            print("Cancel!")

Run it! Click to launch the dialog and you will see a dialog box with buttons.

Our dialog with a label and buttons. Our dialog with a label and buttons.

When you click the button to launch the dialog, you may notice that it appears away from the parent window -- probably in the center of the screen. Normally you want dialogs to appear over their launching window to make them easier for users to find. To do this we need to give Qt a parent for the dialog. If we pass our main window as the parent, Qt will position the new dialog so that the center of the dialog aligns with the center of the window.

We can modify our CustomDialog class to accept a parent parameter.

python
class CustomDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("HELLO!")

        QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.layout = QVBoxLayout()
        message = QLabel("Something happened, is that OK?")
        self.layout.addWidget(message)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)

We set a default value of parent=None so we can omit the parent if we wish.

Then, when we create our instance of CustomDialog we can pass the main window in as a parameter. In our button_clicked method, self is our main window object.

python
    def button_clicked(self, s):
        print("click", s)

        dlg = CustomDialog(self)
        if dlg.exec_():
            print("Success!")
        else:
            print("Cancel!")


Run it! Click to launch the dialog and you should see the dialog pop up right in the middle of the parent window.

Our dialog, centered over the parent window. Our dialog, centered over the parent window.

Congratulations! You've created your first dialog box. Of course, you can continue to add any other content to the dialog box that you like. Simply insert it into the layout as normal.

Simple message dialogs with QMessageBox

There are many dialogs which follow the simple pattern we just saw -- a message with buttons with which you can accept or cancel the dialog. While you can construct these dialogs yourself, Qt also provides a built-in message dialog class called QMessageBox. This can be used to create information, warning, about or question dialogs.

The example below creates a simple QMessageBox and shows it.

python
import sys

from PySide6.QtWidgets import QApplication, QDialog, QMainWindow, QMessageBox, QPushButton


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

        self.setWindowTitle("My App")

        button = QPushButton("Press me for a dialog!")
        button.clicked.connect(self.button_clicked)
        self.setCentralWidget(button)

    def button_clicked(self, s):
        dlg = QMessageBox(self)
        dlg.setWindowTitle("I have a question!")
        dlg.setText("This is a simple dialog")
        button = dlg.exec_()

        if button == QMessageBox.Ok:
            print("OK!")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()

Run it! You'll see a simple dialog with an OK button.

A QMessageBox dialog. A QMessageBox dialog.

As with the dialog button box we looked at already, the buttons shown on a QMessageBox are also configured with a set of constants which can be combined with | (the binary OR operator) to show multiple buttons. The full list of available button types is shown below.

  • QMessageBox.Ok
  • QMessageBox.Open
  • QMessageBox.Save
  • QMessageBox.Cancel
  • QMessageBox.Close
  • QMessageBox.Discard
  • QMessageBox.Apply
  • QMessageBox.Reset
  • QMessageBox.RestoreDefaults
  • QMessageBox.Help
  • QMessageBox.SaveAll
  • QMessageBox.Yes
  • QMessageBox.YesToAll
  • QMessageBox.No
  • QMessageBox.NoToAll
  • QMessageBox.Abort
  • QMessageBox.Retry
  • QMessageBox.Ignore
  • QMessageBox.NoButton

You can also tweak the icon shown on the dialog by setting the icon with one of the following.

Icon state Description
QMessageBox.NoIcon The message box does not have an icon.
QMessageBox.Question The message is asking a question.
QMessageBox.Information The message is informational only.
QMessageBox.Warning The message is warning.
QMessageBox.Critical The message indicates a critical problem.

For example, the following creates a question dialog with Yes and No buttons.

python
    def button_clicked(self, s):
        dlg = QMessageBox(self)
        dlg.setWindowTitle("I have a question!")
        dlg.setText("This is a question dialog")
        dlg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
        dlg.setIcon(QMessageBox.Question)
        button = dlg.exec_()

        if button == QMessageBox.Yes:
            print("Yes!")
        else:
            print("No!")

Run it! You'll see a question dialog with Yes and No buttons.

Question dialog created using QMessageBox. Question dialog created using QMessageBox.

Built in QMessageBox dialogs

To make things even simpler the QMessageBox has a number of methods which can be used to construct these types of message dialog. These methods are shown below --

python
QMessageBox.about(parent, title, message)
QMessageBox.critical(parent, title, message)
QMessageBox.information(parent, title, message)
QMessageBox.question(parent, title, message)
QMessageBox.warning(parent, title, message)

The parent parameter is the window which the dialog will be a child of. If you're launching your dialog from your main window, you can just pass in self. The following example creates a question dialog, as before, with Yes and No buttons.

python
    def button_clicked(self, s):

        button = QMessageBox.question(self, "Question dialog", "The longer message")

        if button == QMessageBox.Yes:
            print("Yes!")
        else:
            print("No!")

Run it! You'll see the same result, this time using the built in .question() method.

The built-in question dialog. The built-in question dialog.

Notice that rather than call exec() we now simply call the dialog method and the dialog is created. The return value of each of the methods is the button which was pressed. We can detect what has been pressed by comparing the return value to the button constants.

The four information, question, warning and critical methods also accept optional buttons and defaultButton arguments which can be used to tweak the buttons shown on the dialog and select one by default. Generally though you don't want to change this from the default.

python
    def button_clicked(self, s):

        button = QMessageBox.critical(
            self,
            "Oh dear!",
            "Something went very wrong.",
            buttons=QMessageBox.Discard | QMessageBox.NoToAll | QMessageBox.Ignore,
            defaultButton=QMessageBox.Discard,
        )

        if button == QMessageBox.Discard:
            print("Discard!")
        elif button == QMessageBox.NoToAll:
            print("No to all!")
        else:
            print("Ignore!")

Run it! You'll see a critical dialog with customized buttons.

Critical error! This is a terrible dialog. Critical error! This is a terrible dialog.

For most situations these simple dialogs are all you need.

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

QML Extension for Visual Studio Code: Develop Qt Quick Apps with VS Code and Felgo Live

Visual Studio Code ranks #1 among the most popular development tools in the annual developers survey of Stack Overflow. More than 70% of all participants use this code editor for development. And there’s a reason why: It is fast, reliable, supports a wide range of languages and runs on Windows, macOS and Linux.

With the right extensions and tools, you can set up and use Visual Studio Code to develop your Qt and QML projects. Watch the tutorial or read this guide to learn how:

 

Heaptrack v1.3.0 Release

Version 1.3.0 of the KDE Heaptrack project was just released by KDAB’s Milian Wolff.

Heaptrack is a heap memory profiler on Linux-based operating systems. It can help you find hotspots that need to be optimized for reducing memory usage, memory leaks, allocation hotspots, and temporary allocations.

Included in this release is a special new feature that NetworkRADIUS hired KDAB to develop: support for custom suppression files, including support for per-application embedded default suppression lists. This can be done by the same API that is already used by LSAN: __lsan_default_suppressions. KDAB is always more than happy to make improvements to products for their customers. And this change even made it into the very next release! Thanks to NetworkRADIUS for bringing this forward.

This release also brings you filtering by time ranges. All you have to do to filter by time range is select a range of time and right click. The ability to see the difference between the two time points that results from this action is a very helpful new feature in the workflow of the heap memory analysis.

heaptrack

Heaptrack v1.3.0 comes with many more important bug fixes and improvements and new features.

To read more about what’s new in Heaptrack 1.3.0 and to get the source code, visit https://invent.kde.org/sdk/heaptrack/-/releases/v1.3.0.

To read a bit of Heaptrack’s backstory, visit https://www.kdab.com/heaptrack-v1-0-0-release/.

If you’re interested in hiring KDAB to improve a product for your team of developers, please feel free to contact us at the office nearest you to find out how we can help.

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 Heaptrack v1.3.0 Release appeared first on KDAB.

An introduction to PyQt6 Signals, Slots and Events — Triggering actions in response to user behaviors and GUI events (updated for PyQt6)

So far we've created a window and added a simple push button widget to it, but the button doesn't do anything. That's not very useful at all -- when you create GUI applications you typically want them to do something! What we need is a way to connect the action of pressing the button to making something happen. In Qt, this is provided by signals and slots or events.

Signals & Slots

Signals are notifications emitted by widgets when something happens. That something can be any number of things, from pressing a button, to the text of an input box changing, to the text of the window changing. Many signals are initiated by user action, but this is not a rule.

In addition to notifying about something happening, signals can also send data to provide additional context about what happened.

You can also create your own custom signals, which we'll explore later.

Slots is the name Qt uses for the receivers of signals. In Python any function (or method) in your application can be used as a slot -- simply by connecting the signal to it. If the signal sends data, then the receiving function will receive that data too. Many Qt widgets also have their own built-in slots, meaning you can hook Qt widgets together directly.

Let's take a look at the basics of Qt signals and how you can use them to hook widgets up to make things happen in your apps.

Save the following app outline to a file named app.py.

python
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):

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

        self.setWindowTitle("My App")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

QPushButton Signals

Our simple application currently has a QMainWindow with a QPushButton set as the central widget. Let's start by hooking up this button to a custom Python method. Here we create a simple custom slot named the_button_was_clicked which accepts the clicked signal from the QPushButton.

python
import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


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

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)

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

    def the_button_was_clicked(self):
        print("Clicked!")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Run it! If you click the button you'll see the text "Clicked!" on the console.

python
Clicked!
Clicked!
Clicked!
Clicked!

Receiving data

That's a good start! We've heard already that signals can also send data to provide more information about what has just happened. The .clicked signal is no exception, also providing a checked (or toggled) state for the button. For normal buttons this is always False, so our first slot ignored this data. However, we can make our button checkable and see the effect.

In the following example, we add a second slot which outputs the checkstate.

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

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)
        button.clicked.connect(self.the_button_was_toggled)

        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")

    def the_button_was_toggled(self, checked):
        print("Checked?", checked)

Run it! If you press the button you'll see it highlighted as checked. Press it again to release it. Look for the check state in the console.

python
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True

You can connect as many slots to a signal as you like and can respond to different versions of signals at the same time on your slots.

Storing data

Often it is useful to store the current state of a widget in a Python variable. This allows you to work with the values like any other Python variable and without accessing the original widget. You can either store these values as individual variables or use a dictionary if you prefer. In the next example we store the checked value of our button in a variable called button_is_checked on self.

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

        self.button_is_checked = True

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_toggled)
        button.setChecked(self.button_is_checked)

        self.setCentralWidget(button)

    def the_button_was_toggled(self, checked):
        self.button_is_checked = checked

        print(self.button_is_checked)


First we set the default value for our variable (to True), then use the default value to set the initial state of the widget. When the widget state changes, we receive the signal and update the variable to match.

You can use this same pattern with any PyQt widgets. If a widget does not provide a signal that sends the current state, you will need to retrieve the value from the widget directly in your handler. For example, here we're checking the checked state in a pressed handler.

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

        self.button_is_checked = True

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.setCheckable(True)
        self.button.released.connect(self.the_button_was_released)
        self.button.setChecked(self.button_is_checked)

        self.setCentralWidget(self.button)

    def the_button_was_released(self):
        self.button_is_checked = self.button.isChecked()

        print(self.button_is_checked)

We need to keep a reference to the button on self so we can access it in our slot.

The released signal fires when the button is released, but does not send the check state, so instead we use .isChecked() to get the check state from the button in our handler.

Changing the interface

So far we've seen how to accept signals and print output to the console. But how about making something happen in the interface when we click the button? Let's update our slot method to modify the button, changing the text and disabling the button so it is no longer clickable. We'll also turn off the checkable state for now.

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

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        self.button.setText("You already clicked me.")
        self.button.setEnabled(False)

        # Also change the window title.
        self.setWindowTitle("My Oneshot App")


Again, because we need to be able to access the button in our the_button_was_clicked method, we keep a reference to it on self. The text of the button is changed by passing a str to .setText(). To disable a button call .setEnabled() with False.

Run it! If you click the button the text will change and the button will become unclickable.

You're not restricted to changing the button that triggers the signal, you can do anything you want in your slot methods. For example, try adding the following line to the_button_was_clicked method to also change the window title.

python
self.setWindowTitle("A new window title")

Most widgets have their own signals -- and the QMainWindow we're using for our window is no exception. In the following more complex example, we connect the .windowTitleChanged signal on the QMainWindow to a custom slot method.

In the following example we connect the .windowTitleChanged signal on the QMainWindow to a method slot the_window_title_changed. This slot also receives the new window title.

python
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

import sys
from random import choice

window_titles = [
    'My App',
    'My App',
    'Still My App',
    'Still My App',
    'What on earth',
    'What on earth',
    'This is surprising',
    'This is surprising',
    'Something went wrong'
]


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

        self.n_times_clicked = 0

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.windowTitleChanged.connect(self.the_window_title_changed)

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

    def the_button_was_clicked(self):
        print("Clicked.")
        new_window_title = choice(window_titles)
        print("Setting title:  %s" % new_window_title)
        self.setWindowTitle(new_window_title)

    def the_window_title_changed(self, window_title):
        print("Window title changed: %s" % window_title)

        if window_title == 'Something went wrong':
            self.button.setDisabled(True)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

First we set up a list of window titles -- we'll select one at random from this list using Python's built-in random.choice(). We hook our custom slot method the_window_title_changed to the window's .windowTitleChanged signal.

When we click the button the window title will change at random. If the new window title equals "Something went wrong" the button will be disabled.

Run it! Click the button repeatedly until the title changes to "Something went wrong" and the button will become disabled.

There are a few things to notice in this example.

Firstly, the windowTitleChanged signal is not always emitted when setting the window title. The signal only fires if the new title is changed from the previous one. If you set the same title multiple times, the signal will only be fired the first time. It is important to double-check the conditions under which signals fire, to avoid being surprised when using them in your app.

Secondly, notice how we are able to chain things together using signals. One thing happening -- a button press -- can trigger multiple other things to happen in turn. These subsequent effects do not need to know what caused them, but simply follow as a consequence of simple rules. This decoupling of effects from their triggers is one of the key concepts to understand when building GUI applications. We'll keep coming back to this throughout the book!

In this section we've covered signals and slots. We've demonstrated some simple signals and how to use them to pass data and state around your application. Next we'll look at the widgets which Qt provides for use in your applications -- together with the signals they provide.

Connecting widgets together directly

So far we've seen examples of connecting widget signals to Python methods. When a signal is fired from the widget, our Python method is called and receives the data from the signal. But you don't always need to use a Python function to handle signals -- you can also connect Qt widgets directly to one another.

In the following example, we add a QLineEdit widget and a QLabel to the window. In the \\__init__ for the window we connect our line edit .textChanged signal to the .setText method on the QLabel. Now any time the text changes in the QLineEdit the QLabel will receive that text to it's .setText method.

python
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget

import sys


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

        self.setWindowTitle("My App")

        self.label = QLabel()

        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)

        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)

        container = QWidget()
        container.setLayout(layout)

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


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()


Notice that in order to connect the input to the label, the input and label must both be defined. This code adds the two widgets to a layout, and sets that on the window. We'll cover layouts in detail later, you can ignore it for now.

Run it! Type some text in the upper box, and you'll see it appear immediately on the label.

Any text typed in the input immediately appears on the label Any text typed in the input immediately appears on the label.

Most Qt widgets have slots available, to which you can connect any signal that emits the same type that it accepts. The widget documentation has the slots for each widget listed under "Public Slots". For example, see https://doc.qt.io/qt-5/qlabel.html#public-slots[QLabel].

Events

Every interaction the user has with a Qt application is an event. There are many types of event, each representing a different type of interaction. Qt represents these events using event objects which package up information about what happened. These events are passed to specific event handlers on the widget where the interaction occurred.

By defining custom, or extended event handlers you can alter the way your widgets respond to these events. Event handlers are defined just like any other method, but the name is specific for the type of event they handle.

One of the main events which widgets receive is the QMouseEvent. QMouseEvent events are created for each and every mouse movement and button click on a widget. The following event handlers are available for handling mouse events --

Event handler Event type moved
mouseMoveEvent Mouse moved
mousePressEvent Mouse button pressed
mouseReleaseEvent Mouse button released
mouseDoubleClickEvent Double click detected

For example, clicking on a widget will cause a QMouseEvent to be sent to the .mousePressEvent event handler on that widget. This handler can use the event object to find out information about what happened, such as what triggered the event and where specifically it occurred.

You can intercept events by sub-classing and overriding the handler method on the class. You can choose to filter, modify, or ignore events, passing them up to the normal handler for the event by calling the parent class function with super(). These could be added to your main window class as follows. In each case e will receive the incoming event.

python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QLabel("Click in this window")
        self.setCentralWidget(self.label)

    def mouseMoveEvent(self, e):
        self.label.setText("mouseMoveEvent")

    def mousePressEvent(self, e):
        self.label.setText("mousePressEvent")

    def mouseReleaseEvent(self, e):
        self.label.setText("mouseReleaseEvent")

    def mouseDoubleClickEvent(self, e):
        self.label.setText("mouseDoubleClickEvent")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()


Run it! Try moving and clicking (and double-clicking) in the window and watch the events appear.

You'll notice that mouse move events are only registered when you have the button pressed down. You can change this by calling self.setMouseTracking(True) on the window. You may also notice that the press (click) and double-click events both fire when the button is pressed down. Only the release event fires when the button is released. Typically to register a click from a user you should watch for both the mouse down and the release.

Inside the event handlers you have access to an event object. This object contains information about the event and can be used to respond differently depending on what exactly has occurred. We'll look at the mouse event objects next.

Mouse events

All mouse events in Qt are tracked with the QMouseEvent object, with information about the event being readable from the following event methods.

Method Returns
.button() Specific button that triggered this event
.buttons() State of all mouse buttons (OR'ed flags)
.position() Widget-relative position as a QPoint integer

You can use these methods within an event handler to respond to different events differently, or ignore them completely. The positional methods provide both global and local (widget-relative) position information as QPoint objects, while buttons are reported using the mouse button types from the Qt namespace.

For example, the following allows us to respond differently to a left, right or middle click on the window.

python
    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            # handle the left-button press in here
            self.label.setText("mousePressEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            # handle the middle-button press in here.
            self.label.setText("mousePressEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            # handle the right-button press in here.
            self.label.setText("mousePressEvent RIGHT")

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseReleaseEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseReleaseEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseReleaseEvent RIGHT")

    def mouseDoubleClickEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseDoubleClickEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseDoubleClickEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseDoubleClickEvent RIGHT")

The button identifiers are defined in the Qt namespace, as follows --

Identifier Value (binary) Represents
Qt.NoButton 0 (000) No button pressed, or the event is not related to button press.
Qt.LeftButton 1 (001) The left button is pressed
Qt.RightButton 2 (010) The right button is pressed.
Qt.MiddleButton 4 (100) The middle button is pressed.

On right-handed mice the left and right button positions are reversed, i.e. pressing the right-most button will return Qt.LeftButton. This means you don't need to account for the mouse orientation in your code.

Context menus

Context menus are small context-sensitive menus which typically appear when right clicking on a window. Qt has support for generating these menus, and widgets have a specific event used to trigger them. In the following example we're going to intercept the .contextMenuEvent a QMainWindow. This event is fired whenever a context menu is about to be shown, and is passed a single value event of type QContextMenuEvent.

To intercept the event, we simply override the object method with our new method of the same name. So in this case we can create a method on our MainWindow subclass with the name contextMenuEvent and it will receive all events of this type.

python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QMenu


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

    def contextMenuEvent(self, e):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(e.globalPos())


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()


If you run the above code and right-click within the window, you'll see a context menu appear. You can set up .triggered slots on your menu actions as normal (and re-use actions defined for menus and toolbars).

When passing the initial position to the exec function, this must be relative to the parent passed in while defining. In this case we pass self as the parent, so we can use the global position.

Just for completeness, there is actually a signal-based approach to creating context menus.

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

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.on_context_menu)

    def on_context_menu(self, pos):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(self.mapToGlobal(pos))

It's entirely up to you which you choose.

Event hierarchy

In PyQt every widget is part of two distinct hierarchies: the Python object hierarchy, and the Qt layout hierarchy. How you respond or ignore events can affect how your UI behaves.

Python inheritance forwarding

Often you may want to intercept an event, do something with it, yet still trigger the default event handling behavior. If your object is inherited from a standard widget, it will likely have sensible behavior implemented by default. You can trigger this by calling up to the parent implementation using super().

This is the Python parent class, not the PyQt .parent().

python
def mousePressEvent(self, event):
    print("Mouse pressed!")
    super(self, MainWindow).contextMenuEvent(event)

The event will continue to behave as normal, yet you've added some non-interfering behavior.

Layout forwarding

When you add a widget to your application, it also gets another parent from the layout. The parent of a widget can be found by calling .parent(). Sometimes you specify these parents manually, such as for QMenu or QDialog, often it is automatic. When you add a widget to your main window for example, the main window will become the widget's parent.

When events are created for user interaction with the UI, these events are passed to the uppermost widget in the UI. So, if you have a button on a window, and click the button, the button will receive the event first.

If the first widget cannot handle the event, or chooses not to, the event will bubble up to the parent widget, which will be given a turn. This bubbling continues all the way up nested widgets, until the event is handled or it reaches the main window.

In your own event handlers you can choose to mark an event as handled calling .accept() --

python
    class CustomButton(QPushButton)
        def mousePressEvent(self, e):
            e.accept()

Alternatively, you can mark it as unhandled by calling .ignore() on the event object. In this case the event will continue to bubble up the hierarchy.

python
    class CustomButton(QPushButton)
        def event(self, e):
            e.ignore()

If you want your widget to appear transparent to events, you can safely ignore events which you've actually responded to in some way. Similarly, you can choose to accept events you are not responding to in order to silence them.

For more, see the complete PyQt6 tutorial.