Home > Back-end >  PYQT: Issue when creating labels dynamically under layout using QThread
PYQT: Issue when creating labels dynamically under layout using QThread

Time:02-14

Have been working on project recently that includes creating labels labels on the fly based on the data coming from the API call. As Api call returns different data based on device config, thought it would be better to proceed with this approach. Instead of putting static labels and resetting them with each iteration. The code works without issue as long it is not running using QThread but when trying to run it using QThread getting this error for each label that trying to add dynamically Code block

self.vpn_label1 = QLabel('k')
self.vpn_label1.setObjectName('k')
self.vpn_label2 = QLabel('expired')
self.vpn_label2.setObjectName('k1')
self.grid.addWidget(self.vpn_label1, 0, 0)
self.grid.addWidget(self.vpn_label2, 0, 1)

QObject::setParent: Cannot set parent, new parent is in a different thread This is the sample code with code to add dynamic labels outside the QThreadpool function

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

import time
import traceback, sys


class WorkerSignals(QObject):
    finished = pyqtSignal()
    error = pyqtSignal(tuple)
    result = pyqtSignal(object)
    progress = pyqtSignal(float)


class Worker(QRunnable):
    """
    Worker thread

    Inherits from QRunnable to handler worker thread setup, signals and wrap-up.

    :param callback: The function callback to run on this worker thread. Supplied args and
                     kwargs will be passed through to the runner.
    :type callback: function
    :param args: Arguments to pass to the callback function
    :param kwargs: Keywords to pass to the callback function

    """

    def __init__(self, fn, *args, **kwargs):
        super(Worker, self).__init__()

        # Store constructor arguments (re-used for processing)
        self.fn = fn
        self.args = args
        self.kwargs = kwargs
        self.signals = WorkerSignals()

        # Add the callback to our kwargs
        self.kwargs['progress_callback'] = self.signals.progress

    @pyqtSlot()
    def run(self):
        """
        Initialise the runner function with passed args, kwargs.
        """

        # Retrieve args/kwargs here; and fire processing using them
        try:
            result = self.fn(*self.args, **self.kwargs)
        except:
            traceback.print_exc()
            exctype, value = sys.exc_info()[:2]
            self.signals.error.emit((exctype, value, traceback.format_exc()))
        else:
            self.signals.result.emit(result)  # Return the result of the processing
        finally:
            self.signals.finished.emit()  # Done



class MainWindow(QMainWindow):
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.vpn_label2 = None
        self.vpn_label1 = None
        self.n = None
        self.counter = 0

        layout = QVBoxLayout()

        self.l = QLabel("Start")
        b = QPushButton("DANGER!")
        b.pressed.connect(self.oh_no)
        self.grid = QGridLayout()

        layout.addWidget(self.l)
        layout.addWidget(b)
        layout.addLayout(self.grid)

        w = QWidget()
        w.setLayout(layout)

        self.vpn_label1 = QLabel('k') # <----------------- 
        self.vpn_label1.setObjectName('k') #<--------------
        self.vpn_label2 = QLabel('expired') #<------------
        self.vpn_label2.setObjectName('k1') #<-------------
        self.grid.addWidget(self.vpn_label1, 0, 0) #<--------------
        self.grid.addWidget(self.vpn_label2, 0, 1) #<-------------

        self.setCentralWidget(w)
        self.show()
        self.threadpool = QThreadPool()
        print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())

        self.timer = QTimer()
        self.timer.setInterval(1000)
        self.timer.timeout.connect(self.recurring_timer)
        self.timer.start()

    def progress_fn(self, n):
        print("%d%% done" % n)

    def execute_this_fn(self, progress_callback):

        self.n = 1
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)

        return "Done."

    def print_output(self, s):
        print(s)

    def thread_complete(self):
        print("THREAD COMPLETE!")

    def check_flow(self, val):
        self.l.setText("Counter: %d" % val)

    def oh_no(self):
        # Pass the function to execute
        worker = Worker(self.execute_this_fn) # Any other args, kwargs are passed to the run function
        worker.signals.result.connect(self.print_output)
        worker.signals.finished.connect(self.thread_complete)
        worker.signals.progress.connect(self.progress_fn)

        # Execute
        self.threadpool.start(worker)


    def recurring_timer(self):
        self.counter  =1
        # self.l.setText("Counter: %d" % self.counter)

if __name__ == '__main__':
    app = QApplication([])
    window = MainWindow()
    app.exec_()

The above code works without any issue. It can be seen in this screenshot where label with value 'k' and 'expired' are added to gridlayout in window Works without issue

But when putting the code block inside the threadpool function execute_this_fn it throws error and it does not show those labels added to grid in window and it can be seen in this screenshot, where label with value 'k' and 'expired' are missing from gridlayout and cannot be seen in window. Problem with QThread function

 def execute_this_fn(self, progress_callback):
        # for n in range(0, 5):
        #     time.sleep(1)
        #     progress_callback.emit(n*100/4)

        self.vpn_label1 = QLabel('k') #<------------
        self.vpn_label1.setObjectName('k') #<------------
        self.vpn_label2 = QLabel('expired') #<------------
        self.vpn_label2.setObjectName('k1') #<------------
        self.grid.addWidget(self.vpn_label1, 0, 0) #<------------
        self.grid.addWidget(self.vpn_label2, 0, 1) #<------------

        self.n = 1
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)
        self.n  = 1
        time.sleep(2)
        self.check_flow(self.n)
        progress_callback.emit(self.n * 100 / 4)

        return "Done."
QObject::setParent: Cannot set parent, new parent is in a different thread
QObject::setParent: Cannot set parent, new parent is in a different thread

This is just sample code but it illustrates the exact problem that getting. Can someone please assist in figuring out the issue and suggest how to overcome it. Would be really helpful and much appreciated.

CodePudding user response:

In Qt, you can only update the GUI from the main thread. You can trigger changes to the GUI from another thread by using signals and slots. You emit a signal from another thread, and the slot will be executed in the main thread.

In your example, the execute_this_fn method is executed in another thread, so you can't change the GUI from there. The calls to progress_callback.emit in execute_this_fn results in the progress_fn method being executed in the main thread. If you put these 6 lines in progress_fn you won't have any issues:

        self.vpn_label1 = QLabel('k') #<------------
        self.vpn_label1.setObjectName('k') #<------------
        self.vpn_label2 = QLabel('expired') #<------------
        self.vpn_label2.setObjectName('k1') #<------------
        self.grid.addWidget(self.vpn_label1, 0, 0) #<------------
        self.grid.addWidget(self.vpn_label2, 0, 1) #<------------

There is a lot to learn about both Qt and multithreaded programming before you stop tripping up. I would recommend you at least finish reading Martin Fitzpatrick's book/blogs, which is where your code has come from. The documentation and articles on the Qt website are very good and are essential reading. I've also found the videos released by KDAB to be exceptionally good.

CodePudding user response:

Gordon, For eg - this is one of the 5 functions functions that will be replacing the above mentioned lines and creating labels on the fly based on the available data

def ha_group(self):
        ha_detail = ha_details(self.firewall, self.fw_key)

        while self.ui.ha_grid.count():
            widget = item = self.ui.ha_grid.takeAt(0).widget()
            if widget is not None:
                widget.deleteLater()

        ha_label_count = 0
        for k, val in ha_detail.items():
            self.vpn_label1 = QLabel(k)
            self.vpn_label1.setObjectName(k)
            self.vpn_label2 = QLabel(val)
            self.vpn_label2.setObjectName(k   '1')
            self.ui.ha_grid.addWidget(self.vpn_label1, ha_label_count, 0, 1, 1)
            self.ui.ha_grid.addWidget(self.vpn_label2, ha_label_count, 1, 1, 1)
            ha_label_count  = 1  

when trying to put this function in progress function progress_fn, it does not work. Any suggestions friend.

  • Related