This is a simple step-by-step walkthrough creating a GUI app using Qt Creator and PyQt5. Since GUIs are entirely visual it's hard to convey what to do through text, so this tutorial is full of step-by-step screenshots.
The app a simple sales tax calculator, which takes an input value, a tax rate and outputs the resulting total price with tax. It's not very exciting, but it is easy to understand!
Designing In Qt Creator
You will need a copy of Qt Creator to follow this tutorial. Get this by downloading Qt from here: https://www.qt.io/download — you can opt to install only Qt Creator during installation.
Start Qt Creator. To start designing create a window we need a blank design. From the File menu select "New file or project. In the popup window that appears choose:
- Qt Designer Form
- Main Window (other Widget types are available)
- Select a location to save the resulting
.ui
file - Revision control (can skip this)
- Click Done to create the design.
The steps are shown visually below.
Qt Creator — Initial View
Qt Creator — Start new design
Create GUI Applications with Python & Qt5 by Martin Fitzpatrick — (PyQt5 Edition) The hands-on guide to making apps with Python — Over 10,000 copies sold!
Qt Creator — Create a new Qt Designer Form
Qt Creator — Select MainWindow for widget type
Qt Creator — Select Save location for the .ui file
Qt Creator — Project management / revision control
Phew. After all that you should be presented with a blank empty canvas on which to construct your new application.
Qt Designer — Blank Canvas
Now we can start designing our UI, using the drag-drop interface. First select a Line Edit (QLineEdit) box from the left.
Qt Designer — with Line Edit highlighted
Drag Line Edit to the main window, to get a box like that shown below.
Qt Designer — Line Edit on MainWindow
The red highlight in the box below shows objectName
which is the name of the object is. The name entered here is the name that will be used to refer to this object from our Python code, so call it something sensible.
I'm calling it price_box, as we will enter the price into this box.
Qt Designer — Editing the objectName
The next thing we will do is attach a label to the box, to make it clear to the user what this box is for.
Find the Label in the list of widgets on the left, and drag it onto the main window. It gets the default text of "TextLabel". Double click the widget and type to change it to "Price".
Don't worry about positioning too much, we'll sort the layout right at the end. Just put the widgets in roughly the right place.
Qt Creator — Adding a Label
You can also make the text large and bold, as seen here:
Qt Creator — QLabel font properties
For the tax box, we are going to use something different — a Spin Box (QSpinBox
). This is a numeric input which limits the values you can enter. We don’t need a Spin Box, it’s just good to see how you can use different widgets that Qt Creator provides.
Drag a Spin Box to the window. The first thing we do is change the objectName
to something sensible, tax_rate
in our case. Remember, this is how this object will be called from Python.
Qt Creator — The Spin Box
We can choose a default value for our Spin Box. I'm choosing 20:
Qt Designer — Setting default value on a QSpinBox
One neat feature of QSpinBox
is that you can also set the minimum and maximum limits. We're keeping them to what they are for our app, but feel free to tweak them for yours.
Qt Designer — QSpinBox properties
Add another label called Tax Rate, editing it the same as before and drag it into place next to the Spin Box.
Qt Designer — Adding another label
Next look for the widget called Push Button in the widget panel and drag it to the window.
The button just says PushButton, which isn't very helpful. Change the objectName
to calc_tax_button
and then double click to edit the label text on the button to "Calculate Tax".
Qt Designer — Adding a QButton
Drag another Label on to the window. This label will be where we write out the result of our calculation. So double-click and edit to delete the default text. Change it's objectName
to results_output
.
Qt Designer — QLabel as output
Finally, if you want to you can add a header. This is a simple label box with the font increased:
Screenshot 2019-05-30 at 23.12.39.png
Adjusting the layout
So we now have the bones of our UI in place, but it's not looking particularly pretty. Firstly, we have a lot of empty space because our window is way too big.
To shrink the window, first select all the widgets and move them out of the way (top left), and then drag the window down to size using the blue handles in the corner.
Qt Designer — Resized QMainWIndow
That certainly helped a bit, but the alignments are still a bit wonky.
Helpfully, Qt provides a system of layouts which can be used to automatically position elements within a window. In our case we have a classic form layout, where we have a vertical stack of widgets and labels. Qt has a specific Form Layout for exactly this purpose.
To apply it, right-click on your Main Window and select "Lay out" at the bottom (yes, there is a space between Lay and out, I don't know why) and then choose "Lay Out in a Form Layout" (snappy title).
Qt Designer — Layout with a Form Layout
You should see the widgets instantly snap into position. If the positions are not correct just drag them around — they will continue to snap to a regular grid, with both sides aligned.
Qt Designer — Form Layout applied
The big label (if you've added it) doesn't have a "pair" so can't align. If you drag it around, it'll throw the other elements off. To fix this, just drag-resize it out of the form layout, and it will be ignored.
The last 2 final tweaks are —
- Set the input box to right-aligned, which is more natural for numbers.
- Update the name of the Main Window
windowTitle
property (choose a good name).
Qt Designer — Setting alignment on an input
Qt Designer — Setting the windowTitle
Done!
That's the design complete. Save your .ui
file by File -> Save (it will default to the name & location you gave during setup.
In the next part we'll write the code to bring our UI to life.
Writing the code
The UI file we've created is just an XML definition, containing the positions attributes and names of the widgets. Open it in a text editor, if you want, and you will find something like this:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>328</width>
<height>273</height>
</rect>
</property>
<property name="windowTitle">
<string>Sales Tax Calulator</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<pointsize>28</pointsize>
<weight>75</weight>
<bold>true</bold>
...
Each of the widgets we added is an object, with a set of common and widget-specific methods e.g. text()
(to read the value in a LineEdit
).
This example uses the `loadUI` method to load the UI file, for other options take a look at First steps with Qt Creator
Loading your UI
To use your UI file in your app you need to load it into your Python code. PyQt provides a uic.loadUIType()
method to load a file and create a QWidget
(or QMainWindow
) object.
The following skeleton file can be used to load any UI file generated from Qt Creator and display it.
- PyQt5
- PySide2
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
qtcreator_file = "your .ui file" # Enter file here.
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtcreator_file)
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtUiTools import QUiLoader
qtcreator_file = "your .ui file" # Enter file here.
loader = QUiLoader()
file = QtCore.QFile(qtcreator_file)
file.open(QtCore.QFile.ReadOnly)
file.close()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = loader.load(file, None)
window.show()
sys.exit(app.exec_())
Let's take a quick look at the code. At the bottom of the file we define our entrypoint which first creates a QApplication
instance, passing in sys.argv
allows Qt to be configured from the command line while running your app (we won't be doing that here).
Next we create an instance of MyWindow
, show it using window.show()
and then execute the application event loop, with app.exec_()
.
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
````
To load your own UI file just change the filename on this line
```python
qtcreator_file = "your .ui file" # Enter file here.
Save the file as tax.py
and then run it from the command line.
python3 tax.py
You should see your UI appear, looking something not unlike the following —
Simple Tax Calculator — Skeleton UI
Hooking up the logic
You'll probably notice that while you can enter numbers and click the button when you do this nothing happens. This is because we still need to implement the actual application logic in our Python code.
The key widget in our GUI was the button. Once you press the button, something happens. What? We need to tell our code what to do when the user presses the Calculate Tax button. In the init function, add this line:
self.calc_tax_button.clicked.connect(self.calculate_tax)
Remember that in Qt Creator we called our button calc_tax_button
— this was the name of the object, not the text that was displayed on it. The button is attached by loadUI
to our MyMainWindow
instance (here available as self
) using the name we defined for it.
All the objects you create in Qt Creator are available in this same way (even the labels which you didn't rename).
.clicked
is an internal signal that is triggered when someone clicks on a QButton
. We connect this signal to a handler using .connect(self.calculate_tax)
. This means that every time the button is pressed and the .clicked
signal fires, the connected method self.calculate_tax
is called.
You can connect multiple target functions or methods (called slots in Qt terminology) to a signal, and they will all be called when the signal fires.
We haven’t written the target method self.calculate_tax
yet, so let's do it now. In the MyWindow
class, add the method with the following code:
def calculate_tax(self):
price = Decimal(self.price_box.text())
tax = Decimal(self.tax_rate.value())
total_price = price + ((tax / 100) * price)
total_price_string = "The total price with tax is: {:.2f}".format(total_price)
self.results_output.setText(total_price_string)
We're using the Python builtin type Decimal for our calculations, as it prevents rounding errors for fixed-point currency calculations. It otherwise works exactly like float
, which you should already be familiar with. To use Decimal
we need to import it at the top of the file, by adding the following —
from decimal import Decimal
Save the file, and run it again from the command line. You should now have a fully functioning application!
python3 tax.py
Simple Tax Calculator — Running, with logic
Let's step through the code line by line to see what it is doing.
We have to do two things: Read the price box, read the tax box, and calculate the final price. Remember, we will call the objects by the names we gave them in Qt Creator (which is why it's a good idea to rename them, as names like box1
become confusing fast!)
price = Decimal(self.price_box.text())
First we read our price_box
by calling .text()
. This method returns the current value of the QLineEdit
.
You don't have to remember these all these names yourself, just Google "Qt5 QLineEdit" to get to the documentation page, which lists all the available methods.
The read value is a string, so we convert it to an Decimal
and store it in a variable called price
.
Next, we read the tax rate from the QSpinBox widget named tax_rate
using the builtin method .value()
. This returns a float
which we convert to a Decimal
for accuracy.
tax = Decimal(self.tax_rate.value())
Now that we have both these values, we can calculate the final price using very complex maths:
total_price = price + ((tax / 100) * price)
total_price_string = "The total price with tax is: {:.2f}".format(total_price)
self.results_output.setText(total_price_string)
We create a string with our final price, rounding the result down to 2 decimal places. Finally we output it to our QLabel
widget results_output
, replacing the text there (initially empty).
The complete code is shown below.
import sys
from decimal import Decimal
from PyQt5 import QtCore, QtGui, QtWidgets, uic
qtCreatorFile = "mainwindow.ui" # Enter file here.
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorFile)
class MyWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
Ui_MainWindow.__init__(self)
self.setupUi(self)
self.calc_tax_button.clicked.connect(self.calculate_tax)
def calculate_tax(self):
price = Decimal(self.price_box.text())
tax = Decimal(self.tax_rate.value())
total_price = price + ((tax / 100) * price)
total_price_string = "The total price with tax is: {:.2f}".format(total_price)
self.results_output.setText(total_price_string)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
So, we've successfully made a simple PyQt5 application using Qt Creator and implemented the logic with Python! If you want to learn more about PyQt check out the Getting started with PyQt5 course.
PyQt6 Crash Course — a new tutorial in your Inbox every day
Beginner-focused crash course explaining the basics with hands-on examples.