0
votes

I am trying to emit a signal when a python thread finish running so when it is done, the emtited signal close the PYQT window that started the thread.

in the code see comments.

import sys
import threading
from PyQt4 import QtGui, QtCore

class MyThread(threading.Thread):
    """ docstring for MyThread
    """
    def __init__(self, job_done, settings):
        super(MyThread, self).__init__()
        self._Settings = settings

    def run(self):
        while not job_done.is_set():
            if not processSetting(self._Settings):
                raise Exception("Could not process settings")
            else:
                ## Emit Signal
                pass


class MyWindow(QtGui.QWidget):
    """docstring for MyWindow"""
    def __init__(self, settings, parent=None):
        super(MyWindow, self).__init__(parent)
        self._Settings = settings
        self._job_done = threading.Event()
        self._myThread = MyThread(self._job_done, self._Settings)
        ## catch the signal to close this window.

    def closeEvent(self, event):
        if self._myThread.isAlive():
            reply=QtGui.QMessageBox.question(self, "Are you sure to quit?","Settings are getting applied !!!",QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
            if reply==QtGui.QMessageBox.Yes:
                event.accept()
            else:
                event.ignore()


def processSettings():
    print "processSettings() Called"
    return True

def main():
    app = QtGui.QApplication(sys.argv)
    main = MyWindow()
    main.show()
    sys.exit(app.exec_())

in the above code i want to signal when processSttingts returns True and then MyWindow should close.

EDIT here is what I tried to do.

So what I am heading towards is to emit a signal if the processSettings returns True and close the QMainWindow MyWindow. import sys import threading from PyQt4 import QtGui, QtCore

def processSettings():
    print "processSettings() Called"
    return True


class MyThread(threading.Thread):
    """ docstring for MyThread
    """
    def __init__(self, settings):
        super(MyThread, self).__init__()
        self._Settings = settings
        self.signal = QtCore.SIGNAL("signal")
        self._job_done = threading.Event()

    def run(self):
        # while not job_done.is_set():
        print "in thread"
        if not processSettings():
            raise Exception("Could not process settings")
        else:
            QtCore.emit(self.signal, "hi from thread")

class MyWindow(QtGui.QMainWindow):
    """docstring for MyWindow"""
    def __init__(self, settings, parent=None):
        super(MyWindow, self).__init__(parent)
        self._Settings = settings
        self._myThread = MyThread(self._Settings)
        self._myThread.daemon = False
        self._myThread.start()
        self.connect(self._myThread, self._myThread.signal, self.testfunc)


    def closeEvent(self, event):
        if self._myThread.isAlive():
            reply=QtGui.QMessageBox.question(self, "Are you sure to quit?","Settings are getting applied !!!",QtGui.QMessageBox.Yes,QtGui.QMessageBox.No)
            if reply==QtGui.QMessageBox.Yes:
                event.accept()
            else:
                event.ignore()

    def testfunc(self, sigstr):
        """ Purpose of this function is to close this window"""
        print sigstr
        self.close()



def main():
    app = QtGui.QApplication(sys.argv)
    settings = {'test': True}
    wind = MyWindow(settings)
    wind.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    sys.exit(main()) 
2
Did you try to emit a signal ? QThreads are QObjects like others. I think you get it clear, you just need to do it.mguijarr
the problem is with which project I am working on I have to avoid QThreads.Ciasto piekarz
Did you try to put a QObject within your Python thread, emit from this QObject and connect to this QObject from MyWindow ?mguijarr
could you please show what you are trying to say I have updated with an edit above.. instead of QtCore i tried using self but that won't make sense since threading.Thread object doesn't have emit function, so now all I need is to emit the QtSignalCiasto piekarz

2 Answers

1
votes

You need to ensure that the solution is thread-safe, and directly connecting signals emitted from a python thread to a slot in QT thread is not thread safe. The SafeConnector class proposed by mguijarr in his answer to "Emit signal in standard python thread" may be useful. I successfully use a modified version of his solution that uses a pipe rather than a socket:

class SafeConnector(object):
    """Share between Python thread and a Qt thread.

    Qt thread calls :meth:`connect` and the python thread calls :meth:`emit`.
    The slot corresponding to the emitted signal will be called in Qt's
    thread.
    """
    def __init__(self):
        self._fh_read, self._fh_write = os.pipe()
        self._queue = Queue.Queue()
        self._qt_object = QtCore.QObject()
        self._notifier = QtCore.QSocketNotifier(self._fh_read,
                                                QtCore.QSocketNotifier.Read)
        self._notifier.activated.connect(self._recv)

    def close(self):
        del self._qt_object
        self._qt_object = None
        os.close(self._fh_read)
        os.close(self._fh_write)
        self._fh_read, self._fh_write = None, None

    def connect(self, signal, receiver):
        """Connect the signal to the specified receiver slot.

        :param signal: The signal to connected.
        :param receiver: The receiver slot for the signal.
        """
        QtCore.QObject.connect(self._qt_object, signal, receiver)

    def emit(self, signal, *args):
        """Emit a Qt signal from a python thread.

        All remaning args are passed to the signal.

        :param signal: The Qt signal to emit.
        """
        self._queue.put((signal, args))
        os.write(self._fh_write, '!')

    def _recv(self):
        """Receive the signal from the Queue in Qt's main thread."""
        os.read(self._fh_read, 1)
        signal, args = self._queue.get()
        self._qt_object.emit(signal, *args)

The example from mguijarr's post is valid using either implementation.

0
votes

Here is an example of the solution I proposed in the comments:

from PyQt4 import QtGui, QtCore
import threading
import time

class MyThread(threading.Thread):
    def __init__(self, *args):
        threading.Thread.__init__(self, *args)

        self.job_done = threading.Event()
        self.qt_object = QtCore.QObject()

    def end_job(self):
        self.job_done.set()

    def run(self):
        while not self.job_done.is_set():
            time.sleep(1)

        QtCore.QObject.emit(self.qt_object, QtCore.SIGNAL("job_done"))


th = MyThread()
th.start()

app = QtGui.QApplication([])

w = QtGui.QWidget()
btn = QtGui.QPushButton('click me to exit', w)
QtGui.QVBoxLayout(w)
w.layout().addWidget(btn)

def btn_clicked():
    th.end_job()

QtCore.QObject.connect(btn, QtCore.SIGNAL("clicked()"), btn_clicked)

QtCore.QObject.connect(th.qt_object, QtCore.SIGNAL("job_done"), w.close)

w.show()

app.exec_()