I am using QThreads to run a function in the background, but when I exit the GUI application, the QThread still continues to run.
There are examples about C++ but I do not know how to implement them in python
class PF35Thread(QtCore.QThread):
signalPF35 = pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
def run(self):
newcase = newcaseList[-1]
os.system('EAZ{0}(3,5).EAZ{0}(3,5).OUT'.format(newcase))
self.signalPF35.emit()
How do I terminate QThread when GUI closes?
I think that in your case the thread is only necessary because os.system() is blocking, but if you use QProcess it does a similar task and you do not need the thread, it also allows easier management with the Qt eventloop.
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
signalPF35 = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
button = QtWidgets.QPushButton("Start task", clicked=self.on_clicked)
self.setCentralWidget(button)
self._process = QtCore.QProcess(self)
self._process.finished.connect(self.on_finished)
#QtCore.pyqtSlot()
def on_clicked(self):
newcase = newcaseList[-1]
self._process.start('EAZ{0}(3,5).EAZ{0}(3,5).OUT'.format(newcase))
#QtCore.pyqtSlot()
def on_finished(self):
self.signalPF35.emit()
def closeEvent(self, event):
self._process.kill()
super(MainWindow, self).closeEvent(event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Related
I have got this problem. I´m trying to set text on a lineEdit object on pyqt4, then wait for a few seconds and changing the text of the same lineEdit. For this I´m using the time.sleep() function given on the python Time module. But my problem is that instead of setting the text, then waiting and finally rewrite the text on the lineEdit, it just waits the time it´s supposed to sleep and only shows the final text. My code is as follows:
from PyQt4 import QtGui
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
time.sleep(2)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.
You should probably use a QTimer and use it's timeout signal to schedule a signal for deferred delivery, or it's singleShot method.
For example (adapted your code to make it run without dependencies):
from PyQt4 import QtGui, QtCore
class Ventana(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.lineEdit = QtGui.QLineEdit(self)
self.button = QtGui.QPushButton('clickme', self)
self.layout().addWidget(self.lineEdit)
self.layout().addWidget(self.button)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
self.lineEdit.setText('Start')
QtCore.QTimer.singleShot(2000, lambda: self.lineEdit.setText('End'))
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
Also, take a look at the QThread sleep() function, it puts the current thread to sleep and allows other threads to run. https://doc.qt.io/qt-5/qthread.html#sleep
You can't use time.sleep here because that freezes the GUI thread, so the GUI will be completely frozen during this time.You can use QtTest module rather than time.sleep().
from PyQt4 import QtTest
QtTest.QTest.qWait(msecs)
So your code should look like:
from PyQt4 import QtGui,QtTest
from gui import *
class Ventana(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setupUi(self)
self.button.clicked.connect(self.testSleep)
def testSleep(self):
import time
self.lineEdit.setText('Start')
QtTest.QTest.qWait(2000)
self.lineEdit.setText('Stop')
def mainLoop(self, app ):
sys.exit( app.exec_())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Ventana()
window.show()
sys.exit(app.exec_())
I have a small program that does something, but i want to "switch modes", for now i press a key and an input prompts on the console, but to make it easier i want to make a window with pyqt6, the problem is that the window blocks or halts the main loop while it's open, i tried with threading/multiprocessing but i can't make it work.
import threading
from queue import Queue
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt
queue = Queue()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
label = QLabel("Change modes")
btn1 = QPushButton("MODE 1")
btn2 = QPushButton("MODE 2")
layout.addWidget(label)
layout.addWidget(btn1)
layout.addWidget(btn2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
btn1.clicked.connect(self.mode1)
btn2.clicked.connect(self.mode2)
self.show()
def mode1(self):
queue.put("mode1")
def mode2(self):
queue.put("mode2")
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
app.exec()
mode = "none"
while True:
_mode = queue.get()
if mode != _mode:
mode = _mode;
print(f"mode: {mode}")
# do stuff here
the only way that the while loop executes is when i close the window.
Traditional Python multiprocessing/multithreading libraries such as multiprocessing and threading do not work well with Qt-like (PyQt and PySide) graphical programs. Fortunately, among other solutions, PySide provides the QThread interface, allowing multithreading in PySide graphical interfaces. It can be applied to your program as follows:
import threading
from queue import Queue
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt, QThread
queue = Queue()
class Worker(QThread):
def __init__(self):
super(Worker, self).__init__()
def run(self):
mode = "none"
while True:
_mode = queue.get()
if mode != _mode:
mode = _mode;
print(f"mode: {mode}")
# do stuff here
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
label = QLabel("Change modes")
btn1 = QPushButton("MODE 1")
btn2 = QPushButton("MODE 2")
layout.addWidget(label)
layout.addWidget(btn1)
layout.addWidget(btn2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
btn1.clicked.connect(self.mode1)
btn2.clicked.connect(self.mode2)
self.show()
self.worker = Worker() # Create a Worker instance
self.worker.start() # Start the Worker instance (which calls the run function of the Worker instance)
def mode1(self):
queue.put("mode1")
def mode2(self):
queue.put("mode2")
def closeEvent(self, event):
self.worker.terminate() # When the window closes, stop the thread
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
app.exec()
Please note the changed import statement of PySide6.QtCore (to import QThread), the addition of the self.worker variable in the __init__ function of the MainWindow class (to actually start the thread), as well as the addition of a closeEvent function in the MainWindow class (to terminate the thread when the window closes).
If I create an application with a MainWindow and a QDialog and then open and close the dialog the main window remains open.
But if my QDialog starts a thread, and once this thread is finished I close the QDialog then my whole application closes.
On the simple app below you can reproduce my problem. It's ok if I open the dialog and close without launch the tread. But if I launch the thread and close the dialog after finished the application terminate.
Can you tell me where is my mistake?
import sys
from PyQt5.QtWidgets import QApplication, QDialog, QMainWindow, QPushButton, QVBoxLayout
from PyQt5.QtCore import QThread, QObject, pyqtSignal
from time import sleep
from PyQt5.Qt import QLabel
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("My App")
button = QPushButton("Press me for a dialog!")
button.clicked.connect(self.button_clicked)
self.setCentralWidget(button)
def button_clicked(self):
my_dialog = myDialog(parent=self)
my_dialog.exec()
class myDialog(QDialog):
def __init__(self, parent=None):
super(myDialog, self).__init__(parent)
self.setWindowTitle('My dialog')
self.button = QPushButton("Run long task")
self.button.clicked.connect(self.run_long_task)
self.message = QLabel("Long task not launched")
self.layout = QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.message)
self.setLayout(self.layout)
def run_long_task(self):
self.message.setText('Long task running')
self.thread = QThread()
self.worker = LongTask()
self.worker.long_task_finished.connect(self.long_task_finished)
self.thread.started.connect(self.worker.work)
self.worker.moveToThread(self.thread)
self.thread.start()
def long_task_finished(self):
self.message.setText('Long task finished')
class LongTask(QObject):
long_task_finished = pyqtSignal()
def work(self):
sleep(5)
self.long_task_finished.emit()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Thanks for your help.
I have been trying to use the concurrent.futures.ThreadPoolExecutor() to run some background tasks in my application, so that I will be able to interact with the GUI while these tasks ("measurements") run. Once these tasks are finished I assign a callback function that updates some fields of the GUI then tries to update the GUI widgets (plots, tables, lists etc.) based on these fields.
Here is an example:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
*some more code goes here*
self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
def perform_measurement():
future = self.executor.submit(*a function*)
future.add_done_callback(self.update_gui_fields)
def update_gui_fields(self, future):
data = future.result()
self.items_for_list.append(QStandardItem(data['key']))
*more fields are updated here*
self.QListView1.setModel(self.items_for_list)
*more widgets are updated here*
The problem is that the fields are updated normally, but when I try to interact with the widgets the app crashes. This is because the children (here the self.items_for_list) are in a different thread than the parent (here self.QListView1). This is the error that I get:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QListView(0x555795efbc10), parent's thread is QThread(0x555795296600), current thread is QThread(0x7fd12400a100)
QBasicTimer::start: QBasicTimer can only be used with threads started with QThread
I couldn't find any solution on previous posts. Any idea of how to attack this?
Thanks!
The callback associated with add_done_callback is executed in a secondary thread, and according to your code you are trying to update the GUI from that secondary thread, which is forbidden, so Qt throws that warning. The solution is to implement the logic by creating a QObject that forwards that information through signals:
import concurrent.futures
import sys
import time
from PyQt5 import QtCore, QtGui, QtWidgets
def measure():
time.sleep(5)
return {"key": "value"}
class TaskManager(QtCore.QObject):
finished = QtCore.pyqtSignal(object)
def __init__(self, parent=None, max_workers=None):
super().__init__(parent)
self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=max_workers)
#property
def executor(self):
return self._executor
def submit(self, fn, *args, **kwargs):
future = self.executor.submit(fn, *args, **kwargs)
future.add_done_callback(self._internal_done_callback)
def _internal_done_callback(self, future):
data = future.result()
self.finished.emit(data)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.model = QtGui.QStandardItemModel()
self.view = QtWidgets.QListView()
self.view.setModel(self.model)
self.button = QtWidgets.QPushButton("launch")
self._manager = TaskManager(max_workers=1)
self._manager.finished.connect(self.update_gui_fields)
self.button.clicked.connect(self.perform_measurement)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
lay.addWidget(self.view)
lay.addWidget(self.button)
def perform_measurement(self):
self._manager.submit(measure)
def update_gui_fields(self, data):
self.model.appendRow(QtGui.QStandardItem(data["key"]))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
I am using pyQt to display data in a textEdit then have the connected textChanged method to send the text to a server application. I need the same behavior exhibited from the QLineEdit.textEdited as textEdited in QLineEdit does not get triggered on setText.
Is there any solutions for this? Possibly a way to detect if the change was programmatic? Thanks in advance.
You can block the emission of the textChanged signal using blockSignals() method:
from PyQt5 import QtCore, QtGui, QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.text_edit = QtWidgets.QTextEdit(
textChanged=self.on_textChanged
)
self.setCentralWidget(self.text_edit)
timer = QtCore.QTimer(
self,
timeout=self.on_timeout
)
timer.start()
#QtCore.pyqtSlot()
def on_textChanged(self):
print(self.text_edit.toPlainText())
#QtCore.pyqtSlot()
def on_timeout(self):
self.text_edit.blockSignals(True)
self.text_edit.setText(QtCore.QDateTime.currentDateTime().toString())
self.text_edit.blockSignals(False)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())