I am trying to make a mailbox checker with imap
lib, it work pretty fine with python, queue and multithread without gui.
But when I try to put a gui, every fonction i made, make the gui freeze until finish .
I tried many thing from various doc(add qthread, signal, cursorr etcc) and tutorials none worked for me .
Can someone help me to understand how to set or append a text to a QtextEdit while running a function coz it work only after finish .
Here is my code :
class Checker(QtCore.QThread):
signal = QtCore.pyqtSignal(object)
def __init__(self, lignesmailtocheck):
QtCore.QThread.__init__(self)
self.lignesmailtocheck = lignesmailtocheck
def run(self):
lignemailtocheck = self.lignesmailtocheck.strip()
maillo, passo = lignemailtocheck.split(":",1)
debmail, finmail = maillo.split("@",1)
setimap =["oultook.com:imap-mail.outlook.com", "gmail.com:imap.gmail.com"]
for lignesimaptocheck in sorted(setimap):
ligneimaptocheck = lignesimaptocheck.strip()
fai, imap = ligneimaptocheck.split(":",1)
if finmail == fai:
passo0 = passo.rstrip()
try :
mail = imaplib.IMAP4_SSL(imap)
mail.login(maillo, passo)
mailboxok = open("MailBoxOk.txt", "a+", encoding='utf-8', errors='ignore')
mailboxok.write(maillo+":"+passo+"\n")
mailboxok.close()
totaly = maillo+":"+passo0+":"+imap
print(maillo+":"+passo+"\n")
self.send_text.emit(totaly)
time.sleep(1)
except imaplib.IMAP4.error:
print ("LOGIN FAILED!!! ")
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 300)
self.pushButton = QtWidgets.QPushButton(Form)
self.pushButton.setGeometry(QtCore.QRect(150, 210, 75, 23))
self.pushButton.setObjectName("pushButton")
self.pushButton.clicked.connect(self.gogogo)
self.openliste = QtWidgets.QToolButton(Form)
self.openliste.setGeometry(QtCore.QRect(40, 110, 71, 21))
self.openliste.setObjectName("openliste")
self.textEdit = QtWidgets.QTextEdit(Form)
self.textEdit.setGeometry(QtCore.QRect(170, 50, 201, 121))
self.textEdit.setObjectName("textEdit")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(10, 260, 381, 23))
self.progressBar.setValue(0)
self.progressBar.setObjectName("progressBar")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Form"))
self.pushButton.setText(_translate("Form", "PushButton"))
self.openliste.setText(_translate("Form", "..."))
def gogogo(self):
mailtocheck = open('File/toCheck.txt', 'r', encoding='utf-8', errors='ignore').readlines()
setmailtocheck = set(mailtocheck)
for lignesmailtocheck in sorted(setmailtocheck):
checker = Checker(lignesmailtocheck)
thread = QThread()
checker.moveToThread(thread)
# connections after move so cross-thread:
thread.started.connect(checker.run)
checker.signal.connect(self.checkedok)
thread.start()
def checkedok(self, data):
print(data)
self.textEdit.append(data)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
Since there are often questions about using
QThread
in PyQt, similar to yours, here is an example that shows how to correctly use threads in PyQt. I'm hoping it can be useful as a goto-answer for similar questions so I spent a bit more time than usual preparing this.The example creates a number of worker objects that execute in non-main threads and communicate with the main (ie GUI) thread via Qt's asynchronous signals.
The main concepts necessary to understand multi-thread programming in PyQt are the following:
QThread
, and its event loop is managed by that thread.processEvents()
on theQApplication
instance. This will allow the QThread to process events, and hence to call slots in response to async signals from the GUI. Note thatQApplication.instance().processEvents()
seems to callprocessEvents()
on every thread, if this is not desired thenQThread.currentThread().processEvents()
is a valid alternative.QThread.quit()
does not immediately quit its event loop: it must wait for currently executing slot (if any) to return. Hence once a thread is told to quit, you must wait() on it. So aborting a worker thread usually involves signaling it (via a custom signal) to stop whatever it is doing: this requires a custom signal on a GUI object, a connection of that signal to a worker slot, and worker work method must call thread'sprocessEvents()
to allow the emitted signal to reach the slot while doing work.Sorry for late answer but it is a technique that can solve similar problems.
The problem is clear. The GUI freezes because its thread has to do another job. An abstracted(from the PyQt point) solution is given below:
Example Code:
self.timer.timeout.connect registers the callback function.
I can't test because setimap is not available on my system. I renamed
CheckerThread
toChecker
since it is no longer a thread (it just "lives" in a thread):Then just replace the contents of the loop in
gogogo(self)
with this:It is almost always a good idea to decorate slots with
pyqtSlot
so bothrun
andcheckedok
should be thus decorated.The SO answer about Qt threads is quite handy to remind yourself of details (note however that it uses old-style connections -- you have to translate C++
connect( sender, SIGNAL(sig), receiver, SLOT(slot));
to PyQt5sender.sig.connect(receiver.slot)
).