Build a Desktop App with PySide6 #1: What PySide6 Is — Desktop Apps with Qt and Python

6 min read

Once you have worked through Modern Python Basics, you can build most automation scripts and CLI tools in Python. But the moment you hand a tool to a coworker or a family member, you hit a wall. You cannot tell someone who has never opened a terminal to “activate the virtual environment and run the script.” This is the point where you need a window with a button and an input field, and this series covers how to build that window. It is the desktop extension of the Python track.

It runs in seven parts.

  • #1 What PySide6 is — desktop apps with Qt and Python ← this post
  • #2 Widgets and layouts — assembling the screen
  • #3 Signals and slots — the core of event handling
  • #4 Qt Designer and UI files — draw the screen, then load it
  • #5 Model/View — connecting data to lists and tables
  • #6 Threads and timers — keeping the UI responsive
  • #7 Packaging and distribution — building executables with PyInstaller

This post builds the case for choosing PySide6 among the Python GUI options, then covers installation, a first window, and the role of the event loop.

The Python GUI landscape #

There are three broad ways to build a desktop screen in Python.

ApproachRepresentativeCharacteristics
Standard librarytkinterNo installation needed. Few widget types and a dated default look
Qt bindingsPySide6, PyQt6A mature widget set, near-native appearance, a long track record in commercial software
Web-technology basedElectron family, pywebviewReuses a web frontend. In exchange, ships a browser engine, so it is heavy

For a simple internal tool, tkinter is enough. But as widgets multiply and screens grow, tkinter takes more and more manual work, and the web-based route carries the weight problem of “a notepad-level app that ships at hundreds of MB.” In between sits the option that gives you native-level performance and a rich widget set in plain Python syntax — the Qt bindings — and this series uses PySide6.

PySide6 and PyQt6 — same Qt, different licenses #

Two bindings for using Qt from Python coexist. The names are confusingly similar, but the APIs are nearly identical, and the decisive difference is who makes it and under what license it ships.

AspectPySide6PyQt6
MakerQt Group (official Qt)Riverbank Computing (third party)
LicenseLGPL v3 (free to ship commercial apps)GPL v3 or a paid commercial license
Official nameQt for PythonPyQt
API differencesMinor notation differences such as Signal, SlotpyqtSignal, pyqtSlot notation

The license is the heart of it. With LGPL PySide6 you can build and sell a closed-source commercial app for free, while doing the same with GPL PyQt6 requires buying a commercial license. Searching for learning material turns up more PyQt articles, but the APIs are close enough to cross-reference, and if you are starting fresh, PySide6 — officially supported and license-friendly — is the safe choice.

Note
The 6 in PySide6 means it is built on Qt 6. This series targets PySide6 6.11 (Qt 6.11) and requires Python 3.10 or later. PySide2 material (Qt 5 based) that you may run into while searching uses different import paths and some different APIs, so check the version before reading.

Installation #

Create a project directory and install PySide6. We use uv, the same tool as in the Modern Python track.

Install with uv
uv init todo-app
cd todo-app
uv add pyside6
If you use pip
pip install pyside6

The installation pulls in hundreds of Qt modules. The two this series mainly uses are QtWidgets, where the widgets live, and QtCore, which holds core features such as signals and timers.

Showing your first window #

Let’s put a window on screen right away. Twenty lines are enough.

main.py — first window
import sys

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My first PySide6 app")
        self.resize(400, 300)

        button = QPushButton("Click me")
        self.setCentralWidget(button)


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())
Run
uv run python main.py

If a window appears with a button filling it, you have succeeded. The code is short, but its structure is the skeleton of this entire series, so let’s confirm it line by line.

  • QApplication — the object that manages the whole application. Create exactly one per program, before any widget.
  • QMainWindow — the top-level window with a menu bar, status bar, and central widget area. Putting your screen composition into a class that inherits from it is the standard pattern.
  • setCentralWidget — places a widget in the window’s central area. For now it is a single button; from the next post on, a bundle of widgets assembled with layouts goes here.
  • window.show() — creating a window does not display it. It appears on screen only after you call show.

app.exec() — what the event loop means #

The last line, app.exec(), is the fork in the road between a GUI program and a script. The Python scripts so far ran top to bottom and exited when done. A GUI app, though, cannot know when the user will click a button or close the window, so it needs a structure that waits without ending.

app.exec() starts the event loop. Whenever an event arrives — a mouse click, keyboard input, a window resize — it delivers the event to the right widget, then goes back to waiting for the next one, in an endless cycle. When you close the window, the loop ends and returns an exit code, and sys.exit() hands that code to the operating system.

One rule follows from this structure: block the event loop for long and the whole app freezes up. If the function connected to a button click runs a ten-second computation, no screen updates and no click handling happen in the meantime. We face this problem and its solution head-on in #6, where we cover threads.

Tip
Beyond widgets, Qt includes application-framework-level modules such as networking (QtNetwork), multimedia (QtMultimedia), and 2D graphics (QtGraphics). This series focuses on widgets and core, the heart of desktop apps, but the fact that you never need to switch frameworks as your app grows is one of Qt’s strengths.

Wrap-up #

This post comes down to three points.

  • Among Python GUI options, PySide6 is the official Qt binding, and its LGPL license lets you build even commercial apps for free.
  • The standard skeleton of a GUI app is one QApplication, a class inheriting QMainWindow, a show call, and app.exec().
  • app.exec() starts the event loop that waits for and dispatches events, and blocking that loop freezes the app.

In the next post, “Build a Desktop App with PySide6 #2: Widgets and Layouts — Assembling the Screen,” we move past the one-button window and assemble an input field, a list, and buttons with layouts into the screen skeleton of a todo app. That app is the hands-on material we will keep extending throughout the series.

X