top

Getting Started GUI`s With Python. PyQt. QThread Class

IntroductionPyQt — a powerful framework for creating applications with Graphical User Interfaces (GUI). These python bindings make the GUI`s simple and fast. 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 frameworkThis basic tutorial on PyQt QThreading will demonstrate a simple example of implementing a GUI based on PyQt5 for communication with some services. To do this, we should know how to use the QThread class with PyQt5 framework. The standard Python library has a threading package, which is also good, but for PyQt GUI will be 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 learn how to create the main GUI window with PyQt QThread class: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 classNow 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 update our text field with scraping 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 stdlib 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 time module from python stdlib:import timeadd QThread class which will emit text value from source line edit every second and append to a 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 function of the emitting signals between thread and app and starting 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 will also set a button that started this thread 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:         passWe 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 emits a signal to the application. We can construct 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 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 of this video:For this example, the first approach (two-way communication) will be used for better understanding working structure of the QThread class.Additional functionalityWe 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, and 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 beautifulsoup4Importing 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 the tag <a> from the source page. And now thread emits 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 are 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:Completed qthread_app.py file available on GitHub:ConclusionThis simple example of building and maintaining  a responsive GUI using PyQt QThread with Python can be a good demonstration of the launch of flexible and fast-paced Python GUI`s. Usage of this opportunities is very wide. Python as a real-time system always requires solving problems associated with threads, one-time running processes, and multiprocessing.
Rated 4.0/5 based on 31 customer reviews
Normal Mode Dark Mode

Getting Started GUI`s With Python. PyQt. QThread Class

Volodymyr Kirichinets
Blog
20th Apr, 2018
Getting Started GUI`s With Python. PyQt. QThread Class

Introduction

PyQt — a powerful framework for creating applications with Graphical User Interfaces (GUI). These python bindings make the GUI`s simple and fast. 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.

Getting Started GUI`s With Python.


Using the QThread class of the PyQt framework

This basic tutorial on PyQt QThreading will demonstrate a simple example of implementing a GUI based on PyQt5 for communication with some services. To do this, we should know how to use the QThread class with PyQt5 framework. The standard Python library has a threading package, which is also good, but for PyQt GUI will be 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 learn how to create the main GUI window with PyQt QThread class:

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 of the PyQt framework


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 update our text field with scraping 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 stdlib 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 time module from python stdlib:

import time

add QThread class which will emit text value from source line edit every second and append to a 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 function of the emitting signals between thread and app and starting 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 will also set a button that started this thread 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:

QThread classOf 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 emits a signal to the application. We can construct 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 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 of this video:

QThread classFor this example, the first approach (two-way communication) will be used for better understanding 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, and 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 the tag <a> from the source page. And now thread emits 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 are 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:

 qthread_appCompleted qthread_app.py file available on GitHub:

Conclusion

This simple example of building and maintaining  a responsive GUI using PyQt QThread with Python can be a good demonstration of the launch of flexible and fast-paced Python GUI`s. Usage of this opportunities is very wide. Python as a real-time system always requires solving problems associated with threads, one-time running processes, and multiprocessing.

Volodymyr

Volodymyr Kirichinets

Blog author
Python-based software developer

Leave a Reply

Your email address will not be published. Required fields are marked *

SUBSCRIBE OUR BLOG

Follow Us On

Share on

other Blogs

20% Discount