I am using Python 3.5.2 with PyQt 5.5.1. When a button is pushed a function is started as a thread, this function contains a while loop which can be terminated either from a specific event inside the thread or by pushing the button again. If the loop is terminated from inside, I want to launch a QMessageBox explaining the reason.
MWE:
from PyQt5 import QtCore, QtGui, QtWidgets, uic
import sys, threading, random
qtCreatorMain = "CloseThread.ui"
Ui_MainWindow, QtBaseClass = uic.loadUiType(qtCreatorMain)
myKillReason = 20
class OperatorGUI(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(OperatorGUI, self).__init__(parent)
self.setupUi(self)
self.running = False
self.kill_reason = None
self.lock = threading.Lock()
self.runBtn.clicked.connect(self.run_pushed)
def run_pushed(self):
self.running = not self.running
if self.running:
self.thread_1 = threading.Thread(target=self.myThread).start()
else:
self.label.setText("Not Running")
def myThread(self):
self.kill_reason = None
self.label.setText("Running")
while self.running:
self.lock.acquire()
num = random.random()
if num > 0.99999999:
self.kill_reason = myKillReason
self.running = False
self.lock.release()
if self.kill_reason == myKillReason:
self.label.setText("Not Running")
QtWidgets.QMessageBox.information(None, "Error", "Random value above limit",
QtWidgets.QMessageBox.Ok)
if __name__ == "__main__":
sys.settrace
app = QtWidgets.QApplication(sys.argv)
window = OperatorGUI()
window.show()
sys.exit(app.exec_())
.ui file
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>154</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QPushButton" name="runBtn">
<property name="geometry">
<rect>
<x>150</x>
<y>110</y>
<width>85</width>
<height>27</height>
</rect>
</property>
<property name="text">
<string>Run</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>381</width>
<height>81</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>24</pointsize>
</font>
</property>
<property name="text">
<string><html><head/><body><p align="center"><span style=" font-size:24pt;">Not running</span></p></body></html></string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>
Doing this I get an error stating: QApplication: Object event filter cannot be in a different thread.
Is there a way to have the thread send a SIGNAL
when it closes? If so I know how to use that to launch the QMessageBox. If not, is there another way to achieve the same thing?
In Qt you can only update the GUI from the main thread, if you want to update any element of the GUI before an event that occurs in another thread you must do it using signals.
class OperatorGUI(QtWidgets.QMainWindow, Ui_MainWindow):
finished = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(OperatorGUI, self).__init__(parent)
self.setupUi(self)
self.running = False
self.kill_reason = None
self.lock = threading.Lock()
self.runBtn.clicked.connect(self.run_pushed)
self.finished.connect(self.on_finished, QtCore.Qt.QueuedConnection)
def run_pushed(self):
self.running = not self.running
if self.running:
self.thread_1 = threading.Thread(target=self.myThread).start()
self.label.setText("Running")
else:
self.label.setText("Not Running")
def on_finished(self):
self.label.setText("Not Running")
QtWidgets.QMessageBox.information(None, "Error", "Random value above limit",
QtWidgets.QMessageBox.Ok)
def myThread(self):
self.kill_reason = None
while self.running:
self.lock.acquire()
num = random.random()
if num > 0.99:
self.kill_reason = myKillReason
self.running = False
self.lock.release()
if self.kill_reason == myKillReason:
self.finished.emit()