4

I'm working on a GUI for calling a function 'my_function' when the button 'my_button' is pushed.

This function processes data iteratively. It contains a 'for' loop, and at each iteration it prints out a message that shows the progress of my function. I would like this prints to be displayed in my GUI (in this example in a textEdit widget), in real time. How could I do that?

I would like to make it clear that I need real time display. I found some solutions online, but all prints only appear when the function finishes execution. I need the prints to display in real time in order to appreciate the function progress. Thanks in advance for your tips!

Here's a minimal reproductible example (I obtained the template via qt designer):

class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(163, 225) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.formLayout = QtWidgets.QFormLayout(self.centralwidget) self.formLayout.setObjectName("formLayout") self.my_button = QtWidgets.QPushButton(self.centralwidget) self.my_button.setObjectName("my_button") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.my_button) self.textEdit = QtWidgets.QTextEdit(self.centralwidget) self.textEdit.setObjectName("textEdit") self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.textEdit) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 163, 22)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) self.my_button.clicked.connect(self.my_function) def my_function(self): for idx in range(10): print('Executing iteration '+str(idx)+' ...') time.sleep(1) # replaces time-consuming task print('Finished!') def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.my_button.setText(_translate("MainWindow", "Execute")) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) MainWindow = QtWidgets.QMainWindow() ui = Ui_MainWindow() ui.setupUi(MainWindow) MainWindow.show() sys.exit(app.exec_()) 
3
  • What do you use the time.sleep(1) for? Do you use it for a delay or is it to replace a time-consuming task? Commented Jan 14, 2020 at 13:39
  • He put it in as a minimal example, so I'd imagine it's replacing a heavy task. Shouldn't matter either way for the problem though. Commented Jan 14, 2020 at 13:49
  • @eyllanesc yep, its for replacing a time-consuming task Commented Jan 14, 2020 at 14:18

1 Answer 1

3

Golden rule of Qt: You should not execute tasks that consume a lot of time in the main thread of the GUI since they block the event loop of Qt.

Considering the above, you must execute the time-consuming task in another thread and send the information to the GUI thread through signals.

On the other hand you should not modify the class generated by Qt Designer, instead create another class that inherits from the appropriate widget and is filled in with the initial class.

import threading import time from PyQt5 import QtCore, QtGui, QtWidgets class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(163, 225) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.formLayout = QtWidgets.QFormLayout(self.centralwidget) self.formLayout.setObjectName("formLayout") self.my_button = QtWidgets.QPushButton(self.centralwidget) self.my_button.setObjectName("my_button") self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.my_button) self.textEdit = QtWidgets.QTextEdit(self.centralwidget) self.textEdit.setObjectName("textEdit") self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.textEdit) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 163, 22)) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) self.statusbar = QtWidgets.QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) self.my_button.setText(_translate("MainWindow", "Execute")) class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): valueChanged = QtCore.pyqtSignal(int) def __init__(self, parent=None): super().__init__(parent) self.setupUi(self) self.valueChanged.connect(self.on_value_changed) self.my_button.clicked.connect(self.on_clicked) @QtCore.pyqtSlot() def on_clicked(self): threading.Thread(target=self.my_function, daemon=True).start() @QtCore.pyqtSlot(int) def on_value_changed(self, value): self.textEdit.append("Value: {}".format(value)) def my_function(self): for idx in range(10): self.valueChanged.emit(idx) print("Executing iteration " + str(idx) + " ...") time.sleep(1) # replaces time-consuming task print("Finished!") if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) w = MainWindow() w.show() sys.exit(app.exec_()) 
Sign up to request clarification or add additional context in comments.

4 Comments

Many thanks for your answer and advices. In my case, my_function calls a function defined in another package. The 'for' loop is actually happening somewhere else. If possible I would like to avoid editing this loop. In other words, is it possible to "listen" to the prints of the function, without having to add 'self.valueChanged.emit(idx)' in the loop?
@Manu My answer based on your MRE so I will not be able to tell you if it is possible or not in other cases that it does not cover. On the other hand if you cannot modify the for-loop then it is very likely that it cannot be done. In conclusion, if you have a different characteristic, then you must provide an appropriate MRE so that I can objectively indicate whether or not it can be done.
Thanks for your quick answer. Here below the new MRE
ok I'll create the followup as soon as my question limit lifts up

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.