Table of Contents
- Getting Started
- Creating Custom Widgets
- The complete book
Welcome to Create Simple GUI Applications the practical guide to building professional desktop applications with Python & Qt.
If you want to learn how to write GUI applications it can be pretty tricky to get started. There are a lot of new concepts you need to understand to get anything to work. A lot of tutorials offer nothing but short code snippets without any explanation of the underlying systems and how they work together. But, like any code, writing GUI applications requires you to learn to think about the problem in the right way.
In this book I will give you the real useful basics that you need to get building functional applications with the PyQt framework. I’ll include explanations, diagrams, walkthroughs and code to make sure you know what you’re doing every step of the way. In no time at all you will have a fully functional Qt application - ready to customise as you like.
The source code for each step is included, but don’t just copy and paste and move on. You will learn much more if you experiment along the way!
So, let’s get started!
This book is formatted as a series of coding exercises and snippets to allow you to gradually explore and learn the details of PyQt5. However, it is not possible to give you a complete overview of the Qt system in a book of this size (it’s huge, this isn’t), so you are encouraged to experiment and explore along the way.
If you find yourself thinking “I wonder if I can do that” the best thing you can do is put this book down, then go and find out! Just keep regular backups of your code along the way so you always have something to come back to if you royally mess it up.
When you write applications using PyQt what you area really doing is writing applications in Qt. The PyQt library is simply1 a wrapper around the C++ Qt library, to allow it to be used in Python.
Because this is a Python interface to a C++ library the naming conventions used within PyQt do not adhere to PEP8 standards. Most notably functions and variables are named using
mixedCase rather than
snake_case. Whether you adhere to this standard in your own applications based on PyQt is entirely up to you, however you may find it useful to help clarify where the PyQt code ends and your own begins.
Further, while there is PyQt specific documentation available, you will often find yourself reading the Qt documentation itself as it is more complete. If you do you will need to translate object syntax and some methods containing Python-reserved function names as follows:
This book is written to be compatible with Python 3.4+. Python 3 is the future of the language, and if you’re starting out now is where you should be focusing your efforts. However, in recognition of the fact that many people are stuck supporting or developing on legacy systems, the examples and code used in this book are also tested and confirmed to work on Python 2.7. Any notable incompatibility or gotchas will be flagged with a meh-face to accurately portray the sentiment e.g.
If you are using Python 3 you can safely ignore their indifferent gaze.
Before you start coding you will first need to have a working installation of PyQt and Qt on your system. The following sections will guide you through this process for the main available platforms. If you already have a working installation of PyQt on your Python system you can safely skip this part and get straight onto the fun.
The complete source code all examples in this book is available to download from here.
PyQt5 for Windows can be installed as for any other application or library. The only slight complication is that you must first determine whether your system supports 32bit or 64bit software. You can determine whether your system supports 32bit or 64bit by looking at the System panel accessible from the control panel.
If your system does support 64bit (and most modern systems do) then you
should also check whether your current Python install is 32 or 64 bit. Open a command prompt (Start >
Look at the top line of the Python output, where you should be able to see whether you have 32bit or 64bit Python installed. If you want to switch to 32bit or 64bit Python you should do so at this point.
A PyQt5 installer for Windows is available direct from the developer Riverbank Computing. Download the
.exe files from the linked page, making sure you download the currently 64bit or 32bit version for your system. You can install this file as for any other Windows application/library.
After install is finished, you should be able to run
Unfortunately, if you want to use PyQt5 on Python 2.7 there are no official installers available to do this. This part of a policy by Riverbank Computing to encourage transition to Python 3 and reduce their support burden.
Simply download the above
.rar file and unpack it with 7zip (or other unzip application). You can then copy the resulting folder to your Python site-packages folder — usually in
Once that is done, you should be able to run
OS X comes with a pre-installed version of Python (2.7), however attempting to install PyQt5 into this is more trouble than it is worth. If you are planning to do a lot of Python development, and you should, it will be easier in the long run to create a distinct installation of Python separate from the system.
By far the simplest way to do this is to use Homebrew. Homebrew is a package manager for command-line software on MacOS X. Helpfully Homebrew also has a pre-built version of PyQt5 in their repositories.
To install Homebrew run the following from the command line:
Once the Homebrew installation has completed, you can then install Python 3 and PyQt5 as follows:
After that has completed, you should be able to run
Installation on Linux is very straightforward as packages for PyQt5 are available in the repositories of most distributions. In Ubuntu you can install either from the command line or via “Software Center”. The packages you are looking for are named
python-pyqt5 depending on which version you are installing for.
You can also install these from the command line as follows:
Or for Python 2.7:
Once the installation is finished, you should be able to run
In the previous chapter we introduced
QPainter and looked at some basic bitmap drawing operations which you can used to draw dots, lines, rectangles and circles on a
QPainter surface such as a
This process of drawing on a surface with
QPainter is in fact the basis by which all widgets in Qt are drawn.Now you know how to use
QPainter you know how to draw your own custom widgets!
In this chapter we’ll take what we’ve learnt so far and use it to construct a completely new custom widget. For a working example we’ll be building the following widget — a customisable PowerBar meter with a dial control.
This widget is actually a mix of a compound widget and custom widget in that we are using the built-in Qt
QDial component for the dial, while drawing the power bar ourselves. We then assemble these two parts together into a parent widget which can be dropped into place seamlessly in any application, without needing to know how it’s put together. The resulting widget provides the common
QAbstractSlider interface with some additions for configuring the bar display.
After following this example you will be able to build your very own custom widgets — whether they are compounds of built-ins or completely novel self-drawn wonders.
As we’ve previously seen compound widgets are simply widgets with a layout applied, which itself contains >1 other widget. The resulting “widget” can then be used as any other, with the internals hidden/exposed as you like.
The outline for our PowerBar widget is given below — we’ll build our custom widget up gradually from this outline stub.
This simply defines our custom power bar is defined in the
_Bar object — here just unaltered subclass of
PowerBar widget (which is the complete widget) combines this, using a
QVBoxLayout with the built in
QDial to display them together.
Save this to a file named
We also need a little demo application to display the widget.
N> We don’t need to create a
QMainWindow since any widget without a parent is a window in it’s own right. Our custom
PowerBar widget will appear as any normal window.
This is all you need, just save it in the same folder as the previous file, under something like
demo.py. You can run this file at any time to see your widget in action. Run it now and you should see something like this:
If you stretch the window down you’ll see the dial has more space above it than below — this is being taken up by our (currently invisible)
paintEvent handler is the core of all widget drawing in PyQt.
Every complete and partial re-draw of a widget is triggered through a
paintEvent which the widget handles to draw itself. A
paintEvent can be triggered by —
- repaint() or update() was called
- the widget was obscured and has now been uncovered
- the widget has been resized
— but it can also occur for many other reasons. What is important is that when a
paintEvent is triggered your widget is able to redraw it.
If a widget is simple enough (like ours is) you can often get away with simply redrawing the entire thing any time anything happens. But for more complicated widgets this can get very inefficient. For these cases the
paintEvent includes the specific region that needs to be updated. We’ll make use of this in later, more complicated examples.
For now we’ll do something very simple, and just fill the entire widget with a single colour. This will allow us to see the area we’re working with to start drawing the bar.
Now we can see the
_Bar widget we can tweak its positioning and size. If you drag around the shape of the window you’ll see the two widgets changing shape to fit the space available. This is what we want, but the
QDial is also expanding vertically more than it should, and leaving empty space we could use for the bar.
We can use
setSizePolicy on our
_Bar widget to make sure it expands as far as possible. By using the
QSizePolicy.MinimumExpanding the provided
sizeHint will be used as a minimum, and the widget will expand as much as possible.
It’s still not perfect as the
QDial widget resizes itself a bit awkwardly, but our bar is now expanding to fill all the available space.
With the positioning sorted we can now move on to define our paint methods to draw our PowerBar meter in the top part (currently black) of the widget.
We now have our canvas completely filled in black, next we’ll use
QPainter draw commands to actually draw something on the widget.
Before we start on the bar, we’ve got a bit of testing to do to make sure we can update the display with the values of our dial. Update the
paintEventwith the following code.
This draws the black background as before, then uses
.parent() to access our parent
PowerBar widget and through that the
_dial. From there we get the current value, as well as the allowed range minimum and maximum values. Finally we draw those using the painter, just like we did in the previous part.
Run this, wiggle the dial around and …..nothing happens. Although we’ve defined the
paintEvent handler we’re not triggering a repaint when the dial changes.
To fix this we need to hook up our
_Barwidget to repaint itself in response to changing values on the dial. We can do this using the
QDial.valueChangedsignal, hooking it up to a custom slot method which calls
.refresh() — triggering a full-repaint.
Add the following method to the
…and add the following to the
__init__ block for the parent
If you re-run the code now, you will see the display updating automatically as you turn the dial (click and drag with your mouse). The current value is displayed as text.
Now we have the display updating and displaying the current value of the dial, we can move onto drawing the actual bar display. This is a little complicated, with a bit of maths to calculate bar positions, but we’ll step through it to make it clear what’s going on.
The sketch below shows what we are aiming for — a series of N boxes, inset from the edges of the widget, with spaces between them.
The number of boxes to draw is determined by the current value — and how far along it is between the minimum and maximum value configured for the
QDial. We already have that information in the example above.
value is half way between
vmax then we want to draw half of the boxes (if we have 4 boxes total, draw 2). If
value is at
vmax we want to draw them all.
To do this we first convert our
value into a number between 0 and 1, where
0 = vmin and
1 = vmax. We first subtract
value to adjust the range of possible values to start from zero — i.e. from
0…(vmax-vmin). Dividing this value by
vmax-vmin (the new maximum) then gives us a number between 0 and 1.
The trick then is to multiply this value (called
pc below) by the number of steps and that gives us a number between 0 and 5 — the number of boxes to draw.
We’re wrapping the result in
int to convert it to a whole number (rounding down) to remove any partial boxes.
drawText method in your paint event to write out this number instead.
As you turn the dial you will now see a number between 0 and 5.
Next we want to convert this number 0…5 to a number of bars drawn on the canvas. Start by removing the
drawText and font and pen settings, as we no longer need those.
To draw accurately we need to know the size of our canvas — i.e the size of the widget. We will also add a bit of padding around the edges to give space around the edges of the blocks against the black background.
We take the height and width and subtract
2 * padding from each — it’s 2x because we’re padding both the left and right (and top and bottom) edges. This gives us our resulting active canvas area in
We need to break up our
d_height into 5 equal parts, one for each block — we can calculate that height simply by
d_height / 5. Additionally, since we want spaces between the blocks we need to calculate how much of this step size is taken up by space (top and bottom, so halved) and how much is actual block.
These values are all we need to draw our blocks on our canvas. To do this we count up to the number of steps-1 starting from 0 using
range and then draw a
fillRect over a region for each block.
N> The fill is set to a red brush to begin with but we will customise this later.
The box to draw with
fillRect is defined as a
QRect object to which we pass, in turn, the left x, top y, width and height.
The width is the full canvas width minus the padding, which we previously calculated and stored in
d_width. The left x is similarly just the
padding value (5px) from the left hand side of the widget.
The height of the bar
bar_heightwe calculated as 0.6 times the
This leaves parameter 2
d_height - ((1 + n) * step_size) + bar_spacer which gives the top y position of the rectangle to draw. This is the only calculation that changes as we draw the blocks.
A key fact to remember here is that y coordinates in
QPainter start at the top and increase down the canvas. This means that plotting at
d_height will be plotting at the very bottom of the canvas. When we draw a rectangle from a point it is drawn to the right and down from the starting position.
In our bar meter we’re drawing blocks, in turn, starting at the bottom and working upwards. So our very first block must be placed at
d_height-step_size and the second at
d_height-(step_size*2). Our loop iterates from 0 upwards, so we can achieve this with the following formula —
The final adjustment is to account for our blocks only taking up part of each
step_size (currently 0.6). We add a little padding to move the block away from the edge of the box and into the middle, and finally add the padding for the bottom edge. That gives us the final formula —
This produces the following layout.
Putting this all together gives the following code, which when run will produce a working power-bar widget with blocks in red. You can drag the wheel back and forth and the bars will move up and down in response.
That already does the job, but we can go further to provide more customisation, add some UX improvements and improve the API for working with our widget.
We now have a working power bar, controllable with a dial. But it’s nice when creating widgets to provide options to configure the behaviour of your widget to make it more flexible. In this part we’ll add methods to set customisable numbers of segments, colours, padding and spacing.
The elements we’re going to provide customisation of are as follows —
|number of bars||How many bars are displayed on the widget|
|colours||Individual colours for each of the bars|
|background colour||The colour of the draw canvas (default black)|
|padding||Space around the widget edge, between bars and edge of canvas.|
|bar height / bar percent||Proportion (0…1) of the bar which is solid (the rest will be spacing between adjacent bars)|
We can store each of these as attributes on the
_bar object, and use them from the
paintEvent method to change its behaviour.
_Bar.__init__ is updated to accept an initial argument for either the number of bars (as an integer) or the colours of the bars (as a list of
QColor, hex values or names). If a number is provided, all bars will be coloured red. If the a list of colours is provided the number of bars will be determined from the length of the colour list. Default values for
self._padding are also set.
Likewise we update the
PowerBar.__init__ to accept the steps parameter, and pass it through.
We now have the parameters in place to update the
paintEvent method. The modified code is shown below.
You can now experiment with passing in different values for the init to
PowerBar, e.g. increasing the number of bars, or providing a colour list. Some examples are shown below — a good source of hex palettes is the Bokeh source.
You could fiddle with the padding settings through the variables e.g.
self._bar_solid_percent but it’d be nicer to provide proper methods to set these.
N> We’re following the Qt standard of camelCase method names for these external methods for consistency with the others inherited from
In each case we set the private variable on the
_bar object and then call
_bar.update() to trigger a redraw of the widget. The method support changing the colour to a single colour, or updating a list of them — setting a list of colours can also be used to change the number of bars.
N> There is no method to set the bar count, since expanding a list of colours would be faffy. But feel free to try adding this yourself!
Here’s an example using 25px padding, a fully solid bar and a grey background.
With these settings you get the following result.
We’ve added methods to configure the behaviour of the power bar. But we currently provide no way to configure the standard
QDial methods — for example, setting the min, max or step size — from our widget. We could work through and add wrapper methods for all of these, but it would get very tedious very quickly.
Instead we can add a little handler onto our outer widget to automatically look for methods (or attributes) on the
QDial instance, if they don’t exist on our class directly. This way we can implement our own methods, yet still get all the
QAbstractSlider goodness for free.
The wrapper is shown below, implemented as a custom
When accessing a property (or method) — e.g. when when call
PowerBar.setNotchesVisible(true) Python internally uses
__getattr__ to get the property from the current object. This handler does this through the object dictionary
self.__dict__. We’ve overridden this method to provide our custom handling logic.
Now, when we call
PowerBar.setNotchesVisible(true), this handler first looks on our current object (a
PowerBar instance) to see if
.setNotchesVisible exists and if it does uses it. If not it then calls
self._dial instead returning what it finds there. This gives us access to all the methods of
QDial from our custom
QDial doesn’t have the attribute either, and raises an
AttributeError we catch it and raise it again from our custom widget, where it belongs.
Currently you can update the current value of the PowerBar meter by twiddling with the dial. But it would be nice if you could also update the value by clicking a position on the power bar, or by dragging you mouse up and down. To do this we can update our
_Bar widget to handle mouse events.
__init__ block for the
PowerBar widget we can connect to the
_Bar.clickedValue signal and send the values to
self._dial.setValue to set the current value on the dial.
If you run the widget now, you’ll be able to click around in the bar area and the value will update, and the dial rotate in sync.
Below is the complete final code for our PowerBar meter widget, called
PowerBar. You can save this over the previous file (e.g. named
power_bar.py) and then use it in any of your own projects, or customise it further to your own requirements.
You should be able to use many of these ideas in creating your own custom widgets. For more examples, take a look at the Learn PyQt widget library — these widgets are all open source and freely available to use in your own projects.
Thankyou for downloading this sample of Create Simple GUI Applications
If you like what you see you can purchase the complete book, together with an optional video course, at: https://www.learnpyqt.com/purchase
The complete book contains the following chapters —
- Installation and Getting Started
- Basic Qt Features
- Qt Creator & Qt Designer
- Extended Signals
- Bitmap Graphics, including a mini Paint-clone app
- Custom Widgets, for full customisability in your applications
- Model View Architecture, building MVC applications in PyQt5, including an example Todo app
- Multithreading, supporting concurrent execution in PyQt5 apps
- Example PyQt5 apps, including a Web Browser and Minesweeper Clone
- Packaging and Distribution
The full book covers many more aspects of developing with PyQt, from getting started with the Qt Creator to multithreading advanced applications. All purchases come with free updates as the book is developed and expands.
For latest tutorials, tips and code samples see https://www.learnpyqt.com/
1Not really that simple.↩