Python3 Qt5 (PyQt5) Tutorial

Introduction

Qt is a robust cross-platform framework that works on Windows, Linux, Mac, Android, and more. It allows you to create GUI applications as well as provides libraries for networking,

In this tutorial, we will focus on some of the very core aspects of using PyQt5 and how to package it for distribution.

Here is an example project I created using Pyqt5: https://github.com/DevDungeon/PyQt5-Bitcoin-Price-Checker and the live coding of that project https://www.devdungeon.com/content/live-coding-pyqt5-guis-w-python.

Install pyqt5

You need the pyqt5 package for Python and Qt Designer. You can get both of these by running:

# The primary package with all dependencies
pip install pyqt5
# Optional; In Windows, you can get the designer tools from this package
pip install pyqt5-tools

You will find the designer in the site-packages/pyqt5_tools directory of your Python environment and should be available in your PATH as designer or pyqt5designer.

These packages will also provide pyuic5 for converting Designer files to Python files. It also includes many other utilities like qdbus, qdoc, qmlscene, linguist, and more.

If you are using Linux or a system that doesn't have the packages for pyqt5-tools you can usually find the Designer and other apps in your system's package repositories. For example, in Fedora there is a package named qt5-designer.

You can test your installation by attempting to run the Hello World application in the next section.

Hello World

Here is a simple example that demonstrates how to initialize a PyQt5 application. It also includes a simple window with nothing in it. A window is not technically required for a PyQt5 application, but generally the primary reason Qt is used is to create GUI windows.

# hello.py
import sys
from PyQt5.QtWidgets import QApplication, QWidget

# Create the main Qt app, passing command line arguments
app = QApplication(sys.argv)

win = QWidget()
win.setWindowTitle('Hello')
win.resize(250, 250)
win.show()

# Run the app, passing it's exit code back through `sys.exit()`
# The app will exit when the close button is pressed on the main window.
sys.exit(app.exec_())

Run the application like normal using Python:

python hello.py

In Windows, you can use the .pyw extension or use pythonw to run the application without the command prompt showing.

Using Qt Designer and .ui files

Using the Designer is optional, but it can be a very helpful way to layout interfaces. I highly recommend using the Designer.

Once you have created a .ui file in the Designer, you can either convert it to a Python file which will create the interface programmatically, or you can load the .ui file directly. The next sections will cover both options.

Convert UI file to Python code

From the terminal, you can convert a QtDesigner .ui file to a Python file. This method works fine, however it becomes difficult to iterate rapidly on changes with this extra step. You also cannot modify the .py file if you ever want to regenerate it. I recommend using the method in the next section, where you load the .ui file directly.

pyuic5 my_design.ui -o my_window.py
python my_window.py

The Python file output will have all the code needed to recreate the interface created in Designer.

Load the UI file in Python

My preferred option for loading interfaces, is to the load the .ui file directly. This means you will need to include the .ui file with your distribution, but it means you can easily make changes in the Designer and quickly test them in Python w/o the extra step of converting the file.

from PyQt5 import uic

# If you saved the template in `templates/main_window.ui`
ui = uic.loadUi("templates/main_window.ui")

# Then you can access the objects from the UI
# For example, if you had a label named label1
ui.label1.setText('new text')

Signals

Signals are a critical concept to understand when using the Qt framework. Signals and events are similar to channels in Go. Every signal you create must have a data type associated with it. The signal can then emit events that contain data of that type.

For example, if you create a string signal, you can emit strings to anyone listening for the events. You can create an integer signal that spits out integers to listeners. Signals are thread safe.

Alternatively, you can use a decorator @pyqtSlot(). You can learn more about that method at https://www.riverbankcomputing.com/static/Docs/PyQt5/signals_slots.html.

Some widgets come with signals already. For example, a button comes with a clicked signal that can be connected.

The next section on threading will provide a working example of how to create a custom signal, connect it to a callback function, and emit events.

Threading

To use QThreads, you can create a subclass of QThread. Be sure to call the parent class constructor, and create any signals that will be used to pass data.

from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import pyqtSignal, QThread
import sys


class MyTask(QThread):
    done_signal = pyqtSignal(str)

    def __init__(self):
        QThread.__init__(self)

    def run(self):
        # Do some work here
        self.done_signal.emit('some string')


def process_done_signal(result):
        print(result)
        sys.exit()


if __name__ == '__main__':
    app = QApplication(sys.argv)

    task = MyTask()
    task.done_signal.connect(process_done_signal)
    task.start()

    # This will continue to run forever, except we are killing the app
    # in the process_done_signal() function.
    sys.exit(app.exec_())

Button clicks

Buttons provide some signals out of the box. For example, the clicked event is a signal that can be connected to a callback. For example:

# Assuming you have loaded a `.ui` and stored it in an object named `ui`
# and there is a button named `button1`
ui.button1.clicked.connect(some_function)

One important thing to keep in mind is that the callback function will block your main application thread, unless it runs the operations in its own thread. See the section above about how to create QThreads. In this example, some_function should kick off a thread to perform operations.

Packaging

You can package your PyQt5 app in a number of ways including:

We will only look at using PyInstaller here since regular Python packaging is already well documented.

PyInstaller can be used to create .exe files for Windows, .app files for Mac, and distributable packages for Linux. Optionally, it can create a single file which is more convenient for distributing, but takes slightly longer to start because it unzip itself.

Use pyi-makespec to generate a .spec file based on your .py application. You can find more details about spec files at https://pythonhosted.org/PyInstaller/spec-files.html.

# Create a spec file that specifies no console and custom icon
# The icon is optional
pyi-makespec myapp.py --noconsole --icon=path/to/icon.ico
# Or, if you want to create a single file use:
pyi-makespec myapp.py --onefile --noconsole --icon=path/to/icon.ico

After generating the spec file, you can customize it to suit your needs. For example, to add template files, modify the datas variable.

# Modify the line in the .spec file that has datas=[]
# and include the files to copy over. Put each pair in a tuple.
    datas=[('templates/*.ui', 'templates')],

Then, after the spec file is complete, you can build it using PyInstaller.

pyinstaller myapp.spec

Documentation

You can get the local documentation on your computer by running the pydoc server and visiting it in your browser.

python -m pydoc -p 8888

Alternatively, you can read the online documentation at https://www.riverbankcomputing.com/static/Docs/PyQt5.

More examples

You can find many examples in this GitHub repository: https://github.com/baoboa/pyqt5/tree/master/examples/ and a few examples in the DevDungeon Cookbook.

Conclusion

After working through this tutorial you should have an understanding of how to make basic PyQt5 applications and package them for distribution. PyQt5 is capable of a whole lot more, like system tray icons, dialogs, taking screenshots, drag-and-drop and much more. This tutorial should be enough to just get you started and able to explore more of the available widgets and tools.

References