Tuesday, November 10, 2020

Learn PyQt: Simple Sales Tax Calculator — Using Qt creator to create a simple GUI

This is an updated Qt5 version of Shantnu Tiwari's tutorial first seen here.

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:

  1. Qt Designer Form
  2. Main Window (other Widget types are available)
  3. Select a location to save the resulting .ui file
  4. Revision control (can skip this)
  5. Click Done to create the design.

The steps are shown visually below.

Qt Creator — Initial View Qt Creator — Initial View

Qt Creator — Start new design Qt Creator — Start new design

Qt Creator — Create a new Qt Designer Form Qt Creator — Create a new Qt Designer Form

Qt Creator — Select MainWindow for widget type Qt Creator — Select MainWindow for widget type

Qt Creator — Select Save location for the .ui file Qt Creator — Select Save location for the .ui file

Qt Creator — Project management / revision control 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 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 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 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 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 Qt Creator — Adding a Label

You can also make the text large and bold, as seen here:

Qt Creator — QLabel font properties 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 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 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 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 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 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 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 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 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 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 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 —

  1. Set the input box to right-aligned, which is more natural for numbers.
  2. Update the name of the Main Window windowTitle property (choose a good name).

Qt Designer — Setting alignment on an input Qt Designer — Setting alignment on an input

Qt Designer — Setting the windowTitle 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
<?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.

python
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic

qtcreator_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_())
python
import sys
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtUiTools import QUiLoader

qtcreator_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_().

python
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.

bash
python3 tax.py

You should see your UI appear, looking something not unlike the following —

Simple Tax Calculator — Skeleton UI 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:

python
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:

python
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 —

python
from decimal import Decimal

Save the file, and run it again from the command line. You should now have a fully functioning application!

bash
python3 tax.py

Simple Tax Calculator — Running, with logic 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!)

python
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.

python
tax = Decimal(self.tax_rate.value())

Now that we have both these values, we can calculate the final price using very complex maths:

python
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.

python
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.



from Planet Python
via read more

No comments:

Post a Comment

TestDriven.io: Working with Static and Media Files in Django

This article looks at how to work with static and media files in a Django project, locally and in production. from Planet Python via read...