Quick start into GUI applications with PyQt5 and PySide2
Qt is a powerful framework for developing cross platform GUI applications. KDE Linux desktop environment uses it as well as many Open Source and commercial applications.
In this article I'll showcase how to use Qt in Python to make GUI applications (the very basics of it).
For Python developers since long time PyQt binding were available. As of now there is also Qt for Python project that provides PySide2 bindings.
There is a very good comparison on machinekoder.com. In short PyQt bindings existed for much longer and are distributed on GPL or Commercial license. PySide2 is distributed on LGPL or Qt commercial license - which may make a difference in some legal cases. In terms of making simple apps there shouldn't be much differences aside of imports. For more complex ones there may be some.
You may find a lot of tutorials for PyQt due to it age. PyQt4 is the former main version while now we use PyQt5. Some code did change (like signals and slots syntax) but overall the workflow should be the same. If you pick up some older PyQt4 books keep that in mind (and the book may use Python 2 instead of 3).
There are also books and resources for PyQt5, like Create Simple GUI Applications with Python and PyQt5 while also having some online resources on learnpyqt.com.
PyQt and PySide can be installed as any other Python packages via PyPi. You should also install Qt development tools. Qt Designer is the desired application for now. It allows creating user interface of the app in a visual manner. On Linux it can be in a separate package while for macOS or Windows it will likely be with the official Qt development package (macOS package managers like homebrew may also have split packages).
Compared to web development, common in the Python community, the GUI application development is less popular and it can get quite complex much quicker. Before developing an app check if a web variant isn't a better solution (no need to build, distribute, install etc.).
When creating an app with a GUI be sure to make the GUI follow look and feel of other similar apps - a.k.a. you should try not to reinvent styles or some widget placements unless you are making an app with non-standard UI. For each operating system or desktop platform there are Human interface guidelines that describe how applications should look and behave to meet user experience standards.
Qt does follow those guidelines and also tries to make widgets follow current style of the system - if you run your app on Windows or on macOS it will look like other apps of that particular OS as well as some widgets may look different or be placed in different spots of the window.
Kivy uses it own style and will not follow OS style and usability guidelines – everything is in your hands. Tkinter will follow the style to some extent although the widgets won’t look
as good as in native apps.
In Qt class reference you will find functionalities that are also covered by Python standard library or popular third party packages. For example you can find QFile, QDir classes that represent files and directories and with other related classes implement a lot of filesystem operations. You can even run a SQL query via QSqlQuery and more.
With such overlap one can wonder which solution should be used - Python or PyQt/PySide bindings to C++ classes of Qt? Overall I would advise using Qt path as much as possible. A lot of classes and widgets is inter-compatible - meaning one class can accept an instance of another, like accepting a QFile object or use QSqlQuery to populate a grid and more.
If the Qt usage seems overly complicated and you know you aren't loosing any of that
glue then sure, you can also use a Python way of doing things.
Also note that a lot of Qt classes takes operating system differences into account which may not be handled as well by non-Qt Python package. Like if you want to print a file Qt has you covered while when trying pure Python solutions you would likely need more time and testing to see if it's cross-platform (like you even have QPrintDialog in Qt...).
QtDesigner is used to create the user interface of your app. It can cooperate with other Qt tools to create translation files for i18n-enabled apps or manage assets like images and other files used by the app.
We will use it to draw UI of our example image viewer, but first some basics:
As an example app I will want to make a simple image viewer - a button than when clicked will open a file dialog prompting to select an image file. When a file will be selected I want to display it as well as the path to the file.
As you can see label on the bottom and the two widgets at the top have equal height. We want the button and it label to be always on top while the label that will display the image to take the whole remaining space. This is configured on the right panel:
It's really handy to properly name widgets as an IDE will code complete those names and
openImageButton is more clear than
pushButton (imagine having many buttons in an app). And of course I set the text used by the button and labels.
Save the file and prepare to convert it to Python code.
PyQt5 uses pyuic5 while PySide2 uses pyside2-uic command line apps to convert a
ui file to a Python file containing a Python class representing the interface:
pyuic5 image_viewer.ui > image_viewer_pyqt.py pyside2-uic image_viewer.ui > image_viewer_pyside.py
You can open them and for the most part they will be similar aside of imports. You should not modify those files - anytime you change the UI you have to generate those files again and any change would be lost. They should be used only as an imported class into a skeleton code that launches it and then adds all the logic.
Create a main file for your app, for example run.py and use such boilerplate code:
Both then follow with:
We import PyQt or PySide, then the generated interface Python class and inherit it with QMainWindow widget as a mixin. The Ui_ImageViewerWindow name will be based on the objectName of the window you made in QtDesigner. If you set it differently the class will have a different name, use appropriately.
You can now run the file to launch the app. You can click on the button but nothing will happen.
Qt application runs in a
run loop that is constantly awaiting events that could be sent by user actions. QPushButton - the widget that makes the button has a defined signal - clicked. You can connect it with your code so that when button gets clicked your code will run - reacting to the click. The thing signal gets connected to is called a slot.
When you are using a widget you can easily check what methods, signals and other properties it has by looking at the >Qt class reference - lots of classes there. If you go to QPushButton you can see it methods but no signals. If you look at the table on the top you can notice it inherits QAbstractButton that has that signal.
This is how it would look like:
Whenever you click the button you should see the text in the console.
So let's practice with some additional widgets. We have the UI in the UI file but some widgets or Qt classes will be commonly used pragmatically like the file picker or prompt windows. For this example I want to use QMessageBox to ask a user if he really wants to open a file. Not really practical but I want to show you how widget looks change based on OS and how Qt handles such cases.
QMessageBox can be configured like so:
We set text, icon and buttons. As you can see it has embedded icons - widget.Question will display system specific question mark icon. Same with buttons. As you can see they have a role assigned - AcceptRole and RejectRole. Based on role they will be displayed differently based on OS. The widget has an accepted signal inherited from
QDialog, so we can connect that signal to another method:
Whenever you confirm the prompt the
show_image_picker method will be called. So now we can call the QFileDialog that has a fileSelected signal:
Wherever you select a file you should see a path printed in the console.
I named the QLabel intended to show the path as
currentFile so I can set it value simply by:
The QLabel I intend to use to display the image I named
imageLabel. Image file is not a text so it has to be done differently. QLabel does have a setPixmap method:
The final application looks like so:
I moved QMessageBox and QFileDialog widgets setup out of the main class to not mix business logic with presentation. With increasing app complexity such abstraction would become bigger and external classes would have more than one method or there would be more of them.
There is a few way for testing desktop applications and PyQt in particular. You can use pytest with pytest-qt plugin. The qtbot fixture can operate on an PyQt/PySide app. It's not easy to write such tests but doable. For example:
You can find more in the github repository. The project there also has pytest configured ready to use.
In the end you want to build and deploy your applications as stand-alone packages for users to use on different operating systems. Build tools and configurations will differ a bit between PyQt and PySide but overall the idea is the same. You can check PyQt reference and PySide reference on build instructions.
You can find full source code of this example app for PyQt5 and PySide2 variants on github. It will run on Linux, Windows and macOS if you install Python 3 and all requirements (that's how I made all of the screenshots).
I hope you liked this PyQt5/PySide2 introduction. I have some PyQt4 tutorials here, but those were written more than 10 years ago! They won't work with Python 3 and PyQt5 without modifications (and code style is bad ;)).
You can find more help and support on PyQt mailing list and similar sites, just check both project