PyQt5 Simple Guide

This is a compact PyQt5 guide for common GUI tasks: converting Qt Designer files, making scrollable layouts, connecting signals, changing the application icon, reordering tabs, and moving long-running work into a QThread.

Generate a .py File From a .ui File

If the interface is designed in Qt Designer, convert the .ui file into Python with pyuic:

python -m PyQt5.uic.pyuic -x [FILENAME].ui -o [FILENAME].py

The -x option adds a small runnable test block. If the generated file will only be imported by another module, omit -x:

python -m PyQt5.uic.pyuic [FILENAME].ui -o [FILENAME].py

A useful project structure is:

project/
├── main.py
├── ui_main.py        # generated from .ui
├── workers.py        # QThread/Worker code
└── resources/
    └── icon.png

Keep generated UI files separate from application logic. If the .ui file changes, regenerate ui_main.py instead of editing it manually.

Make a Widget Scrollable

To make a large form or result panel scrollable, put the content widget inside a QScrollArea:

from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea, QLabel

content = QWidget()
layout = QVBoxLayout(content)

for i in range(100):
    layout.addWidget(QLabel(f"Row {i}"))

scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(content)

If using Qt Designer, add a QScrollArea, place a child widget inside it, and put the actual layout on that child widget. The scroll area itself should usually have widgetResizable enabled.

Change Appearance and Set the Taskbar Icon

Use setWindowTitle() and setWindowIcon() on the main window. Set the application icon before showing the window:

import sys
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)
app.setWindowIcon(QIcon("resources/icon.png"))

window = QMainWindow()
window.setWindowTitle("PyQt5 Application")
window.setWindowIcon(QIcon("resources/icon.png"))
window.show()

sys.exit(app.exec_())

For simple styling, use a stylesheet:

window.setStyleSheet("""
QPushButton {
    padding: 6px 10px;
}
QLineEdit {
    padding: 4px;
}
""")

Keep stylesheets small unless the project has a dedicated theme file.

Connect Signals

A signal is connected to a slot with .connect():

self.button.clicked.connect(self.run_task)

A slot can be any callable:

def run_task(self):
    print("Button clicked")

To pass arguments, use lambda or functools.partial:

self.button.clicked.connect(lambda: self.open_file("data.txt"))

If a button is reconfigured dynamically, disconnect outdated handlers before connecting a new one:

try:
    self.button.clicked.disconnect()
except TypeError:
    pass

self.button.clicked.connect(self.new_handler)

Calling disconnect() when no signal is connected raises TypeError, so catch it when the connection state is uncertain.

Use pyqtSignal

Custom signals are declared as class attributes:

from PyQt5.QtCore import QObject, pyqtSignal

class Worker(QObject):
    progress = pyqtSignal(int)
    result = pyqtSignal(str)
    failed = pyqtSignal(str)

A signal can emit multiple values:

class Worker(QObject):
    finished = pyqtSignal(str, int)

The type list follows this pattern:

pyqtSignal(type1, type2, ...)

For multiple values, prefer emitting structured values directly instead of relying on global variables:

self.finished.emit("done", 100)

A tuple can also be emitted as one object:

summary = pyqtSignal(tuple)
self.summary.emit((filename, count, elapsed_seconds))

Run Long Tasks With QThread

Do not run slow work directly in a button handler. It blocks the event loop and freezes the GUI. Move the work into a worker object running in a separate QThread.

from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot

class Worker(QObject):
    progress = pyqtSignal(int)
    finished = pyqtSignal(str)
    failed = pyqtSignal(str)

    @pyqtSlot()
    def run(self):
        try:
            for i in range(101):
                # Do part of the long-running task here.
                self.progress.emit(i)
            self.finished.emit("Task complete")
        except Exception as exc:
            self.failed.emit(str(exc))


def start_worker(self):
    self.thread = QThread()
    self.worker = Worker()
    self.worker.moveToThread(self.thread)

    self.thread.started.connect(self.worker.run)
    self.worker.progress.connect(self.progress_bar.setValue)
    self.worker.finished.connect(self.on_worker_finished)
    self.worker.failed.connect(self.on_worker_failed)

    self.worker.finished.connect(self.thread.quit)
    self.worker.failed.connect(self.thread.quit)
    self.thread.finished.connect(self.worker.deleteLater)
    self.thread.finished.connect(self.thread.deleteLater)

    self.thread.start()

Keep references to self.thread and self.worker. If they are local variables only, Python may garbage-collect them while the task is still running.

Change the Order of Tabs

For a QTabWidget, use removeTab() and insertTab():

index = self.tabs.indexOf(self.settings_tab)
widget = self.tabs.widget(index)
label = self.tabs.tabText(index)
icon = self.tabs.tabIcon(index)

self.tabs.removeTab(index)
self.tabs.insertTab(0, widget, icon, label)
self.tabs.setCurrentWidget(widget)

If the tabs are created in Qt Designer, it is often easier to reorder them there. Use code when the order depends on user settings or runtime state.

Modularize Preprocessing and Control Logic

A maintainable PyQt project separates UI setup, preprocessing, and control logic:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.connect_signals()

    def connect_signals(self):
        self.ui.runButton.clicked.connect(self.run_preprocessing)

    def run_preprocessing(self):
        options = self.collect_options()
        self.start_worker(options)

    def collect_options(self):
        return {
            "input": self.ui.inputLineEdit.text(),
            "enabled": self.ui.enableCheckBox.isChecked(),
        }

This keeps generated UI code disposable and keeps business logic testable. The main window should coordinate the interface; worker classes should handle slow processing; helper modules should hold reusable parsing, preprocessing, or calculation functions.

Leave a Reply