Beginners guide to learning GUI programming using PyQT/PySide(Part-2)

Paul Art
8 min readOct 26, 2021

Table of contents.

  1. Recap.
  2. Styling.
  3. Saving GUI states.
  4. Sample Todo App.
  5. Debugging application.
  6. Advanced tutorial.
  7. Further reading.

Recap:

In part-1, we made a small application, which made use of a signal-slot mechanism to use the button.

Styling:

As of now, we have a grey background, which doesn’t seem to be interesting.

We can change the color of the application in two different ways.

The first way is to use the palette (think of artist’s palette) and the second way (much easier way) would be to use stylesheets.

First Way:

I will not be going into detail about Palettes as a more modern approach is to use stylesheets provided by Qt. The Qt’s stylesheets will work for most cases if correctly used, but in case it doesn’t work you can try the QPalette as shown below.

Using QPalette example:

first, we import QPalette from QtGui.

You then create an instance of QPalette() either by using Qpalette() or by widget.palette() which returns the widget’s palette, useful if you need to preserve the previous palette.

Now you set the palette’s color using QPalette’s setColor setColor is an overload function which means, that the function will be called according to the parameters the developer pass to the function.

The first overload function is setColor(QPalette.QColorGroup, QPalette.ColorRole, QtGui.QColor)

The second overload function is setColor(QPalette.colorRole, QtGui.QCOlor)

Both QPalette.QColorGroup and QPalette.ColorRole expects a constant.

The QColorGroup specifies the state color for example if you might want the label to have a different color if it's active and a different color if it's disabled (we won’t use this).

In the second argument, you specify the portion you want to be filled, for example, you might want a different foreground color and a different background color. (QPalette.Window-is for background color and QPalette.WindowText is for label text)

So the way you would specify that is

label_palette.setColor(QPalette.Active, QPalette.Window, QtGui.QColor("red"))

You can skip the first parameter and just use the second and third parameters as shown in the program.

Second Way (Stylesheets):

Stylesheets help you to separate your programs from style, they also help in providing consistency across all the platforms.

If you are already familiar with CSS, writing stylesheet’s are going to be very easy. In fact, Qt’s stylesheet was inspired by the CSS for HTML

Let's see an example.

remember: almost all the widgets inherit from QWidget and setStyleSheet is a common method among all its children

In the above example, we use setStyleSheet() method of the QWidget and pass string as an argument to set the stylesheet. Writing qss(qt stylesheet) is pretty straightforward. You will have to separate each property using a semi-colon as shown above.

But this can get tedious if you have many widgets. So it's better to store all the styling in a string and set the stylesheet on an application level, QApplication as shown below.

Here you will simply have to specify the widgets you want to apply a style too., for example. If you want all the instances of Label to have a black background, all you will have to do is specify QLabel{background-color: black;}

Note: Qt won’t raise any error if you make any mistake in stylesheet syntax. It simply wont apply style thereafter.

The :hover is simply a pseudo-state that tells Qt that it has to change the background color on hover. There is many more pseudo-state as stated in the Qt’s Documentation. (note: there shouldn’t be any space before and after the colon when specifying the pseudo-state, this is quite a common mistake many people make.)

more often developers write the stylesheets in a separate file and load the file and pass it to the QApplication().setStyleSheet()

for example save all the strings in the _style variable in a file called theme.qss and load it into your application using the open function provided by python.

Here is an example

theme.qss

QWidget{
background-color: #f5fbff;
}
QLabel{
color: black;
background-color: transparent;
font-size: 25px;
font-family: Times;
}
QPushButton{
color: white;
background-color: #3db1ff;
font-size: 20px;
font-family: Times;
border-radius: 10px;
padding: 5px;
}
QPushButton:hover{
background-color: #2a7ab0;
}

Now your python file:

Saving State:

In GUI programming we might want to save the state of the program, for example, you might want the user preferences to be saved and not change the preferences each time the application is opened. In GUI programming you will not save the widget itself, but just its states into a file such as text file, JSON file, etc. and to get to the same state back you will read the file and set the state accordingly.

For example, if a user has checked the Dark-theme check box(QCheckBox) you will save only if its state(checked or not checked) in a file. GUI widgets will provide a way to retrieve if a check box is checked or not, in PyQt5 you can tell if a check button is checked by using isChecked() the method of QCheckBox . Now that you have saved it, when reopening the app you can read the file and set the check box state to checked or not checked using the setChecked() method provided by QCheckBox.

Below is an example of saving the Checkbox state and button text. Type it out in your editor and run the code. (I have added comments where necessary)

Sample Todo App:

Now that we have our basics let's try to create a sample application to improve our knowledge.

Below is the design of the sample app we will be creating.

A sample application

Here are the widgets we would require.

  1. QFrame or QWidget
  2. QLabel
  3. QPushButton
  4. QLineEdit
  5. QTextEdit
  6. QScrollArea (if you want the todo list to be scrollable)
  7. And any layout manager you think is suitable.

How does the app work?

The user first enters the title, description, and date on the right side, and when the user presses create the todo item gets added to the scroll area(on the left side), and the LineEdit and TextEdit get cleared for the next entry. (optionally you can show description using the show more-less button)

Can you try to implement this yourself before moving ahead? Feel free to refer to any tutorials or documentation, as you will be required to refer to it most of the time.

Once you have tried to create the application you can then take a look at how I made the todo app, as the application is a little long I’ll link my GitHub page below.

Tutorials/PyQtBlog/programs/todoapp at master · PaulleDemon/Tutorials (github.com)

I have added comments where ever required. You can run the main.py to see the application

Debugging application:

You would have noticed by now, if you had made any errors you, don’t get any traceback, which makes it quite difficult to tell what the problem is.

So I suggest you run your program from your terminal, using the command, python mypyqtapp.py , if you have set up a virtual environment don’t forget to activate it. This way you will be able to see the traceback.

Advanced Tutorial:

Overriding some of the common methods:

Its a fairly common practice to override some of the common methods of QWidgets listed below to get custom behaviour.

  1. mousePressEvent
  2. mouseMoveEvent
  3. mouseReleaseEvent
  4. KeyPressEvent
  5. eventFilter
  6. paintEvent

I’ll be subclassing QLabel to show an example of getting custom behaviour., which will allow us to draw a rectangle on the widget using mouse movements and remove the rectangle if the escape key is pressed.

Custom signals:

Qt allows you to generate custom signals, for example, you might want to notify your program when the program has completed writing to the disk, or when a label is clicked(for whatever reason).

You can generate custom signals from any widget that is a subclass of QObject for example, QWidget , QLabel , QPushButton etc.

We will see an example of how to generate a custom signal on clicking a label.

To create a custom signal you will have to use pyqtSignal() you can pass in any number of parameters, all of them should be only the type of the argument (eg: int, str, bool, etc) not a reference name. You will have to declare this in class level, not instance level

QThreads.

This is an important concept in any GUI programming. If you run blocking tasks such as writing very long files, socket programming, etc. Your GUI will freeze and you will get a window not responding error depending on your OS.

This is because the events have to be processed continuously. You are not supposed to have another infinite while loop or time.sleep() in the main thread, as it blocks the GUI’s event loop. One way to overcome that is to make use of threading.

Note: you should never update GUI elements from a child thread as PyQt widgets are not thread-safe, you might get undesirable results if you do so.

While you can use the threading module provided by Python, I suggest you make use of QThreads as this allows you to make use of the signal-slot mechanism if required.

To demonstrate the use of QThread we will make a simple countdown timer, while there are other ways to do so, we will make our own timer to demonstrate the use of QThread.

We will first subclass QThread and implement the run method.

In the above program the countDownUpdated will be emitted whenever the count down variable decreases, we will use this signal to update the timer on our label. Note: we are not updating the label from a child thread but from the main thread itself.

Drawing on Canvas:

You will usually not draw 2d diagrams on labels or buttons or other widgets if you have a lot of custom drawings it is recommended to put them on a canvas.

The Canvas in PyQt is QGraphicsScene and the board that holds the canvas is the QGraphicsView

You will be able to place QGraphicsItems like QGraphicsLineItem (with which you will be able to display line on our scene), QGraphicsRectItem (draws a rectangle in the scene) etc.

In the above code I show you two ways to add items to the scene, the first way is to create a QGraphicsLineItem object yourself and add to the scene, the second way is to use addLine or addRect provided by QGraphicsScene.

Further Reading:

Reading the Qt’s C++ documentation.

The most important sections of the documentation are the public function and the Signals section

The public function section contains the method that you can access and use.

I’ll be taking QWidget documentation and explaining to you how to use it.

If the method contains a parameter you need to look at the type(eg: int, str, etc) of the parameter that it's expecting. For example, setPalette(const QPalette &) expects a QPalette so you need to create an instance of QPalette and pass it to the set palette.

There can also be overload methods for example setMaximumSize(const QSize &) and setMaximumSize(int maxw, int maxh) If you pass a Qsize as an argument to the setMaximumSize the first method will be called if you pass two integers then the second method will be called, though they both do the same thing.

If you want to know the signals available take a look at Signals section.

You shouldn’t stop here, if you want to get good at PyQt, the best way is to try and create small applications, like todo app, Text editor, music player, etc., you will often be stuck, so don’t hesitate to seek help from Q/A forums such as Stack Overflow, Reddit, etc.

PyQt already comes with many widgets, before you make a custom widget make sure PyQt doesn’t provide it.

Qt also has very good documentation, a quick google search on QWidget, QPushButton, QLabel, etc. should bring up specific documentation that you can refer to.

references used:

Qt’s Offical documentation. https://doc.qt.io/

--

--

Paul Art

I open-source enthusiast, I have written and published many opensource libraries many of which are being used by over 30,000 developers.