Desktop and Mobile Application Development Trends in 2023

With January done and dusted, we wanted to take a look at the year ahead and discuss some of the hottest trends of 2023 for desktop and mobile app development, as whether you're a seasoned developer or just about to set up your new app, it's always good to stay up-to-date with trends to ensure your apps are interesting, user-friendly, and keep up with the competition. 

For extra insight, I’ve roped in Tapio Haantie, our Product Business Line Director for Application Development, Desktop & Mobile.  

KDAB at Embedded World 2023

We will have a great show for you this year at our booth in Hall 4-302 at Embedded World 2023.

As every year, we will present our latest demos, showcasing outstanding performance on cost-effective hardware featuring Qt, C++, Slint, Rust and Flutter.

We know how complex and demanding software development for Embedded Devices can be. Our experts have you covered with your questions around choosing a framework, choosing a CPU, getting started with Embedded Linux, performance optimization, and of course all topics around Qt and tooling.

Watch out for the February news edition to learn more about the demos that will be shown at our booth 4-302.

To get your free ticket for Embedded World use code: ew23web.

See you in Nürnberg!

The post KDAB at Embedded World 2023 appeared first on KDAB.

Can You Charge for Open-Source Software? Making Money from Open-Source Projects

by S.M. Oliva, Leo Well, Martin Fitzpatrick (Python GUIs)

The use of the term "free software" to describe software that is published under open-source licenses has often led to confusion over whether or not developers can actually charge money for their work.

Indeed, this confusion came up in one of the few litigated court cases in the United States involving open-source licenses. In 2006, a federal appeals court in Chicago dismissed a lawsuit brought by a developer -- Williams v. International Business Machines Corp. -- who alleged that various companies and entities responsible for developing Linux had engaged in a massive antitrust conspiracy to "eliminate competition in the operating system market by making Linux available at an unbeatable price," i.e., for nothing.

Frank Easterbrook, the judge who authored the appeals court's decision, explained that the GNU General Public License (GPL) "sets a price of zero" for covered works but that did not qualify as an illegal "price fixing" agreement under U.S. antitrust law. The flaw in Easterbrook's reasoning, however, was that the GPL says nothing of the sort. To the contrary, Section 4 of version 3 of the GPL expressly states:

You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. (Source)

So why did Easterbrook say the GPL sets a price of "zero"? The likely explanation was that in the context of a U.S. appeals court proceeding, Easterbrook took the allegations of the person who filed the lawsuit at face value. The developer who alleged the GPL violated antitrust law claimed the GPL mandated a zero-price, and the courts held that even assuming that was true, there was no antitrust violation since there was no harm to consumers.

Still, the fact that a judicial opinion authored by a prominent U.S. jurist stated the price of GPL-covered works is zero by design reflects an ongoing misunderstanding of how open-source licenses actually work. As we discussed in a prior article, the main goal of reciprocal open-source licenses like the GPL is to ensure that source code remains free and available to all users. But that isn't the same thing as saying an individual developer must provide their code--or modifications to existing code--without charging for it.

Applying Old Rules to Modern Software

It is important to note that the original GPL was published back in 1989. The software market back then was quite different than it is now. For one thing, most software was distributed on physical media like floppy disks and later optical discs or compact discs (CD). Most of that software only contained pre-compiled binary files without any source code. This included a lot of software that was distributed free of charge. Today, of course, most of our software comes from Internet sources.

One thing that may have been lost in this transition is an understanding of charging for open-source software as a normal business practice. To illustrate my point, a few years ago I was at a sale of used books held by my local public library. There was a small section of the sale dedicated to old computer books and media. It turned out that I found a sealed, boxed copy of Corel Linux, a long-forgotten Linux distribution first published in 1999. In addition to a hefty user's manual, the box contained two CD-ROMs. The first contained the pre-compiled version of Corel Linux to install on a computer. The second was the source code for all the files on the first CD-ROM.

This was not an uncommon practice back in the day when you would actually buy Linux distributions at a store. To comply with GPL or similar licensing requirements, the distributor would simply publish a separate CD-ROM with all of the source code. And if you look at section 6 of the current GPL, it states compliance may still be accomplished by distributing object code on a physical medium "accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange." But for most modern developers, it is far more practical to offer access to source code through an online repository on Github, Gitlab, or any other source code hosting service than to provide physical media.

Charging for Software Under the GPL and Other Open-Source Licenses

So what does the GPL actually allow -- or prohibit -- with respect to charging for software? Here is a brief rundown of some key provisions:

  • You can charge any price you wish -- including zero -- for any copy of a licensed work that you convey in either source or binary form.
  • You can charge for providing support for licensed code.
  • You can charge for providing a warranty for licensed code.
  • You may not charge a separate licensing fee or royalty to anyone who exercises their rights to use or convey GPL-licensed code, nor can you require them to collect such fees or royalties on your behalf.
  • You must still provide access to the accompanying source code if you convey GPL-licensed code in binary form.
  • You can charge for the "reasonable cost" of preparing and distributing a physical media if you provide source code in this kind of format.
  • You can not charge to allow access to the source code on an online server -- i.e., on the Internet --

Now, all of these provisions are specific to the GPL. What about non-reciprocal open-source licenses? In general, those licenses contain no restrictions on the sale or distribution of code. For example, the Python Software Foundation License says nothing about when you can and can not charge for software developed with Python. You are free to develop an application using Python and charge for it without even needing to provide access to your source code. The flip side of that, however, is that if you "mix" GPL and non-GPL code, the GPL's provisions will likely still apply to the entire work.

Another thing to consider when developing a commercial application is that while open-source licenses typically address issues related to copyrights in the underlying source code, they often do not cover other intellectual property rights attached to an application such as trademarks, which will be the subject of my next article.

EV Charging is Boring ...

… was the response from many manufacturers at this year’s Consumer Electronics Show (CES) in Las Vegas. CES 2023 was heavily focused on automotive with a big presence from EV charging manufacturers, for both domestic (at home) or commercial (fleet charging) applications. 

The demand for charging options is growing along with the adoption of electric cars (EVs). The market for EV chargers is expanding, and more people are wanting to transition to electric. This presents a big opportunity for EV charger manufacturers. We'll examine more closely at the impending rise of electric charging in this blog post, as well as what it means for EV charger producers.

Product Analytics With Qt Insight – Make Business Decisions Based on Real Usage Data

How are critical product-related decisions made at your company? Whilst other areas of business may rely on years of experience, generic data gathered on the way, or even competitor analysis, digging deeper into how your products or digital solutions perform is required in order to optimize the product development lifecycle. Without an understanding of user flows, customer pain points, or the elements going unused, the product development lifecycle can't be fully optimised, leading to wasted time and resources. Sure, you can always conduct endless feedback loops and research surveys, or even interview your customers to understand how they feel about your products, but the results may not be providing you with an unbiased view – not to mention the time it takes to commit to overseeing qualitative research.

PyQt QCheckBox Widget — Add toggleable Config & Preferences checkbox options

A checkbox is a square-shaped widget used in graphical user interfaces (GUI) to provide users with a way to enable or disable options, or to allow users to enable or disable certain features of the app. Checkboxes usually have an adjacent label, indicating what the checkbox represents.

Checkboxes are usually used to represent boolean state, being either checked (on) or unchecked (off). However, you can also have tri-state checkboxes with a third indeterminate state. In this tutorial, you'll learn how to create and customize checkboxes in your own applications, using the QCheckBox class.

Creating Checkbox Widgets With QCheckBox

The QCheckBox class represents a checkbox widget. A checkbox is an option widget that can be switched on (checked) or off (unchecked) by the user. This widget is common in settings or preferences dialogs where the user fine-tunes an application's configuration.

Checkboxes typically have a square shape looking like a checkbox on a paper form. We can click the box to switch it on and click it again to switch it off. When checked a cross or checkmark symbol will appear in the box. When a checkbox is unchecked, it will appear as an empty square.

If you're using PyQt or PySide to create GUI applications in Python, then you can add checkboxes to your application with the QCheckBox class. This class provides two different constructors that we can use to create checkboxes:

Constructor Description
QCheckBox(parent: QWidget = None) Constructs a checkbox with an optional parent widget but without an associated text
QCheckBox(text: str, parent: QWidget = None) Constructs a checkbox with an associated description text and an optional parent widget

To illustrate how to use the above constructors, let's get into our first example:

python
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Checkbox with no label
        noTextCkBox = QCheckBox()
        # Checkbox with a text label
        textCkBox = QCheckBox(text="Option")
        layout = QVBoxLayout()
        layout.addWidget(noTextCkBox)
        layout.addWidget(textCkBox)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Checkbox with no label
        noTextCkBox = QCheckBox()
        # Checkbox with a text label
        textCkBox = QCheckBox(text="Option")
        layout = QVBoxLayout()
        layout.addWidget(noTextCkBox)
        layout.addWidget(textCkBox)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we import QCheckBox from the QtWidgets module. We create two QCheckBox instances in the __init__ method of the Window class. The first is created using the constructor that does not require a text label.

Both constructors take an optional parent widget, but since we are adding the checkbox widgets to a layout, we don't need to pass a parent when contructing them.

Next up, we use the second constructor of QCheckBox to create another checkbox, this time with descriptive label. The text will display on the right of the checkbox. The text is a regular Python string. This label would typically be used to describe the option to be enabled or disabled, but here is just "Option".

Save this code to a .py file and run it. You'll get a window that looks something like this:

QCheckBox constructors

As expected, the first checkbox has no associated text. It's just a square box on the window. This isn't too helpful, because it doesn't provide any information to your users. However, it can be useful when creating an array of checkboxes laid out to present some data in a table or a list.

The second checkbox on the window looks more familiar -- it has an associated text.

Both checkboxes are fully functional. We can check and uncheck them by clicking on them or -- in the case of the second example -- on the label. However, the checkboxes don't do any action yet, they just change their internal state from checked to unchecked and the other way around. To give a checkbox purpose we need to check the state and take action accordingly ourselves.

In most UIs checkboxes typically don't trigger external actions when toggled. Use buttons to launch or trigger things.

Normal checkboxes have two internal states Checked and Unchecked, while tri-state checkboxes add a third PartiallyChecked state. Next we'll look at how to check and set these states on both two-state and tri-state checkboxes.

Getting and Setting Checkbox state

The most important property of a checkbox is its internal state. This holds whether the checkbox is checked or not. By checking this property, you can take appropriate action in your application.

There are two main methods for checking the checkbox internal state -- isChecked() and checkState(). The possible values returned for a two-state checkbox are shown below:

Methods Behavior
isChecked() Will return True if checkbox is checked, False if it is unchecked.
checkState() In two-state checkboxes this will return either Checked and Unchecked.

Consider the following example:

python
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.checkBox = QCheckBox(text="Unchecked")
        layout = QVBoxLayout()
        layout.addWidget(self.checkBox)
        self.setLayout(layout)
        self.checkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        if self.checkBox.isChecked():
            self.checkBox.setText("Checked")
        else:
            self.checkBox.setText("Unchecked")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.checkBox = QCheckBox(text="Unchecked")
        layout = QVBoxLayout()
        layout.addWidget(self.checkBox)
        self.setLayout(layout)
        self.checkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        if self.checkBox.isChecked():
            self.checkBox.setText("Checked")
        else:
            self.checkBox.setText("Unchecked")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we first create a QCheckBox instance with the text "Click me!" on it. In this case, we're defining a two-state checkbox, which represents the default behavior of this widget.

Then we create the app's layout and add the checkbox. Finally, we connect the stateChanged() signal with the onStateChanged() slot or method. This signal is emitted whenever we click the checkbox, and the onStateChanged() method is automatically called.

You can use the stateChanged() signal to trigger something to happen each time the checkbox changes its state. To connect the stateChanged() signal to a slot, you use the standard syntax:

python
checkbox.<signal>.connect(<method>)

In this construct, checkbox is the QCheckBox object we need to connect to a given slot. The <signal> name is stateChanged. Finally, <method> represents the target slot or method we want to connect the signal.

The onStateChanged() method uses isChecked() to determine the current state of our checkbox. This method returns True if the checkbox is checked and False if it's unchecked. The checkbox text changes alternatively from Checked to Unchecked depending on the result of this condition:

Two-state checkbox

Whenever you click the checkbox on this window, the text changes from Checked to Unchecked alternatively.

It's usually not a good idea to change a checkbox label with the state as it's confusing. Does the "Unchecked" label next to a unchecked checkbox mean checked or unchecked?

We could have used checkState() to determine the current state of our checkbox in the above example. However, using isChecked() feels more natural when working with two-state checkboxes.

When we are working with tri-state checkboxes, checkState() is our only option. We'll see how to work with checkState() in the next section.

Building and Working With Tri-State Checkboxes

In addition to regular two-state checkboxes, you can optionally create tri-state checkboxes. This type of checkbox comes in handy when we want to give our user the option to set the checkbox in an intermediate state known as a partially checked state. This state means that the checkbox is neither checked nor unchecked.

Tri-state checkboxes are typically used to represent groups of other checkboxes, allowing them to all be turned on or off in one go. The partially checked state then indicates that some of the children are on and some are off.

When using tri-state checkboxes, the isChecked() and checkState() methods return the following values --

Methods Behavior
isChecked() Will return True if checkbox is checked or partially checked, False if it is unchecked.
checkState() In three-state checkboxes this will return either Checked, Unchecked or PartiallyChecked .

The QCheckBox class has a Boolean tristate property that indicates whether the checkbox is tri-state. This property defaults to False. If we set it to True, we turn our checkbox into a tri-state checkbox. To change the value of tristate, you need to use the setTristate() with a boolean value as an argument.

You can also turn a checkbox tri-state by setting a partially checked state on it, using .setState(Qt.CheckState.PartiallyChecked).

Consider the following example:

python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.triStateCheckBox = QCheckBox(text="Unchecked")
        self.triStateCheckBox.setTristate(True)
        layout = QVBoxLayout()
        layout.addWidget(self.triStateCheckBox)
        self.setLayout(layout)
        self.triStateCheckBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        state = self.triStateCheckBox.checkState()
        if state == Qt.CheckState.Checked:
            self.triStateCheckBox.setText("Checked")
        elif state == Qt.CheckState.PartiallyChecked:
            self.triStateCheckBox.setText("Partially checked")
        else:
            self.triStateCheckBox.setText("Unchecked")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtCore import Qt
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.triStateCheckBox = QCheckBox(text="Unchecked")
        self.triStateCheckBox.setTristate(True)
        layout = QVBoxLayout()
        layout.addWidget(self.triStateCheckBox)
        self.setLayout(layout)
        self.triStateCheckBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        state = self.triStateCheckBox.checkState()
        if state == Qt.CheckState.Checked:
            self.triStateCheckBox.setText("Checked")
        elif state == Qt.CheckState.PartiallyChecked:
            self.triStateCheckBox.setText("Partially checked")
        else:
            self.triStateCheckBox.setText("Unchecked")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we create a regular checkbox using the QCheckBox() constructor. To set this checkbox as tri-state, you call .setTristate() with True as an argument. From now on, our checkbox will have three states represented by the following values:

Constant Value Description
Qt.CheckState.Unchecked 0 The checkbox is unchecked.
Qt.CheckState.PartiallyChecked 1 The checkbox is partially checked.
Qt.CheckState.Checked 2 The checkbox is checked.

In tri-state checkboxes, the isChecked() method will return True whether the checkbox is checked or partially checked, and False if it is unchecked.

If you want to distinguish between checked and partially checked states, you need to use checkState() directly. Calling checkState() returns one of the constants in the above table, representing the current state of the checkbox.

Our onStateChanged() method is connected to the stateChanged signal of the checkbox. In this method, we get the current state by calling checkState() on the checkbox and storing the result in the variable state. We then check the value of this variable against the different states and take action accordingly. In this example, we alternatively change the checkbox text to reflect its state.

Here's how this example works in practice:

Tri-state checkbox

When you click the checkbox, its text changes from Unchecked to Partially checked and finally to Checked. These are the three possible states of this kind of checkbox.

While we used the checkState() method above, the stateChanged signal actually sends the current state, as an integer value, to the slot. We can map that into the correct state in the method directly by passing it to Qt.CheckState. For example.

python
    def onStateChanged(self, state):
        state = Qt.CheckState(state)
        if state == Qt.CheckState.Checked:
            self.triStateCheckBox.setText("Checked")
        elif state == Qt.CheckState.PartiallyChecked:
            self.triStateCheckBox.setText("Partially checked")
        else:
            self.triStateCheckBox.setText("Unchecked")

In this alternative implementation of onStateChanged(), we receive a state argument that we map to a Qt.CheckState object. The rest of the code remains the same.

The mapping step is only required in PyQt6. In both PyQt5, PySide2, and PySide6 this is done automatically.

It's up to you which implementation to use in your code.

Using Checkboxes in Practice

We can use checkbox widgets to provide a clean and user-friendly way to enable or disable options in a GUI app. Checkboxes are useful when building preferences or setting dialogs where the users can customize our applications. For example, say that you're making a text editor and you want to allow the user to customize a few features like:

  • Show word count
  • Auto pair brackets
  • Show minimap

Checkbox options shouldn't usually be mutually exclusive. If you have mutually exclusive options, consider using the QRadioButton class instead.

In this case, you can use a checkbox for each of these options like in the following code:

python
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QLabel,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.wordCountCkBox = QCheckBox(text="Show word count", parent=self)
        self.bracketsCkBox = QCheckBox(text="Auto pair brackets", parent=self)
        self.minimapCkBox = QCheckBox(text="Show minimap", parent=self)
        self.selectionLabel = QLabel(text="No selection yet", parent=self)
        layout = QVBoxLayout()
        layout.addWidget(self.wordCountCkBox)
        layout.addWidget(self.bracketsCkBox)
        layout.addWidget(self.minimapCkBox)
        layout.addWidget(self.selectionLabel)
        self.setLayout(layout)
        self.wordCountCkBox.stateChanged.connect(self.onStateChanged)
        self.bracketsCkBox.stateChanged.connect(self.onStateChanged)
        self.minimapCkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        selection = "You've selected:\n"
        if self.wordCountCkBox.isChecked():
            selection += "- Word count\n"
        if self.bracketsCkBox.isChecked():
            selection += "- Auto pair brackets\n"
        if self.minimapCkBox.isChecked():
            selection += "- Show minimap\n"
        self.selectionLabel.setText(selection)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QLabel,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.wordCountCkBox = QCheckBox(text="Show word count", parent=self)
        self.bracketsCkBox = QCheckBox(text="Auto pair brackets", parent=self)
        self.minimapCkBox = QCheckBox(text="Show minimap", parent=self)
        self.selectionLabel = QLabel(text="No selection yet", parent=self)
        layout = QVBoxLayout()
        layout.addWidget(self.wordCountCkBox)
        layout.addWidget(self.bracketsCkBox)
        layout.addWidget(self.minimapCkBox)
        layout.addWidget(self.selectionLabel)
        self.setLayout(layout)
        self.wordCountCkBox.stateChanged.connect(self.onStateChanged)
        self.bracketsCkBox.stateChanged.connect(self.onStateChanged)
        self.minimapCkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self):
        selection = "You've selected:\n"
        if self.wordCountCkBox.isChecked():
            selection += "- Word count\n"
        if self.bracketsCkBox.isChecked():
            selection += "- Auto pair brackets\n"
        if self.minimapCkBox.isChecked():
            selection += "- Show minimap\n"
        self.selectionLabel.setText(selection)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we create three checkboxes, one per option. We also create a label object to show the user's selections. If you run this application, then you'll get the following behavior:

Checkboxes for text editor settings

This dialog shows the three required options for your text editor. Of course, this dialog doesn't look like a fully functional setting or preferences dialog. It intends to show how you can use checkboxes to provide non-exclusive options to your users. Note that you can select any combination of options.

Setting Other Properties of QCheckBox

Up to this point, we've learned how to create two-state and tri-state checkboxes in your GUI applications. We've also learned how to make our checkboxes perform actions in response to state changes by connecting the stateChanged signals with concrete methods known as slots.

In this section, we'll learn about other useful features of QCheckBox, including the text and icon properties shown below:

Property Description Getter Method Setter Method
text Holds the text shown on the button text() setText()
icon Holds the icon shown on the button icon() setIcon()

As its name suggests, the text property controls the current text associated with a checkbox. You can use the setText() method to change this property and text() to retrieve its current value if needed. In previous sections, you used setText() to change the text of a checkbox. So, let's focus on how to work with icons.

Here's an example of a checkbox representing a traffic light in which each state will have its own associated icon:

python
import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QCheckBox, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.lightCkBox = QCheckBox(parent=self)
        self.lightCkBox.setTristate(True)
        self.lightCkBox.setIcon(QIcon("icons/red.png"))
        layout = QVBoxLayout()
        layout.addWidget(self.lightCkBox)
        self.setLayout(layout)
        self.lightCkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self, state):
        state = Qt.CheckState(state)
        if state == Qt.CheckState.Checked:
            self.lightCkBox.setIcon(QIcon("icons/green.png"))
        elif state == Qt.CheckState.PartiallyChecked:
            self.lightCkBox.setIcon(QIcon("icons/yellow.png"))
        else:
            self.lightCkBox.setIcon(QIcon("icons/red.png"))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QCheckBox, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.lightCkBox = QCheckBox(parent=self)
        self.lightCkBox.setTristate(True)
        self.lightCkBox.setIcon(QIcon("icons/red.png"))
        layout = QVBoxLayout()
        layout.addWidget(self.lightCkBox)
        self.setLayout(layout)
        self.lightCkBox.stateChanged.connect(self.onStateChanged)

    def onStateChanged(self, state):
        state = Qt.CheckState(state)
        if state == Qt.CheckState.Checked:
            self.lightCkBox.setIcon(QIcon("icons/green.png"))
        elif state == Qt.CheckState.PartiallyChecked:
            self.lightCkBox.setIcon(QIcon("icons/yellow.png"))
        else:
            self.lightCkBox.setIcon(QIcon("icons/red.png"))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())


In this example, we use the setIcon() method to change the icon associated with the checkbox according to its current state. Go ahead and run the app! You'll get a window like the following:

Checkbox with icons

In this example, the red light is associated with the unchecked state, the yellow light is linked to the partially checked state, and the green light is associated with the checked state.

Using icons like in this example is something rare with checkbox widgets. You'll typically use descriptive text. But it's good to know the capability is there for when you need it.

Conclusion

Checkboxes are useful widgets in any GUI application. They allow us to give our users a clean way to enable or disable options in our applications. They are commonly used in dialogs and preferences windows to allow enabling and disabling of application features or configuring behavior.

In this tutorial, you've learned how to create, use, and customize checkboxes while building a GUI application with PyQt.

For more, see the complete PySide6 tutorial.

Qt Quick additions to Qt5

Some time ago we released some QML components as part of the Qt6 which was well received. However, this led the Qt5 users not having them as Qt5 cannot have new APIs without extra magic. So, as some of you already saw, we removed the prices of the marketplace items that this is about.

Qt for MCUs 2.3.1 Released

Qt for MCUs 2.3.1 has been released and is available for download. As a patch release, Qt for MCUs 2.3.1 provides bug fixes and other improvements, and maintains source compatibility with Qt for MCUs 2.3.x. It does not add any new functionality.

Adding Linux BlueZ DBus peripheral role support

Broadly speaking the Qt’s Bluetooth Low Energy (“BT LE") support consists of two complementing use cases: the central/client and the peripheral/server roles. For more details about the two roles please consult the Qt Bluetooth documentation.

Qt 6.5 introduces a new Linux backend for the peripheral (server) role which is based on the BlueZ DBus API. This backend now complements the already existing Qt BlueZ DBus client backend. 

The existing BlueZ Bluetooth management socket peripheral backend support is kept available for reasons laid out later in this post.

C++ is the Programming Language of the Year 2022

C++ has been awarded the Programming Language of the Year 2022 title by Tiobe, a leading Quality Assurance service provider. C++ won the award because it has the fastest growth among the top 20 languages. We at Qt welcome this selection as it confirms our long-term commitment to C++ as the underlying programming language for the Qt framework and development platform. 

Happy 9th birthday, qutebrowser!

qutebrowser is turning 9 today! I’ll use the opportunity for a – perhaps slightly tl;dr – overview of how it all came to be. As you might notice by the length of this post, stopping to write once I started writing something like this… isn’t exactly my forte! Hopefully …

PyQt QPushButton — A universal Button Widget

The push button, or command button, is perhaps the most commonly used widget in any graphical user interface (GUI). A button is a rectangular widget that typically displays a text describing its aim.

When we click a button, we command the computer to perform actions or to answer a question. Typical buttons include Ok, Apply, Cancel, Close, Yes, No and Help. However, they're not restricted to this list.

In this tutorial, you'll learn how to create and customize button widgets in your GUI applications.

Creating Buttons Widgets With QPushButton

Buttons are probably the most common widgets in GUI applications. They come in handy when you need to create dialogs for communicating with your users. You'll probably be familiar with Accept, Ok, Canel, Yes, No, and Apply buttons, which are commonplace in modern graphical user interfaces.

In general, buttons allow you to trigger actions in response to the user's clicks on the button's area. Buttons often have a rectangular shape and include a text label that describes their intended action or command.

If you've used PyQt or PySide to create GUI applications in Python, then it'd be pretty likely that you already know about the QPushButton class. This class allows you to create buttons in your graphical interfaces quickly.

The QPushButton class provides three different constructors that you can use to create buttons according to your needs:

Constructor Description
QPushButton(parent: QWidget = None) Constructs a button with a parent widget but without text or icon
QPushButton(text: str, parent: QWidget = None) Constructs a button with a parent widget and the description text but without an icon
QPushButton(icon: QIcon, text: str, parent: QWidget = None) Constructs a button with an icon, text, and parent widget

To illustrate how to use the above constructors, you can code the following example:

python
import sys

from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Button with a parent widget
        topBtn = QPushButton(parent=self)
        topBtn.setFixedSize(100, 60)
        # Button with a text and parent widget
        centerBtn = QPushButton(text="Center", parent=self)
        centerBtn.setFixedSize(100, 60)
        # Button with an icon, a text, and a parent widget
        bottomBtn = QPushButton(
            icon=QIcon("./icons/logo.svg"),
            text="Bottom",
            parent=self
        )
        bottomBtn.setFixedSize(100, 60)
        bottomBtn.setIconSize(QSize(40, 40))
        layout = QVBoxLayout()
        layout.addWidget(topBtn)
        layout.addWidget(centerBtn)
        layout.addWidget(bottomBtn)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        # Button with a parent widget
        topBtn = QPushButton(parent=self)
        topBtn.setFixedSize(100, 60)
        # Button with a text and parent widget
        centerBtn = QPushButton(text="Center", parent=self)
        centerBtn.setFixedSize(100, 60)
        # Button with an icon, a text, and a parent widget
        bottomBtn = QPushButton(
            icon=QIcon("./icons/logo.svg"),
            text="Bottom",
            parent=self
        )
        bottomBtn.setFixedSize(100, 60)
        bottomBtn.setIconSize(QSize(40, 40))
        layout = QVBoxLayout()
        layout.addWidget(topBtn)
        layout.addWidget(centerBtn)
        layout.addWidget(bottomBtn)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

You can download the logo here or use your own image file. You can also use PNG format images if you prefer.

This code does a lot! First, we do the required imports. Inside our Window class, we create three QPushButton instances. To create the first button, we use the first constructor of QPushButton. That's why we only pass a parent widget.

For the second button, we use the second constructor of QPushButton. This time, we provide the button's text and parent. Note that the text is a regular Python string.

Our last button uses the third constructor. In this case, we need to provide the button's icon, text, and parent. We use the QIcon class with an SVG image as an argument to provide the icon. Note that we can also use a QPixmap object as the button's icon.

Save this code to a .py file and run it. You'll get a window that looks something like this:

QPushButton constructors example, showing three buttons with labels & icon QPushButton constructors example, showing three buttons with labels & icon

The first button has no text. It's just a rectangular shape on the app's windows. The second button in the center has text only, while the third button at the bottom of the window has an icon and text. That looks great!

These buttons don't do any action jet. If you click them, then you'll realize that nothing happens. To make your buttons perform concrete actions, you need to connect their signals to some useful slots. You'll learn how to do this in the next section.

Connecting Signals and Slots

Depending on specific user events, the QPushButton class can emit four different signals. Here's is a summary of these signals and their corresponding meaning:

Signal Emitted
clicked(checked=false) When the user clicks the button and activates it.
pressed() When the user presses the button down.
released() When the user releases the button.
toggled(checked=false) Whenever a checkable button changes its state from checked to unchecked and vice versa.

When you're creating a GUI application, and you need to use buttons, then you need to think of the appropriate signal to use. Most of the time, you'll use the button's clicked() signal since clicking a button is the most common user event you'll ever need to process.

The other part of the signal and slot equation is the slot itself. A slot is a method or function that performs a concrete action in your application. Now, how can you connect a signal to a slot so that the slot gets called when the signal gets emitted? Here's the required syntax to do this:

python
button.<signal>.connect(<method>)

In this construct, button is the QPushButton object we need to connect with a given slot. The <signal> placeholder can be any of the four abovementioned signals. Finally, <method> represents the target slot or method.

Let's write an example that puts this syntax into action. For this example, we'll connect the clicked signal with a method that counts the clicks on a button:

python
import sys

from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.count = 0
        self.button = QPushButton(f"Click Count: {self.count}", self)
        self.button.setFixedSize(120, 60)
        self.button.clicked.connect(self.count_clicks)
        layout = QVBoxLayout()
        layout.addWidget(self.button)
        self.setLayout(layout)

    def count_clicks(self):
        self.count += 1
        self.button.setText(f"Click Count: {self.count}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.count = 0
        self.button = QPushButton(f"Click Count: {self.count}", self)
        self.button.setFixedSize(120, 60)
        self.button.clicked.connect(self.count_clicks)
        layout = QVBoxLayout()
        layout.addWidget(self.button)
        self.setLayout(layout)

    def count_clicks(self):
        self.count += 1
        self.button.setText(f"Click Count: {self.count}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

The button variable holds an instance of QPushButton. To create this button, we use a text and parent widget. This parent widget works as our current window. Then we connect the button's clicked signal with the count_clicks() method.

The count_clicks() method counts the number of clicks on the button and updates the button's text accordingly. Go ahead and run the app!

Exploring the Public API of QPushButton

Up to this point, you've learned about the different ways to create buttons in your GUI applications. You've also learned how to make your buttons perform actions in response to the user's actions by connecting the buttons' signals with concrete methods known as slots.

In the following sections, you'll learn about the QPushButton class's public API and its most useful properties, including the following:

Property Description Getter Method Setter Method
text Holds the text shown on the button text() setText()
icon Holds the icon shown on the button icon() setIcon()
shortcut Holds the keyboard shortcut associated with the button shortcut() setShortcut()

Let's kick things off by learning how to set and get a button's text, icon, and keyboard shortcut. These actions can be an essential part of your GUI design process.

Setting a Button's Text, Icon, and Shortcut

In previous sections, you've learned how to create buttons using different class constructors. Some of these constructors allow you to set the button's text directly. However, sometimes you need to manipulate a button's text at runtime. To accomplish this, you can use setText() and text().

As its name suggests, the setText() method allows you to set the text of a given button. On the other hand, the text() lets you retrieve the current text of a button. Here's a toy example of how to use these methods:

python
import sys

from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.button = QPushButton("Hello!")
        self.button.setFixedSize(120, 60)
        self.button.clicked.connect(self.onClick)
        layout = QVBoxLayout()
        layout.addWidget(self.button)
        self.setLayout(layout)

    def onClick(self):
        text = self.button.text()
        if text == "Hello!":
            self.button.setText(text[:-1] + ", World!")
        else:
            self.button.setText("Hello!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.button = QPushButton("Hello!")
        self.button.setFixedSize(120, 60)
        self.button.clicked.connect(self.onClick)
        layout = QVBoxLayout()
        layout.addWidget(self.button)
        self.setLayout(layout)

    def onClick(self):
        text = self.button.text()
        if text == "Hello!":
            self.button.setText(text[:-1] + ", World!")
        else:
            self.button.setText("Hello!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we use text() and setText() inside the onClick() method to manipulate the text of our button object. These methods come in handy when we need to set and retrieve a button's text at run time, which would be useful in a few situations. For example, if we need a button to fold and unfold a tree of widgets or other objects.

Go ahead and run the app! You'll get a window like the following:

Window with a single button, with the text Hello! Click to change the text. Window with a single button, with the text Hello! Click to change the text.

In this example, when you click the button, its text alternate between Hello! and Hello, World!.

QPushButton also has methods to manipulate the icon of a given button. In this case, the methods are setIcon() and icon(). You can set the button's icon at run time with the first method. The second method allows you to retrieve the icon of a given button. There's also a third method related to icons. It's called .setIconSize() and allows you to manipulate the icon size.

Here's an example that illustrates how to use these methods:

python
import sys

from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(
            icon=QIcon("./icons/logo.svg"), text="Click me!", parent=self
        )
        self.btnOne.setFixedSize(100, 60)
        self.btnOne.clicked.connect(self.onClick)
        self.btnTwo = QPushButton(parent=self)
        self.btnTwo.setFixedSize(100, 60)
        self.btnTwo.setEnabled(False)
        self.btnTwo.clicked.connect(self.onClick)
        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        layout.addWidget(self.btnTwo)
        self.setLayout(layout)

    def onClick(self):
        sender = self.sender()
        icon = sender.icon()

        if sender is self.btnOne:
            sender.setText("")
            sender.setIcon(QIcon())
            sender.setEnabled(False)
            self.btnTwo.setEnabled(True)
            self.btnTwo.setText("Click me!")
            self.btnTwo.setIcon(icon)
            self.btnTwo.setIconSize(QSize(20, 20))
        elif sender is self.btnTwo:
            sender.setText("")
            sender.setIcon(QIcon())
            sender.setEnabled(False)
            self.btnOne.setEnabled(True)
            self.btnOne.setText("Click me!")
            self.btnOne.setIcon(icon)
            self.btnOne.setIconSize(QSize(30, 30))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtCore import QSize
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(
            icon=QIcon("./icons/logo.svg"), text="Click me!", parent=self
        )
        self.btnOne.setFixedSize(100, 60)
        self.btnOne.clicked.connect(self.onClick)
        self.btnTwo = QPushButton(parent=self)
        self.btnTwo.setFixedSize(100, 60)
        self.btnTwo.setEnabled(False)
        self.btnTwo.clicked.connect(self.onClick)
        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        layout.addWidget(self.btnTwo)
        self.setLayout(layout)

    def onClick(self):
        sender = self.sender()
        icon = sender.icon()

        if sender is self.btnOne:
            sender.setText("")
            sender.setIcon(QIcon())
            sender.setEnabled(False)
            self.btnTwo.setEnabled(True)
            self.btnTwo.setText("Click me!")
            self.btnTwo.setIcon(icon)
            self.btnTwo.setIconSize(QSize(20, 20))
        elif sender is self.btnTwo:
            sender.setText("")
            sender.setIcon(QIcon())
            sender.setEnabled(False)
            self.btnOne.setEnabled(True)
            self.btnOne.setText("Click me!")
            self.btnOne.setIcon(icon)
            self.btnOne.setIconSize(QSize(30, 30))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we create an app with two buttons. Both buttons are connected to the onClick() method. Inside the method, we first get the clicked button using the sender() method on our app's window, self.

Next, we get a reference to the sender button's icon using the icon() method. The if statement checks if the clicked object was btnOne. If that's the case, then we reset the icon with setIcon() and disable the button with setEnabled(). The following four lines enable the btnTwo button, set its text to "Click me!", change the button's icon, and resize the icon. The elif clause does something similar, but this time the target button is btnOne.

If you run this application, then you'll get a window like this:

Window with two buttons, the top with a icon & label, the bottom empty. Click to toggle which button has the label & icon._ Window with two buttons, the top with a icon & label, the bottom empty. Click to toggle which button has the label & icon.

When we click the top button, the bottom button's text and icon will be set to Click me! and to the PythonGUIs.com logo, respectively. At the same time, the top button's text and icon will disappear. Note that the logo's size will change as well.

Another useful feature of buttons is that you can assign them a keyboard shortcut for those users that prefer the keyboard over the mouse. These methods are .setShortcut() and .shortcut(). Again, you can use the first method to set a shortcut and the second method to get the shortcut assigned to the underlying button.

These methods are commonly helpful in situations where we have a button that doesn't have any text. Therefore we can't assign it an automatic shortcut using the ampersand character &.

Checking the Status of a Button

Sometimes you'd need to check the status of a given button and take action accordingly. The QPushButton class provides a few methods that can help you check different properties related to the current status of your buttons:

Property Description Access Method Setter Method
down Indicates whether the button is pressed down or not isDown() setDown()
checked Indicates whether the button is checked or not isChecked() setChecked()
enabled Indicates whether the button is enabled or not isEnabled() setEnabled()

The down status is typically transitory and naturally happens between the pressed and released statuses. However, we can use the setDown() method to manipulate the down status at runtime.

The checked status is only available when we use checkable buttons. Only checkable buttons can be at either the checked or unchecked status.

Finally, when we enable or disable a given button, we allow or disallow the user's click on the button. In other words, disabled buttons don't respond to the user's clicks or other events, while enabled buttons do respond.

Here's an example that shows how these three sets of statuses work:

python
import sys

from PyQt6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(text="I'm Down!", parent=self)
        self.btnOne.setFixedSize(150, 60)
        self.btnOne.setDown(True)
        self.btnOne.clicked.connect(self.onBtnOneClicked)

        self.btnTwo = QPushButton(text="I'm Disabled!", parent=self)
        self.btnTwo.setFixedSize(150, 60)
        self.btnTwo.setEnabled(False)

        self.btnThree = QPushButton(text="I'm Checked!", parent=self)
        self.btnThree.setFixedSize(150, 60)
        self.btnThree.setCheckable(True)
        self.btnThree.setChecked(True)
        self.btnThree.clicked.connect(self.onBtnThreeClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        layout.addWidget(self.btnTwo)
        layout.addWidget(self.btnThree)
        self.setLayout(layout)

    def onBtnOneClicked(self):
        if not self.btnOne.isDown():
            self.btnOne.setText("I'm Up!")
            self.btnOne.setDown(False)

    def onBtnThreeClicked(self):
        if self.btnThree.isChecked():
            self.btnThree.setText("I'm Checked!")
        else:
            self.btnThree.setText("I'm Unchecked!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(text="I'm Down!", parent=self)
        self.btnOne.setFixedSize(150, 60)
        self.btnOne.setDown(True)
        self.btnOne.clicked.connect(self.onBtnOneClicked)

        self.btnTwo = QPushButton(text="I'm Disabled!", parent=self)
        self.btnTwo.setFixedSize(150, 60)
        self.btnTwo.setEnabled(False)

        self.btnThree = QPushButton(text="I'm Checked!", parent=self)
        self.btnThree.setFixedSize(150, 60)
        self.btnThree.setCheckable(True)
        self.btnThree.setChecked(True)
        self.btnThree.clicked.connect(self.onBtnThreeClicked)

        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        layout.addWidget(self.btnTwo)
        layout.addWidget(self.btnThree)
        self.setLayout(layout)

    def onBtnOneClicked(self):
        if not self.btnOne.isDown():
            self.btnOne.setText("I'm Up!")
            self.btnOne.setDown(False)

    def onBtnThreeClicked(self):
        if self.btnThree.isChecked():
            self.btnThree.setText("I'm Checked!")
        else:
            self.btnThree.setText("I'm Unchecked!")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we first set btnOne down using the setDown() method. Then we disable btnTwo using the setEnabled() with False as an argument. This will make this button unresponsive to user events. Finally, we set btnThree as checkable with setCheckable(). Being checkable means that we can use the checked and unchecked statuses in our code.

The onBtnOneClicked() method is connected to btnOne. This method checks if the button is not down and changes the button text accordingly.

The onBtnThreeClicked() is connected to btnThree. This method alternatively changes the button's text depending on its checked status.

If you run this app, you'll get the following window:

Window with 3 buttons: one starting in the down state, one disabled and one checked & toggleable. Window with 3 buttons: one starting in the down state, one disabled and one checked & toggleable.

First, note that these three buttons have different tones of gray. These different tones of gray indicate three different states. The first button is down, the second button is disabled, and the third button is checked.

If you click the first button, then it'll be released, and its text will be set to I'm Up!. The second button won't respond to your clicks or actions. The third button will alternate its status between unchecked and checked.

Exploring Other Properties of Button Objects

QPushButton has several other useful properties we can use in our GUI applications. Some of these properties with their corresponding setter and getter method include:

Property Description Access Method Setter Method
default Indicates whether the button is the default button on the containing window or not isDefault() setDefault()
flat Indicates whether the button border is raised or not isFlat() setFlat()

The default property comes in handy when you have several buttons on a window, and you need to set one of these buttons as the default window's action. For example, say we have a dialog with the Ok and Cancel buttons. In this case, the Ok button can be your default action.

The flat property is closely related to the look and feel of your app's GUI. If we set flat to True, then the button's border won't be visible.

Associating a Popup Menu to a Button

QPushButton objects can also display a menu when you click them. To set up this menu, you must have a proper popup menu beforehand. Then you can use the setMenu() method to associate the menu with the button.

Here's an example that creates a button with an attached popup menu:

python
import sys

from PyQt6.QtWidgets import (
    QApplication,
    QMenu,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(text="Menu!", parent=self)

        self.menu = QMenu(self)
        self.menu.addAction("First Item")
        self.menu.addAction("Second Item")
        self.menu.addAction("Third Item")

        self.btnOne.setMenu(self.menu)

        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

python
import sys

from PySide6.QtWidgets import (
    QApplication,
    QMenu,
    QPushButton,
    QVBoxLayout,
    QWidget,
)

class Window(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.btnOne = QPushButton(text="Menu!", parent=self)

        self.menu = QMenu(self)
        self.menu.addAction("First Item")
        self.menu.addAction("Second Item")
        self.menu.addAction("Third Item")

        self.btnOne.setMenu(self.menu)

        layout = QVBoxLayout()
        layout.addWidget(self.btnOne)
        self.setLayout(layout)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())

In this example, we create a button with an associated popup menu. To attach the menu to the target button, we use setMenu(), which turns the button into a menu button.

If you run this application, then you'll get a window that will look something like this:

Window with a single button, with attached drop-down menu. Window with a single button, with attached drop-down menu.

In some window styles, the button will show a small triangle on the right end of the button. If we click the button, then the pop-up menu will appear, allowing us to select any available menu option.

Conclusion

Push buttons are pretty useful widgets in any GUI application. Buttons can respond to the user's events and perform actions in our applications.

In this tutorial, you've learned how to create, use, and customize your button while building a GUI application.

For an in-depth guide to building GUIs with Python see my PySide6 book.

Grantlee version 5.3.1 now available

I’ve just made a new 5.3.1 release of Grantlee. The 5.3.0 release had some build issues with Qt 6 which should now be resolved with version 5.3.1.

Unlike previous releases, this release will not appear on http://www.grantlee.org/downloads/. I’ll be turning off grantlee.org soon. All previous releases have already been uploaded to https://github.com/steveire/grantlee/releases.

The continuation of Grantlee for Qt 6 is happening as KTextTemplate so as not to be constrained by my lack of availability. I’ll only make new Grantlee patch releases as needed to fix any issues that come up in the meantime.

Many thanks to the KDE community for taking up stewardship and ownership of this library!

Understanding qAsConst and std::as_const

Every now and then, when I submit some code for a code review, people tell me that I forgot qAsConst.

Now I have one more enemy, namely: Clazy! It has also started saying this, and I guess it’s about time for me to figure out what is going on. When do I need qAsConst and why do I need to know these things?

The Dreaded Warning

The following code is an example of where Clazy and my coworkers tell me to add qAsConst:

class MyClass {
  QStringList m_list;
public:
  void print() {
     for (const QString &str : m_list)  // WARNING: might detach, add qAsConst
        print(str);
  }
};

Why do I need qAsConst there? Let me tell you a little bit about what’s happening behind the scenes, and that will allow us to understand why qAsConst is needed.

Clazy is complaining there regarding a very peculiar interaction that we have between the C++11 range-based for loop that we have there and a Qt container, in this case QStringList. But this can apply to any Qt container.

What is the (potential) problem with that code that Clazy is warning about? Clazy is warning that the code, written like that, could perform a hidden copy of your container. If you just take your container and run through the container, there’s no copy in here. It looks like there is no copy, but there could be a copy. Why would you need a copy here?


It all boils down to the fact that Qt containers are implicitly shared or, as we say, Copy On Write (or COW 🐮). In a nutshell, that means that inside each and every Qt container there is a little number — a reference counter. Every time you try to modify a container, the first thing that Qt does is checks that number.

If the number is bigger than 1, then Qt does a deep copy of that container. We refer to this operation as a detach of the container. That code, written like that, could detach. That’s the kind of warning that Clazy is giving you. Since it looks like that, you’re not modifying the container in any way. You’re just iterating over the StringList, just printing the strings.

Clazy is getting a bit suspicious, thinking, “Okay, so you’re taking a copy of the container but you don’t want to modify the container? Why are you taking the copy in the first place?”


Where on this particular line does it even have a chance to detach? It’s not visible, but it’s behind the scenes.

It has to do with the new for loop. How is it implemented? Or even better, what is it equivalent to? Do you know what the new for loop expands to when the compiler sees that and compiles it?

For loops on containers have always been about iterators. You get an iterator pointing to the first element, an iterator beyond the last element, and then you iterate through. You’d assume some iterators and then whatever you’re pointing to is copied into your reference, or you’d get a QString reference to that element that it’s pointed to.

How does the range-based for loop iterate on an arbitrary container? You’ve guessed it right, it still uses iterators, even if they’re not visible to us. Given a container, the new for loop calls begin() and end() on it, in order to get the iterators that are necessary to actually transverse the container.

Here’s the catch: if the container is a Qt container, and it is non-const, then those operations are operations that do the little game I was playing; that is, they go inside the Qt container, check if the reference counter is bigger than 1, and, if it is, detach.

So the very act of calling begin() and end() could detach. As I said, it’s not visible but it’s there, hidden inside the range-based for loop.

How to Solve the Problem

Make the Container const

When using a Qt container, a necessary condition for a detach to happen is that we are calling methods such as begin() and end() on a container which is not const. Therefore, the simplest approach to fix the whole problem is to make that container const somehow. We could, for instance, take a copy — a const copy, of course, or even better, a const reference to the container.

class MyClass {
  QString m_list;
public:
  void print() {
     const QStringList copy = m_list;
     for (const QString &str : copy) // OK
        print(str);
  }
};

Here we’re taking a const copy of the container. Now, taking a copy is cheap, given this is a Qt container. The copy on write mechanism implies that the act of taking a copy simply increments the reference counter. (If you take a const reference, you’re not even paying for that.)

But now the key aspect is that the for loop is acting on a const container: the begin() that it’s going to call is going to be the begin() overload for const containers, which does not detach. Since you have a const container, there’s no chance for you to modify the container because it’s const. So there is no need to detach at all. And, indeed, this will suppress the warning from Clazy because you cannot detach anymore.

There are better ways, of course. There are a couple of other things that you can do.

Mark the Entire Method As const

The first thing you can do better is realize that the entire print function actually doesn’t need to modify this object. So you could mark the entire method as const and that would, as a by-product, make the member m_list const. So, you’re good to go.

class MyClass {
  QStringList m_list;
public:
  void print() const {
     for (const QString &str : m_list) 
        print(str);
  }
};

That would be the best solution. But, of course, it’s not general — maybe you also need to do something else and modify this, so you cannot mark a given method as const.

qAsConst or std::as_const

The second best solution is using something like qAsConst or, equivalently, std::as_const on top of your m_list object.

class MyClass {
  QStringList m_list;
public:
  void print() {
     for (const QString &str : std::as_const(m_list))
        print(str);
  }
};

qAsConst and std::as_const do exactly the same thing. std::as_const is C++17, so if you’re not on 17 just yet, you can use the Qt version. What they do is simply give you back a const reference to that container.

What do these functions do? They’re just like a const_cast — they’re taking a reference to m_list and adding a const on top of it. So it’s just a manipulation of the type, something that’s done just for the purpose of the compiler. Now the compiler knows that the thing it gets out of qAsConst is, of course, a reference to a const QStringList; so it can call only the const begin() and the const end(). As we have discussed, those calls will not detach your container.

This solution doesn’t cost you anything at runtime. It does not generate any additional code or anything of that sort. It is purely a type manipulation done for the compiler.

Corner Cases

There are some corner cases worth discussing.

Suppose that you have a method that generates a container and you call that method from a range-based for loop because you want to iterate over its results:

QStringList generateStrings();

void print() {
   for (const QString &str : generateStrings()) // Clazy warning here
      print(str);
}

Of course Clazy is going to give you a warning there, because you could be detaching the container returned by the function call. So you try to wrap the call in qAsConst:

QStringList generateStrings();

void print() {
  for (const QString &str : qAsConst(generateStrings())) // ERROR
    print(str);
}

This code doesn’t compile. The reason is a bit complicated, because it has to do with the C++ rules around what exactly you can feed into qAsConst. To keep it simple, you’re not allowed to place things like function calls inside qAsConst or, equivalently, inside std::as_const. It’s actually a good thing that you can’t do that because, otherwise, your program would crash at runtime.

Declare a Local QStringList

Fortunately, for this specific use case, there is a better solution. You can simply declare a local QStringList. Make it const, of course, because that’s the purpose of the whole exercise. Then, pass the local QStringList into your for loop. For instance:

QStringList generateStrings();

void print() {
  const QStringList list = generateStrings();
  for (const QString &str : list) // OK
    print(str);
}

In C++20, I would actually not even need an extra line to declare list. A nice thing about C++20, as a kind of smaller convenience, it added the possibility of adding an initialization inside the range-based for loop. This brings it slightly closer to the old for loop, when you always had the first statement, then the guard, and then the increment.

So you could write something like this, to spare you having this variable around for longer than the loop itself:

QStringList generateStrings();

void print() {
  for (const QStringList list = generateStrings(); const QString &str : list) // OK
    print(str);
}

Why does this problem exist to begin with? What if generateStrings() generates and returns a list and there can’t be anyone else who has that string list?

QStringList generateStrings() {
  QStringList result;
  result << "hello" << "world";
  return result;
}

void print() {
   for (const QString &str : generateStrings()) // Why a warning? The returned list isn't shared with anyone!
      print(str);
}

You, as a human being, can reason on the code flow and see that the QStringList returned by generateStrings() is not shared. Its reference counter is going to be 1, and that cannot detach. But that’s why this is a warning: Clazy is just telling you that this could detach. You have to remember that, in the general case, your generateStrings() function could not be returning a brand new QStringList.

Maybe it’s returning a QStringList that you have elsewhere. If that’s the case, that return from generateStrings() would implicitly increase the reference counter of your QStringList, because you’re creating a new copy. You would actually detach.

Inside your for loop, you would have a QStringList with a reference counter bigger than 1. Clazy, of course, does not have a crystal ball; it cannot predict what your code is going to do. So it’s just warning you by telling you this code might detach — that there is the possibility. It is actually a fairly concrete possibility, given the fact that, typically, when dealing with Qt containers, we always return them by value.

Even if doesn’t actually detach right now, one day we might refactor the code. We may say something like, “my string list has to survive the generateStrings() method. Maybe I want to cache it. Maybe I want to save that work”. That day, the reference counter is going to increase and the warning would actually be a good warning.

Return a const QStringList?

Instead of just returning a QStringList, generateStrings() could return a const QStringList:

const QStringList generateStrings();

void print() {
   for (const QString &str : generateStrings()) // OK
      print(str);
}

Clazy will not complain here, again because we cannot detach a const container.

However: this is a bad idea. You shouldn’t be returning const things from functions, and especially const containers. Why is that? Because you’re going to break move semantics.

C++11 introduced move semantics, which allow you to optimize, in general, the return from functions. When you call a function and get a value back, you’re going to get a temporary built as result of the function call. You can then move from that temporary into, let’s say, a local object. Moving containers is usually very cheap! That’s definitely something we want to keep.

If you return const objects however that move gets broken; you can’t move from a const object. What happens then? You’re going to perform a copy instead. Now, in the case of Qt containers, you can get away with that because copying a Qt container is not particularly expensive. All that you really do is increase the reference counter.

So, as a practical workaround, that could work. But consider this: the moment you decide you don’t like QStringList any more and decide to make it a std::vector or something more complicated (some non-Qt container of any sort), you’re going to pay for that const because copying a standard container and not moving it is going to cost you a lot. So it’s very important to be careful about what you do.

Return a std::vector

You wouldn’t have this problem at all with std::vector. The reason for that is that std::vector, and actually all Standard Library containers, are not reference counted; they do not implement this mechanism. Among these other things, this means that a call to begin() into a std::vector object will never copy it behind the scenes. You’ll be absolutely fine, if your generateStrings() function returns a std::vector.

std::vector<QString> generateStrings();

void print() {
   for (const QString &str : generateStrings()) // OK
      print(str);
}

Summary

To summarize, you need qAsConst() when you iterate in a range-based for loop. That’s the first thing. And you need qAsConst when what you’re iterating over is non-const.

It could be const if you’re in a const method (and the variable is a data member of this) or if it’s a variable that you declared as const elsewhere. Otherwise, you need qAsConst but you can only use qAsConst if it’s an l-value, something that has a local name, and not the return of a function.

If you are iterating over an object that you get from a function call, save it in a local variable instead (and make it const).

Mutating Iteration

The Clazy warning stems from the fact that your for loop doesn’t want to modify your container. In all of our examples, we were using just a debug, or it seemed like we were just reading from the container. Clearly, if you do want to modify the container, then you don’t want to apply const at any level! You would need to be working on a non-const container.

But the key aspect of mutating iteration is that, in this case, your iteration variable would have a different type. As you can see, right now we’re taking a const QString reference. That means that we cannot mutate the container contents through that variable. If you need to modify the container itself, you’d be taking a non-const QString reference! In that case, you would not get the warning from Clazy, because it would detect that you want to modify the container. If that means a detach, that’s the price to pay — you have a container that’s just a shallow copy to some shared data and, if you want to modify it, you need to create your own copy of that data.

In C++23 there is yet another solution that will work in even more cases. We’ll cover that at a later time, either on our YouTube channel or as a blog, or both. We’ll keep you posted, so please stay tuned.

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 Understanding qAsConst and std::as_const appeared first on KDAB.

KDAB and Qt World Summit 2022

KDAB will be Gold sponsors at this year’s free online edition of Qt World Summit on November 9th, 2022. Our very own Jesper Pedersen will present his talk “Highlights from Qt Widgets and More”. Don’t miss out! Join developers, designers, managers, and executives to get inspired by the latest developments with Qt.

Register now and make sure to not miss Jesper’s talk here! It will be online on November 9th, 10:45 am – 11.30 am CET

‘Highlights from Qt Widgets and More’

For more than a year Jesper Pedersen has hosted a Youtube series focusing on Qt Widgets and everything around developing with it. In this presentation, Jesper will highlight some of the most important take away’s, including coding tips, and especially those relating to the model/view framework, Qt Creator power tips, and general tips relating to software development tools.

The beauty of this presentation is that there will be material on our Youtube channel for each point where you can dive into more details if needed!

Watch all episodes on Qt Widgets and More

qt widgets and more KDAB

Click the image or visit this link

The post KDAB and Qt World Summit 2022 appeared first on KDAB.

Getting started with VS Code for Python: Setting up a development environment

Setting up a working development environment is the first step for any project. Your development environment setup will determine how easy it is to develop and maintain your projects over time. That makes it important to choose the right tools for your project. This article will guide you through how to set up Visual Studio Code, which is a popular free-to-use, cross-platform code editor developed by Microsoft, in order to develop Python applications.

Visual Studio Code is not to be confused with Visual Studio, which is a separate product also offered by Microsoft. Visual Studio is a fully-fledged IDE that is mainly geared towards Windows application development using C# and the .NET Framework.

Setup a Python environment

In case you haven't already done this, Python needs to be installed on the development machine. You can do this by going to python.org and grabbing the specific installer for either Windows or macOS. Python is also available for installation via Microsoft Store on Windows devices.

Make sure that you select the option to Add Python to PATH during installation (via the installer).

If you are on Linux, you can check if Python is already installed on your machine by typing python3 --version in a terminal. If it returns an error, you need to install it from your distribution's repository. On Ubuntu/Debian, this can be done by typing sudo apt install python3. Both pip (or pip3) and venv are distributed as separate packages on Ubuntu/Debian and can also be installed by typing sudo apt install python3-pip python3-venv.

Setup Visual Studio Code

First, head over to to code.visualstudio.com and grab the installer for your specific platform.

If you are on a Raspberry Pi (with Raspberry Pi OS), you can also install VS Code by simply typing sudo apt install code. On Linux distributions that support Snaps, you can do it by typing sudo snap install code --classic.

Once VS Code is installed, head over to the Extensions tab in the sidebar on the left by clicking on it or by pressing CTRL+SHIFT+X. Search for the 'Python' extension published by Microsoft and click on Install.

The Extensions tab in the left-hand sidebar The Extensions tab in the left-hand sidebar.

Usage and Configuration

Now that you have finished setting up VS Code, you can go ahead and create a new Python file. Remember that the Python extension only works if you open a .py file or have selected the language mode for the active file as Python.

To change the language mode for the active file, simply press CTRL+K once and then press M after releasing the previous keys. This kind of keyboard shortcut is called a chord in VS Code. You can see more of them by pressing CTRL+K CTRL+S (another chord).

The Python extension in VS Code allows you to directly run a Python file by clicking on the 'Play' button on the top-right corner of the editor (without having to type python file.py in the terminal).

You can also do it by pressing CTRL+SHIFT+P to open the Command Palette and running the > Python: Run File in Terminal command.

Finally, you can configure VS Code's settings by going to File > Preferences > Settings or by pressing CTRL+COMMA. In VS Code, each individual setting has an unique identifier which you can see by clicking on the cog wheel that appears to the left of each setting and clicking on 'Copy Setting ID'. This ID is what will be referred to while talking about a specific setting. You can also search for this ID in the search bar under Settings.

Linting and Formatting Support (Optional)

Linters make it easier to find errors and check the quality of your code. On the other hand, code formatters help keep the source code of your application compliant with PEP (Python Enhancement Proposal) standards, which make it easier for other developers to read your code and collaborate with you.

For VS Code to provide linting support for your projects, you must first install a preferred linter like flake8 or pylint.

bash
pip install flake8

Then, go to Settings in VS Code and toggle the relevant setting (e.g. python.linting.flake8Enabled) for the Python extension depending on what you installed. You also need to make sure that python.linting.enabled is toggled on.

A similar process must be followed for code formatting. First, install something like autopep8 or black.

bash
pip install autopep8

You then need to tell VS Code which formatter to use by modifying python.formatting.provider and toggle on editor.formatOnSave so that it works without manual intervention.

If pip warns that the installed modules aren't in your PATH, you may have to specify the path to their location in VS Code (under Settings). Follow the method described under Working With Virtual Environments to do that.

Now, when you create a new Python file, VS Code automatically gives you a list of Problems (CTRL+SHIFT+M) in your program and formats the code on saving the file.

Identified problems in the source code. Identified problems in the source code, along with a description and line/column numbers.

You can also find the location of identified problems from the source overview on the right hand, inside the scrollbar.

Working With Virtual Environments

Virtual environments are a way of life for Python developers. Most Python projects require the installation of external packages and modules (via pip). Virtual environments allow you to separate one project's packages from your other projects, which may require a different version of those same packages. Hence, it allows all those projects to have the specific dependencies they require to work.

The Python extension makes it easier for you by automatically activating the desired virtual environment for the in-built terminal and Run Python File command after you set the path to the Python interpreter. By default, the path is set to use the system's Python installation (without a virtual environment).

To use a virtual environment for your project/workspace, you need to first make a new one by opening a terminal (View > Terminal) and typing python -m venv .venv. Then, you can set the default interpreter for that project by opening the Command Palette (CTRL+SHIFT+P) and selecting > Python: Select Interpreter.

You should now either close the terminal pane in VS Code and open a new one or type source .venv/bin/activate into the existing one to start using the virtual environment. Then, install the required packages for your project by typing pip install <package_name>.

VS Code, by default, looks for tools like linters and code formatters in the current Python environment. If you don't want to keep installing them over and over again for each new virtual environment you make (unless your project requires a specific version of that tool), you can specify the path to their location under Settings in VS Code. - flake8 - python.linting.flake8Path - autopep8 - python.formatting.autopep8Path

To find the global location of these packages on macOS and Linux, type which flake8 and which autopep8 in a terminal. If you are on Windows, you can use where <command_name>. Both these commands assume that flake8 and autopep8 are in your PATH.

Understanding Workspaces in VS Code

VS Code has a concept of Workspaces. Each 'project folder' (or the root/top folder) is treated as a separate workspace. This allows you to have project-specific settings and enable/disable certain extensions for that workspace. It is also what allows VS Code to quickly recover the UI state (e.g. files that were previously kept open) when you open that workspace again.

In VS Code, each workspace (or folder) has to be 'trusted' before certain features like linters, autocomplete suggestions and the in-built terminal are allowed to work.

In the context of Python projects, if you tend to keep your virtual environments outside the workspace (where VS Code is unable to detect it), you can use this feature to set the default path to the Python interpreter for that workspace. To do that, first Open a Folder (CTRL+K CTRL+O) and then go to File > Preferences > Settings > Workspace to modify python.defaultInterpreterPath.

Setting the default interpreter path for the workspace. Setting the default interpreter path for the workspace.

In VS Code settings you can search for settings by name using the bar at the top.

You can also use this approach to do things like use a different linter for that workspace or disable the code formatter for it. The workspace-specific settings you change are saved in a .vscode folder inside that workspace, which you can share with others.

If your VS Code is not recognizing libraries you are using in your code, double check the correct interpreter is being used. You can find which Python version you're using on the command line by running which python or which python3 on macOS/Linux, or where python or where python3 on Windows.

Working With Git in VS Code (Optional)

Using Version Control is required for developing applications. VS Code does have in-built support for Git but it is pretty barebones, not allowing much more than tracking changes that you have currently made and committing/pushing those changes once you are done.

For the best experience, it is recommended to use the GitLens extension. It lets you view your commit history, check who made the changes and much more. To set it up, you first need to have Git set up on your machine (go here) and then install GitLens from the Extensions tab in the sidebar on the left. You can now use those Git-related features by going to the Git tab in the sidebar (CTRL+SHIFT+G).

There are more Git-related extensions you could try as well, like Git History and GitLab Workflow. Give them a whirl too!

Community-driven & open source alternatives

While VS Code is open source (MIT-licensed), the distributed versions include some Microsoft-specific proprietary modifications, such as telemetry (app tracking). If you would like to avoid this, there is also a community-driven distribution of Visual Studio Code called VSCodium that provides freely-licensed binaries without telemetry.

Due to legal restrictions, VSCodium is unable to use the official Visual Studio Marketplace for extensions. Instead, it uses a separate vendor neutral, open source marketplace called Open VSX Registry. It doesn't have every extension, especially proprietary ones, and some are not kept up-to-date but both the Python and GitLens extensions are available on it.

You can also use the open source Jedi language server for the Python extension, rather than the bundled Pylance language server/extension, by configuring the python.languageServer setting. You can then completely disable Pylance by going to the Extensions tab. Note that, if you are on VSCodium, Jedi is used by default (as Pylance is not available on Open VSX Registry) when you install the Python extension.

Conclusion

Having the right tools and making sure they're set up correctly will greatly simplify your development process. While Visual Studio starts as a simple tool, it is flexible and extendable with plugins to suit your own preferred workflow. In this tutorial we've covered the basics of setting up your environment, and you should now be ready to start developing your own applications with Python!

For an in-depth guide to building GUIs with Python see my PyQt6 book.

Flutter vs React Native vs Qt in 2022

Choosing the best framework to develop an application is a tough task. Nonetheless, that is a critical decision to make before starting the development of your next project. Currently, quite a popular method of software development is cross platform software development. What is cross platform development? This is an approach that allows you to reuse code between different operating systems like iOS and Android or macOS and Windows. Following this way of apps development is first of all cost and time effective. In multiple cases, it is a better approach than native app development. Therefore, if you want to follow this approach, reduce costs and develop apps more quickly, you need to use a cross platform framework such as Flutter, React Native or Qt. But which one to choose? In this article, we compare Flutter vs React Native vs Qt to help you answer that question.

Which Python GUI library should you use in 2022?

Python is a popular programming used for everything from scripting routine tasks to building websites and performing complex data analysis. While you can accomplish a lot with command line tools, some tasks are better suited to graphical interfaces. You may also find yourself wanting to build a desktop front-end for an existing tool to improve usability for non-technical users. Or maybe you're building some hardware or a mobile app and want an intuitive touchscreen interface.

To create graphical user interfaces with Python, you need a GUI library. Unfortunately, at this point things get pretty confusing -- there are many different GUI libraries available for Python, all with different capabilities and licensing. Which Python GUI library should you use for your project?

In this article, we will look at a selection of the most popular Python GUI frameworks currently available and why you should consider using them for your own projects. You'll learn about the relative strengths of each library, understand the licensing limitations and see a simple Hello, World! application written in each. By the end of the article you should feel confident choosing the right library for your project.

Tkinter

Best for simple tool GUIs, small portable applications

Tkinter is the defacto GUI framework for Python. It comes bundled with Python on both Windows and macOS. (On Linux, it may require downloading an additional package from your distribution's repo.) Tkinter is a wrapper written around the Tk GUI toolkit. Its name is an amalgamation of the words Tk and Interface.

Tkinter is a simple library with support for standard layouts and widgets, as well as more complex widgets such as tabbed views & progressbars. Tkinter is a pure GUI library, not a framework. There is no built-in support for GUIs driven from data sources, databases, or for displaying or manipulating multimedia or hardware. However, if you need to make something simple that doesn't require any additional dependencies, Tkinter may be what you are looking for. Tkinter is cross-platform however the widgets can look outdated, particularly on Windows.

Installation Already installed with Python on Windows and macOS. Ubuntu/Debian Linux sudo apt install python3-tk

A simple hello world application in Tkinter is shown below.

python
import tkinter as tk

window = tk.Tk()
window.title("Hello World")


def handle_button_press(event):
    window.destroy()


button = tk.Button(text="My simple app.")
button.bind("", handle_button_press)
button.pack()

# Start the event loop.
window.mainloop()

python
from tkinter import Tk, Button


class Window(Tk):
    def __init__(self):
        super().__init__()

        self.title("Hello World")

        self.button = Button(text="My simple app.")
        self.button.bind("", self.handle_button_press)
        self.button.pack()

    def handle_button_press(self, event):
        self.destroy()


# Start the event loop.
window = Window()
window.mainloop()

Tkinter Application Screenshot Hello world application built using Tkinter, running on Windows 11

Tkinter was originally developed by Steen Lumholt and Guido Van Rossum, who designed Python itself. Both the GUI framework and the language are licensed under the same Python Software Foundation (PSF) License. While the license is compatible with the GPL, it is a 'permissive' license (similar to the MIT License) that allows it to be used for proprietary applications and modifications.

PyQt or PySide

Best for desktop applications, multimedia, scientific and engineering software

PyQt and PySide are wrappers around the Qt framework. They allow you to easily create modern interfaces that look right at home on any platform, including Windows, macOS, Linux and even Android. They also have solid tooling with the most notable being Qt Creator, which includes a WYSIWYG editor for designing GUI interfaces quickly and easily. Being backed by a commercial project means that you will find plenty of support and online learning resources to help you develop your application.

Qt (and by extension PyQt & PySide) is not just a GUI library, but a complete application development framework. In addition to standard UI elements, such as widgets and layouts, Qt provides MVC-like data-driven views (spreadsheets, tables), database interfaces & models, graph plotting, vector graphics visualization, multimedia playback, sound effects & playlists and built-in interfaces for hardware such as printing. The Qt signals and slots models allows large applications to be built from re-usable and isolated components.

While other toolkits can work great when building small & simple tools, Qt really comes into its own for building real commercial-quality applications where you will benefit from the pre-built components. This comes at the expense of a slight learning curve. However, for smaller projects Qt is not really any more complex than other libraries. Qt Widgets-based applications use platform native widgets to ensure they look and feel at home on Windows, macOS and Qt-based Linux desktops.

Installation pip install pyqt6 or pip install pyside6

A simple hello world application in PyQt6, using the Qt Widgets API is shown below.

python
from PyQt6.QtWidgets import QMainWindow, QApplication, QPushButton

import sys


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

        self.setWindowTitle("Hello World")

        button = QPushButton("My simple app.")
        button.pressed.connect(self.close)

        self.setCentralWidget(button)
        self.show()


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

python
from PySide6.QtWidgets import QMainWindow, QApplication, QPushButton

import sys


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

        self.setWindowTitle("Hello World")

        button = QPushButton("My simple app.")
        button.pressed.connect(self.close)

        self.setCentralWidget(button)
        self.show()


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

As you can see, the code is almost identical between PyQt & PySide, so it's not something to be concerned about when you start developing with either: you can always migrate easily if you need to.

PyQt6 Application Screenshot Hello world application built using PyQt6, running on Windows 11

Before the Qt Company (under Nokia) released the officially supported PySide library in 2009, Riverbank Computing had released PyQt in 1998. The main difference between these two libraries is in licensing. The free-to-use version of PyQt is licensed under GNU General Public License (GPL) v3 but PySide is licensed under GNU Lesser General Public License (LGPL). This means that PyQt is limited GPL-licensed applications unless you purchase its commercial version, while PySide may be used in non-GPL applications without any additional fee. However, note that both these libraries are separate from Qt itself which also has a free-to-use, open source version and a paid, commercial version.

For a more information see our article on PyQt vs PySide licensing.

PyQt/PySide with QML

Best for Raspberry Pi, microcontrollers, industrial and consumer electronics

When using PyQt and PySide you actually have two options for building your GUIs. We've already introduced the Qt Widgets API which is well-suited for building desktop applications. But Qt also provides a declarative API in the form of Qt Quick/QML.

Using Qt Quick/QML you have access to the entire Qt framework for building your applications. Your UI consists of two parts: the Python code which handles the business logic and the QML which defines the structure and behavior of the UI itself. You can control the UI from Python, or use embedded Javascript code to handle events and animations.

Qt Quick/QML is ideally suited for building modern touchscreen interfaces for microcontrollers or device interfaces -- for example, building interfaces for microcontrollers like the Raspberry Pi. However you can also use it on desktop to build completely customized application experiences, like those you find in media player applications like Spotify, or to desktop games.

Installation pip install pyqt6 or pip install pyside6

A simple Hello World app in PyQt6 with QML. Save the QML file in the same folder as the Python file, and run as normally.

python
import sys

from PyQt6.QtGui import QGuiApplication
from PyQt6.QtQml import QQmlApplicationEngine


app = QGuiApplication(sys.argv)

engine = QQmlApplicationEngine()
engine.quit.connect(app.quit)
engine.load('main.qml')

sys.exit(app.exec())

qml
import QtQuick 2.15
import QtQuick.Controls 2.15

ApplicationWindow {
    visible: true
    width: 600
    height: 500
    title: "HelloApp"

    Text {
        anchors.centerIn: parent
        text: "Hello World"
        font.pixelSize: 24
    }

}

Licensing for Qt Quick/QML applications is the same as for other PyQt/PySide apps.

PyQt6 QML Application Screenshot Hello world application built using PyQt6 & QML, running on Windows 11

Kivy

Best for Python mobile app development

While most other GUI frameworks are bindings to toolkits written in other programming languages, Kivy is perhaps the only framework which is primarily written in pure Python. If you want to create touchscreen-oriented interfaces with a focus on mobile platforms such as Android and iOS, this is the way to go. This does run on desktop platforms (Windows, macOS, Linux) as well but note that your application may not look and behave like a native application. However, there is a pretty large community around this framework and you can easily find resources to help you learn it online.

The look and feel of Kivy is extremely customizable, allowing it to be used as an alternative to libraries like Pygame (for making games with Python). The developers have also released a number of separate libraries for Kivy. Some provide Kivy with better integration and access to certain platform-specific features, or help package your application for distribution on platforms like Android and iOS. Kivy has it's own design language called Kv, which is similar to QML for Qt. It allows you to easily separate the interface design from your application's logic.

There is a 3rd party add-on for Kivy named KivyMD that replaces Kivy's widgets with ones that are compliant with Google's Material Design.

A simple hello world application in Kivy is shown below.

Installation pip install kivy

A simple hello world application in Kivy is shown below.

python
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window

Window.size = (300, 200)


class MainWindow(BoxLayout):
    def __init__(self):
        super().__init__()
        self.button = Button(text="Hello, World?")
        self.button.bind(on_press=self.handle_button_clicked)

        self.add_widget(button)

    def handle_button_clicked(self, event):
        self.button.text = "Hello, World!"


class MyApp(App):
    def build(self):
        self.title = "Hello, World!"
        return MainWindow()


app = MyApp()
app.run()

Kivy Application Screenshot Hello world application built using Kivy, running on Windows 11

An equivalent application built using the Kv declarative language is shown below.

python
import kivy
kivy.require('1.0.5')

from kivy.uix.floatlayout import FloatLayout
from kivy.app import App
from kivy.properties import ObjectProperty, StringProperty


class Controller(FloatLayout):
    '''Create a controller that receives a custom widget from the kv lang file.

    Add an action to be called from the kv lang file.
    '''
    def button_pressed(self):
        self.button_wid.text = 'Hello, World!'


class ControllerApp(App):

    def build(self):
        return Controller()


if __name__ == '__main__':
    ControllerApp().run()

python
#:kivy 1.0

:
    button_wid: custom_button

    BoxLayout:
        orientation: 'vertical'
        padding: 20

        Button:
            id: custom_button
            text: 'Hello, World?'
            on_press: root.button_pressed()

The name of the Kv file must match the name of the class from the main application -- here Controller and controller.kv.

Kivy Kv Application Screenshot Hello world application built using Kivy + Kv, running on Windows 11

In February 2011, Kivy was released as the spiritual successor to a similar framework called PyMT. While they shared similar goals and was also led by the current core developers of Kivy, where Kivy differs is in its underlying design and a professional organization which actively develops and maintains it. Kivy is licensed under the MIT license, which is a 'permissive' license that allows you to use it freely in both open source and proprietary applications. As such, you are even allowed to make proprietary modifications to the framework itself.

PySimpleGUI

Best for quickly building UIs for simple tools, very portable

PySimpleGUI aims to simplify GUI application development for Python. It doesn't reinvent the wheel but provides a wrapper around other existing frameworks such as Tkinter, Qt (PySide 2), WxPython and Remi. By doing so, it not only lowers the barrier to creating a GUI but also allows you to easily migrate from one GUI framework to another by simply changing the import statement. While there is a separate port of PySimpleGUI for each of these frameworks, the Tkinter version is considered the most feature complete with the Qt version coming in at second. At the time of writing, the other ports are still more or less a work-in-progress.

There is a fair amount of good resources to help you learn to use PySimpleGUI, including an official Cookbook and a Udemy course offered by the developers themselves. According to their project website, PySimpleGUI was initially made (and later released in 2018) because the lead developer wanted a 'simplified' GUI framework to use in his upcoming project and wasn't able to find any that met his needs.

Installation pip install pysimplegui

python
import PySimpleGUI as sg


layout = [
    [sg.Button("My simple app.")]
]

window = sg.Window("Hello World", layout)

while True:
    event, values = window.read()
    print(event, values)
    if event == sg.WIN_CLOSED or event == "My simple app.":
        break

window.close()

PySimpleGUI Application Screenshot Hello world application built using PySimpleGUI, running on Windows 11

PySimpleGUI is licensed under the same LGPL v3 license as PySide, which allows its use in proprietary applications but modifications to the framework itself must be released as open source.

WxPython

Best for simple portable desktop applications

WxPython is a wrapper for the popular, cross-platform GUI toolkit called WxWidgets. It is implemented as a set of Python extension modules that wrap the GUI components of the popular wxWidgets cross platform library, which is written in C++.

WxPython uses native widgets on most platforms, ensure that your application looks and feels at home. However, WxPython is known to have certain platform-specific quirks and it also doesn't provide the same level of abstraction between platforms as Qt for example. This may affect how easy it is to maintain cross-platform compatibility for your application.

WxPython is under active development and is also currently being reimplemented from scratch under the name 'WxPython Phoenix'. The team behind WxWidgets is also responsible for WxPython, which was initially released in 1998.

Installation pip install wxpython

python
import wx


class MainWindow(wx.Frame):
    def __init__(self, parent, title):
        wx.Frame.__init__(self, parent, title=title, size=(200, -1))

        self.button = wx.Button(self, label="My simple app.")
        self.Bind(
            wx.EVT_BUTTON, self.handle_button_click, self.button
        )

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.button)

        self.SetSizer(self.sizer)
        self.SetAutoLayout(True)
        self.Show()

    def handle_button_click(self, event):
        self.Close()


app = wx.App(False)
w = MainWindow(None, "Hello World")
app.MainLoop()

WxPython Application Screenshot Hello world application built using WxPython, running on Windows 11

Both WxWidgets and WxPython are licensed under a WxWindows Library License, which is a 'free software' license similar to LGPL (with a special exception). It allows both proprietary and open source applications to use and modify WxPython.

PyGObject (GTK+)

Best for developing applications for GNOME desktop

If you intend to create an application that integrates well with GNOME and other GTK-based desktop environments for Linux, PyGObject is the right choice. PyGObject itself is a language-binding to the GTK+ widget toolkit. It allows you to create modern, adaptive user interfaces that conform to GNOME's Human Interface Guidelines (HIG).

It also enables the development of 'convergent' applications that can run on both Linux desktop and mobile platforms. There are a few first-party and community-made, third-party tools available for it as well. This includes the likes of GNOME Builder and Glade, which is yet another WYSIWYG editor for building graphical interfaces quickly and easily.

Unfortunately, there aren't a whole lot of online resources to help you learn PyGObject application development, apart from this one rather well-documented tutorial. While cross-platform support does exist (e.g. Inkscape, GIMP), the resulting applications won't feel completely native on other desktops. Setting up a development environment for this, especially on Windows and macOS, also requires more steps than for most other frameworks in this article, which just need a working Python installation.

Installation Ubuntu/Debian sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-4.0, macOS Homebrew brew install pygobject4 gtk+4

python
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import Gtk

def on_activate(app):
    win = Gtk.ApplicationWindow(application=app)
    btn = Gtk.Button(label="Hello, World!")
    btn.connect('clicked', lambda x: win.close())
    win.set_child(btn)
    win.present()

app = Gtk.Application(application_id='org.gtk.Example')
app.connect('activate', on_activate)
app.run(None)

PyGObject Application Screenshot Hello world application built using PyGObject, running on Ubuntu Linux 21.10

PyGObject is developed and maintained under the GNOME Foundation, who is also responsible for the GNOME desktop environment. PyGObject replaces several separate Python modules, including PyGTK, GIO and python-gnome, which were previously required to create a full GNOME/GTK application. Its initial release was in 2006 and it is licensed under an older version of LGPL (v2.1). While there are some differences with the current version of LGPL (v3), the license still allows its use in proprietary applications but requires any modification to the library itself to be released as open source.

Remi

Best for web based UIs for Python applications

Remi, which stands for REMote Interface, is the ideal solution for applications that are intended to be run on servers and other headless setups. (For example, on a Raspberry Pi.) Unlike most other GUI frameworks/libraries, Remi is rendered completely in the browser using a built-in web server. Hence, it is completely platform-independent and runs equally well on all platforms.

That also makes the application's interface accessible to any computer or device with a web browser that is connected to the same network. Although access can be restricted with a username and password, it doesn't implement any security strategies by default. Note that Remi is meant to be used as a desktop GUI framework and not for serving up web pages. If more than one user connects to the application at the same time, they will see and interact with the exact same things as if a single user was using it.

Remi requires no prior knowledge of HTML or other similar web technologies. You only need to have a working understanding of Python to use it, which is then automatically translated to HTML. It also comes included with a drag n drop GUI editor that is akin to Qt Designer for PyQt and PySide.

python
import remi.gui as gui
from remi import start, App

class MyApp(App):

    def main(self):
        container = gui.VBox(width=120, height=100)

        # Create a button, with the label "Hello, World!"
        self.bt = gui.Button('Hello, World?')
        self.bt.onclick.do(self.on_button_pressed)

        # Add the button to the container, and return it.
        container.append(self.bt)
        return container

    def on_button_pressed(self, widget):
        self.bt.set_text('Hello, World!')

start(MyApp)

Remi is licensed under the Apache License v2.0, which is another 'permissive' license similar to the MIT License. The license allows using it in both open source and proprietary applications, while also allowing proprietary modifications to be made to the framework itself. Its main conditions revolve around the preservation of copyright and license notices.

Remi Application Screenshot Hello world application built using Remi, running on Chrome on Windows 11

Conclusion

If you're looking to build GUI applications with Python, there is probably a GUI framework/library listed here that fits the bill for your project. Try and weigh up the capabilities & licensing of the different libraries with the scale of your project, both now and in the future.

Don't be afraid to experiment a bit with different libraries, to see which feel the best fit. While the APIs of GUI libraries are very different, they share many underlying concepts in common and things you learn in one library will often apply in another.

You are only limited by your own imagination. So go out there and make something!

For an in-depth guide to building GUIs with Python see my PyQt6 book.

Cutelyst v3.5 relicensed as BSD-3-Clause

Cutelyst the Qt Web framework just got a new license, the more permissive BSD-3-Clause. Back in 2013 when I started the project the LGPL was a perfectly fine license as software on servers can be closed as long they are not AGPL, thus it was permissive enough for the web and REST backends use-cases I had in mind.

Fast-forward almost 10 years and I have used for a few of projects where it was embedded into another application, and I realized that there might be users with commercial Qt license that would like to use it but can't due the current license.

Hesitate no more! It's amazing how much nicer it is to implement client-server applications using HTTP/REST APIs all with Qt/C++, and when real time is needed WebSockets is also there to the rescue.

Since Ubuntu 22.04 has Qt 5.15 and 6.2 releases, for the next releases 5.15.2 will be the new minimum as I want to increase the QStringView usage eventually doing a major version update.

*UPDATE: This release includes a QtWidgets example application showing how to use Cutelyst::Server embedded into a GUI application.

https://github.com/cutelyst/cutelyst/releases/tag/v3.5.0