NKnuelle
NKnuelle

Reputation: 282

PySide2 use QProgressBar as signal argument

I am trying to solve a problem with PySide2, QThread and the Signal/Slot mechanism.

What I want to achieve is updating a specific progressbar which is passed as reference via signal/slot mechanism.

The problem is the call of self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, value) in my ProgressBarThread class.

The program stops after emitting the signal with a QProgressBar and an int value as arguments and causes a Seg Fault. If I just pass an int value to the signal and call emit in my ProgressBarThread everything is working fine.

Is it just not possible to pass an Object via Signal/Slot mechanism?

(Reduced) Code Samples:

GUISignal.py

from PySide2.QtCore import QObject, Signal
from PySide2.QtWidgets import QProgressBar

class GUISignal(QObject):
    signal_progress_value = Signal(QProgressBar, int)

main.py

# File: main.py
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QPushButton
from PySide2.QtCore import QFile
from config.configManager import ConfigManager
from layout.srcConfigLayout import SrcConfigLayout
from layout.ingestLayout import IngestLayout

if __name__ == "__main__":
    app = QApplication(sys.argv)

    ui_file = QFile("main_window.ui")
    ui_file.open(QFile.ReadOnly)

    loader = QUiLoader()
    window = loader.load(ui_file)
    ui_file.close()
    window.show()

    src_config_layout = SrcConfigLayout(window)
    ingestLayout = IngestLayout(window)
    src_config_layout.load_config()
    ingestLayout.load_config()

    sys.exit(app.exec_())

ingestLayout.py

from PySide2.QtWidgets import QTableWidget, QTableWidgetItem, QPushButton, QProgressBar
from PySide2.QtCore import QObject, Slot

from util.ingestManager import IngestManager
from config.configManager import ConfigManager
from util.progressBarThread import ProgressBarThread

from functools import partial

class IngestLayout(QObject):    

    def __init__(self, parent):
        super().__init__()
        self.parent = parent

    def handleIngestClicked(self, srcpath, destpath, progressbar):
        ingestManager = IngestManager()
        ingestManager.copy_to_destination(self, srcpath, destpath)
        progressThread = ProgressBarThread(srcpath, destpath, progressbar, self.parent)
        progressThread.gui_connection.signal_progress_value.connect(self.updateProgressbar)
        progressThread.start()

    @Slot(QProgressBar, int)
    def updateProgressbar(self, progressbar: QProgressBar, value: int):
        pass
        # progressbar.setValue(value)

progressBarThread.py

from PySide2.QtCore import QThread
import os
import threading
import time
from signals.GUISignal import GUISignal

class ProgressBarThread(QThread):
    def __init__(self, srcpath, destpath, progressbar, parent):
        super(ProgressBarThread, self).__init__(parent)
        self.gui_connection = GUISignal()
        self.srcpath = srcpath
        self.destpath = destpath
        self.src_size = self.size(srcpath)
        self.progressbar = progressbar

    def run(self):
        self.periodically_compare_folder_size()

    def periodically_compare_folder_size(self):
        dest_size = self.size(self.destpath)
        while self.src_size > dest_size:
            value = self.calc_progress(self.src_size, dest_size)
            self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, value)
            dest_size = self.size(self.destpath)
            time.sleep(2)
        else:
            self.gui_connection.signal_progress_value.emit(lambda: self.progressbar, 100)
            print(100)
            return

    def size(self, path, *, follow_symlinks=False):
        try:
            with os.scandir(path) as it:
                return sum(self.size(entry, follow_symlinks=follow_symlinks) for entry in it)
        except NotADirectoryError:
            return os.stat(path, follow_symlinks=follow_symlinks).st_size

    def calc_progress(self, src_size, dest_size):
        return dest_size / src_size * 100

The problem is that the program stops after emitting the signal with a QProgressBar and a value as arguments and causes a Seg Fault. If I just pass an int value to the signal and call emit in my ProgressBarThread everything is working fine.

Is it just not possible to pass Object via Signal/Slot mechanism?

Upvotes: 1

Views: 467

Answers (2)

NKnuelle
NKnuelle

Reputation: 282

SOLVED: The problem was the lambda function in the emit function inside the QThread class. It seems like the object was not passed by the lambda function. Just using .emit(self,progressbar, 100) is working.

Upvotes: 0

eyllanesc
eyllanesc

Reputation: 244282

It is not necessary for the signal to send as QProgressBar dates, in addition to the GUI is not thread-safe, on the other hand it is not necessary to use a lambda.

Considering the above, the solution is:

class GUISignal(QObject):
    signal_progress_value = Signal(int)
class ProgressBarThread(QThread):
    def __init__(self, srcpath, destpath, parent):
        super(ProgressBarThread, self).__init__(parent)
        self.gui_connection = GUISignal()
        self.srcpath = srcpath
        self.destpath = destpath
        self.src_size = self.size(srcpath)

    def run(self):
        self.periodically_compare_folder_size()

    def periodically_compare_folder_size(self):
        dest_size = self.size(self.destpath)
        while self.src_size > dest_size:
            value = self.calc_progress(self.src_size, dest_size)
            self.gui_connection.signal_progress_value.emit(value)
            dest_size = self.size(self.destpath)
            time.sleep(2)
        else:
            self.gui_connection.signal_progress_value.emit(100)
            print(100)
            return
    # ...
def handleIngestClicked(self, srcpath, destpath, progressbar):
    ingestManager = IngestManager()
    ingestManager.copy_to_destination(self, srcpath, destpath)
    progressThread = ProgressBarThread(srcpath, destpath, self.parent)
    progressThread.gui_connection.signal_progress_value.connect(progressbar.setValue)
    progressThread.start()

Update:

In the previous part of my answer I separate the GUI from the business logic as this will have a more scalable SW. But the OP seems to not want that so the simple solution is not to use the lambda method since it is unnecessary:

self.gui_connection.signal_progress_value.emit(self.progressbar, value)
self.gui_connection.signal_progress_value.emit(self.progressbar, 100)

Upvotes: 3

Related Questions