Getting started GUIs with Python. PyQt. QThread class.

Volodymyr Kirichinets
7 min readApr 13, 2018

The original PDF Book, from original Author, with full package of examples for $3:

Introduction

PyQt — a powerful framework for creating applications with Graphical User Interfaces (GUI). But what about the speed of the application? When using apps with low weight problems do not arise, but when the application grows by its size and functionality — the speed of operations usually falls. The solution to this problem is the splitting of the application tasks into many subtasks or creating some subprocesses, using multiprocessing, creating pseudo-independent threads to perform these tasks. All of these are available in the standard library of the Python programming language. Different applications and their tasks have different approaches to the implementation of these tools, and the solution of problems depends on application structures.

Using the QThread class of the PyQt framework

This article will demonstrate a simple example of implementing a GUI based on PyQt5 for communication with some services. To do this, we will use the QThread class from the PyQt5 framework. The standard Python library has a threading package, which is also good, but for PyQt GUI will be it is better to use the QThread class because this class gives us the ability to send and emit
signals between threads and the main application. We will also use tools to obtain information from the source pages and display every few seconds in the GUI application. As a working environment, of course, there will be Anaconda, because I can not find the most powerful or alternative set of python tools at this time. Let’s create the main GUI window:

from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal

font_but = QtGui.QFont()
font_but.setFamily("Segoe UI Symbol")
font_but.setPointSize(10)
font_but.setWeight(95)


class PushBut1(QtWidgets.QPushButton):

def __init__(self, parent=None):
super(PushBut1, self).__init__(parent)
self.setMouseTracking(True)
self.setStyleSheet("margin: 1px; padding: 7px;
background- color: rgba(1,255,255,100);
color: rgba(0,190,255,255);
border-style: solid;
border-radius: 3px; border-width: 0.5px;
border-color: rgba(127,127,255,255);")

def enterEvent(self, event):
if self.isEnabled() is True:
self.setStyleSheet("margin: 1px; padding: 7px;
background-color:
rgba(1,255,255,100);
color: rgba(0,230,255,255);
border-style: solid;
border-radius: 3px;
border-width: 0.5px;
border-color: rgba(0,230,255,255);")
if self.isEnabled() is False:
self.setStyleSheet("margin: 1px; padding: 7px;
background-color:
rgba(1,255,255,100);
color: rgba(0,190,255,255);
border-style: solid;
border-radius: 3px;
border-width: 0.5px;
border-color:
rgba(127,127,255,255);")

def leaveEvent(self, event):
self.setStyleSheet("margin: 1px; padding: 7px;
background-color: rgba(1,255,255,100);
color: rgba(0,190,255,255);
border-style: solid;
border-radius: 3px; border-width: 0.5px;
border-color: rgba(127,127,255,255);")


class QthreadApp(QtWidgets.QWidget):
sig = pyqtSignal(str) def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.setWindowTitle("QThread Application")
self.setWindowIcon(QtGui.QIcon("Path/to/image/file.png"))
self.setMinimumWidth(resolution.width() / 3)
self.setMinimumHeight(resolution.height() / 1.5)
self.setStyleSheet("QWidget {
background-color: rgba(0,41,59,255);}
QScrollBar:horizontal {width: 1px;
height: 1px;
background-color: rgba(0,41,59,255);}
QScrollBar:vertical {width: 1px;
height: 1px;
background-color: rgba(0,41,59,255);}")
self.linef = QtWidgets.QLineEdit(self)
self.linef.setPlaceholderText("Connect to...")
self.linef.setStyleSheet("margin: 1px; padding: 7px;
background-color:
rgba(0,255,255,100);
color: rgba(0,190,255,255);
border-style: solid;
border-radius: 3px;
border-width: 0.5px;
border-color:
rgba(0,140,255,255);")
self.textf = QtWidgets.QTextEdit(self)
self.textf.setPlaceholderText("Results...")
self.textf.setStyleSheet("margin: 1px; padding: 7px;
background-color:
rgba(0,255,255,100);
color: rgba(0,190,255,255);
border-style: solid;
border-radius: 3px;
border-width: 0.5px;
border-color:
rgba(0,140,255,255);")
self.but1 = PushBut1(self)
self.but1.setText("⯈")
self.but1.setFixedWidth(72)
self.but1.setFont(font_but)
self.but2 = PushBut1(self)
self.but2.setText("⯀")
self.but2.setFixedWidth(72)
self.but2.setFont(font_but)
self.grid1 = QtWidgets.QGridLayout()
self.grid1.addWidget(self.linef, 0, 0, 1, 12)
self.grid1.addWidget(self.but1, 0, 12, 1, 1)
self.grid1.addWidget(self.but2, 0, 13, 1, 1)
self.grid1.addWidget(self.textf, 1, 0, 13, 14)
self.grid1.setContentsMargins(7, 7, 7, 7)
self.setLayout(self.grid1)


if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
desktop = QtWidgets.QApplication.desktop()
resolution = desktop.availableGeometry()
myapp = QthreadApp()
myapp.setWindowOpacity(0.95)
myapp.show()
myapp.move(resolution.center() - myapp.rect().center())
sys.exit(app.exec_())
else:
desktop = QtWidgets.QApplication.desktop()
resolution = desktop.availableGeometry()

the application window looks like this:

QThread class

Now we add the QThread class to our application. Related to PyQt, QThread class is commonly used for splitting of tasks into multiple threads to increase the speed of the GUI application, because a large number of tasks in one thread make the application slow and frozen. This thread will be updating our text field with scrapping info from the source that is signed in the line edit field. As described above, the greatest benefit between the PyQt QThread class and the python threading module from the standard library is the support of sending and emitting signals. These signals can be a string with text, lists with variables, tuples, integers, other python types. You can change the color of these apps, insert or set or append texts, draw, paint and have many other features to create GUI application fast and flexible. Let`s add the time module from the Python standard library:

import time

add QThread class which will emit text value from source line edit every second and append to the text field (as a test for the application functionality):

class QThread1(QtCore.QThread):

sig1 = pyqtSignal(str)

def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)

def on_source(self, lineftxt):
self.source_txt = lineftxt

def run(self):
self.running = True
while self.running:
self.sig1.emit(self.source_txt)
time.sleep(1)

Adding the function of the emitting signals between thread and app and start thread:

def on_but1(self):
self.textf.clear()
lineftxt = self.linef.text()
self.thread1 = QThread1()
self.sig.connect(self.thread1.on_source)
self.sig.emit(lineftxt)
self.thread1.start()
self.thread1.sig1.connect(self.on_info)
self.but1.setEnabled(False)

We’ll get the text from LineEdit, define the thread, connect to the thread from the application class, emit this text, start a thread and emit a text, launch a function that will add emitted text from the thread to the text field of the app. We also set a button that started this thread is in disabled mode. Function for adding text to the text field:

def on_info(self, info)
self.textf.append(str(info))

The second button will stop the thread and, after a short pause, set the thread start button to enable mode again. Function for this:

def on_but2(self):
try:
self.thread1.running = False
time.sleep(2)
self.but1.setEnabled(True)
except:
pass

We use try/except construction, because the thread may not be running when we press this button or something other. This construction can be changed to another, for example, we can verify that this thread works or not, or if some event occurs: if not self.thread.isRunning().

And connecting buttons to functions with adding to the bottom of the __init__ function of the main QthreadApp class this lines:

self.but1.clicked.connect(self.on_but1)
self.but2.clicked.connect(self.on_but2)

A video with a working application demonstrates this:

Of course, there are also other ways to create a connection between the GUI application and the threads. This construction is based on two-way communication when an application emits a signal to the thread and the thread emit a signal to the application. We can construct a one-way connection. To do this, let’s change the function to start the thread:

def on_but1(self):
self.textf.clear()
global source_txt
source_txt = self.linef.text()

self.thread1 = QThread1()
self.thread1.start()
self.thread1.sig1.connect(self.on_info)
self.but1.setEnabled(False)

source_txt is now not a QThread class variable, now it`s a global variable, available from any place of code, and can be used in various cases. And change the QThread class:

class QThread1(QtCore.QThread):

sig1 = pyqtSignal(str)

def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)

def run(self):
self.running = True
while self.running:
self.sig1.emit(source_txt)
time.sleep(1)

Maybe this approach is better? Your decision is what you will use.

The result we can see on this video:

For this example, the first approach (two-way communication) will be used for a better understanding of the working structure of the QThread class.

Additional functionality

We need to add more features to this application. For this, we add some functionality to the app. The content of the QThread class startup function will be changed to the code to obtain some information from a page from the source from the edit field of the line. To solve this task we need to have additional libraries, for example, BeautifulSoup. Install this with pip — if
does not exist:

> pip install beautifulsoup4

Importing additional tools:

from http.client import HTTPSConnection
from bs4 import BeautifulSoup

Python standard library module http.client for connection to the source, BeautifulSoup for HTML parsing. Let`s change our QThread class:

class QThread1(QtCore.QThread):

sig1 = pyqtSignal(str, int, int)

def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)

def on_source(self, lineftxt):
self.source_txt = lineftxt

def run(self):
self.running = True
while self.running:
try:
conn = HTTPSConnection(self.source_txt)
conn.request('GET', "/")
req = conn.getresponse()
page = req.read().decode('utf-8')
bs = BeautifulSoup(page, 'html.parser')
links = bs.find_all('a')
l = len(links)
for i in range(0, l):
if self.running is True:
self.sig1.emit(str(links[i]), i, l)
time.sleep(1.5)
except Exception as err:
self.sig1.emit(str(err), 0, 1)

We added HTTPSConnection there to create a connection to the source entered in the line edit field as the domain name. Creating bs4(BeautifulSoup) construction, where we get all links with tag <a>
from the source page. And now thread emitting three values. Now we need to change the function that receives the emitted signal:

def on_info(self, info, i, l):
if i == l-1:
self.textf.clear()
self.textf.append(str(info))
else:
self.textf.append(str(info))

This function adds text to the text field when a signal is sent from the thread, and if the amount signals equal to the length of links from the page, this text field will be clear, the last value adds and starts connecting to the page and emits signals with updated values again. Video with the result below:

The completed qthread_app.py file available on GitHub: https://gist.github.com/WEBMAMOFFICE/fea8e52c8105453628c0c2c648fe618f

Conclusion

This simple example of building a PyQt GUI application with QThread class can be a good demonstration of the launch of flexible and fast python based GUI`s. The usage of these opportunities is very wide. Python as a real-time system always requires solving problems associated with threads, one-time running processes, multiprocessing.

--

--