I have spent a few days trying to get a separate thread to read the incoming serial data as if I loop through a function in the GUI class with a timer the GUI gets unresponsive as it tries to receive the data. I have managed to print the incoming serial on the console even though it has been done using a while loop, which I do not like too much, and I plan to replace it with a timer.
Now I have troubles passing the variable decoded_serial from the WorkerThread class to the displaySerial function in the main GUI class.
This is the code I have been working on:
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QThread, QTimer
import serial
class WorkerThread(QThread):
def run(self):
ser= serial.Serial(port='COM5', baudrate=9600)
while (True):
if (ser.inWaiting() > 0):
data_string = ser.read(ser.inWaiting()).decode('utf-8')
decoded_serial = (data_string)
print(decoded_serial)
class Ui_MainWindow(object):
def workerSignal(self):
self.worker.start()
print('Worker started')
def setupUi(self, MainWindow):
self.worker = WorkerThread()
MainWindow.setObjectName('MainWindow')
MainWindow.resize(250, 300)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName('centralwidget')
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(30, 100, 201, 161))
self.groupBox.setObjectName('groupBox')
self.LineEdit = QtWidgets.QLineEdit(self.groupBox)
self.LineEdit.setGeometry(QtCore.QRect(10, 50, 181, 20))
self.LineEdit.setObjectName('LineEdit')
self.Label = QtWidgets.QLabel(self.groupBox)
self.Label.setGeometry(QtCore.QRect(10, 30, 181, 10))
self.Label.setObjectName('Label')
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate('MainWindow', 'TestApp'))
self.groupBox.setTitle(_translate('MainWindow', 'Serial'))
self.Label.setText(_translate('MainWindow', 'Serial'))
def displaySerial(self):
self.LineEdit.setText(decoded_serial)
if __name__ == '__main__':
import sys
def eventTimer():
ui.workerSignal()
def serialLoop():
ui.displaySerial()
app = QtWidgets.QApplication(sys.argv)
QtCore.QTimer.singleShot(0, eventTimer)
serial_timer = QtCore.QTimer()
serial_timer.timeout.connect(serialLoop)
serial_timer.start(1000)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
To try to pass the value to the main GUI class I have tried something like:
class WorkerThread(QThread):
def run(self):
WorkerThread.data = decoded_serial
class Ui_MainWindow(object, WorkerThread):
print(WorkerThread.data)
Although I do not seem to be doing it the right way, and it returns some errors.
What would be the best approach in order to send the retrieved serial string to the line edit field?
I have had a look at multiple solutions presented in the past, but I do not seem to understand how to implement those in my code.
Any guidance is highly appreciated!
i have a GUI with PyQt5. The GUI has a Form, 2 Buttons and a ProgressBar.
I want to implement a start/wait thread with QThread.
Unfortunately i get Process finished with exit code 139 (interrupted by signal 11: SIGSEGV) when i click Start button to run thread.
MainWindow.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'MainWindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(400, 138)
self.btnRun = QtWidgets.QPushButton(Form)
self.btnRun.setGeometry(QtCore.QRect(20, 20, 89, 25))
self.btnRun.setObjectName("btnRun")
self.btnStop = QtWidgets.QPushButton(Form)
self.btnStop.setGeometry(QtCore.QRect(20, 60, 89, 25))
self.btnStop.setObjectName("btnStop")
self.progressBar = QtWidgets.QProgressBar(Form)
self.progressBar.setGeometry(QtCore.QRect(20, 100, 371, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "Test Thread"))
self.btnRun.setText(_translate("Form", "Run"))
self.btnStop.setText(_translate("Form", "Stop"))
When i click the Start button the application crashes with segmentation fault.
Thread.py
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from MainWindow import *
import sys
import time
class MainWindow(QWidget, QThread):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.center()
#Init progressBar
self.ui.progressBar.setValue(0)
#Buttons
self.ui.btnRun.clicked.connect(self.start)
self.ui.btnStop.clicked.connect(self.wait)
def run(self):
count = 0
while count <= 100:
count += 1
self.ui.progressBar.setValue(count)
time.sleep(1)
def center(self):
# geometry of the main window
qr = self.frameGeometry()
# center point of screen
cp = QDesktopWidget().availableGeometry().center()
# move rectangle's center point to screen's center point
qr.moveCenter(cp)
# top left of rectangle becomes top left of window centering it
self.move(qr.topLeft())
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setAttribute(Qt.AA_DontShowIconsInMenus, False)
w = MainWindow()
# Disable maximize window button
w.setWindowFlags(Qt.WindowCloseButtonHint | Qt.WindowMinimizeButtonHint)
w.show()
sys.exit(app.exec_())
The problem is that you cannot inherit from QWidget and QThread at the same time because:
Cannot inherit from 2 QObject, there is a conflict in behaviors.
PyQt limits multiple inheritance.
The solution is to use composition instead of multiple inheritance.
class Thread(QThread):
progressChanged = pyqtSignal(int)
def run(self):
count = 0
while count <= 100:
count += 1
self.progressChanged.emit(count)
time.sleep(1)
class MainWindow(QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.center()
self._custom_thread = Thread()
#Init progressBar
self.ui.progressBar.setValue(0)
#Buttons
self.ui.btnRun.clicked.connect(self._custom_thread.start)
self.ui.btnStop.clicked.connect(self._custom_thread.wait)
self._custom_thread.progressChanged.connect(self.ui.progressBar.setValue)
I am trying to create a loading screen because my MainWindow takes over 10 seconds to open, which is ok as long as I have a loading screen.
Here I have some code and when i run it, it will open a loading screen.
After the loading screen pops up i want it to immediately try to open the MainWindow. (On my actual app this would take 10 seconds to open)
I have used time.sleep to simulate the time my actual programme would take to open as this is just smaller code for you to read. However my real code will not include any sleep it will just naturally take 10 seconds to load.
How can I make it so once my loading screen is visible it will immediately open MainWindow? Currently I have not asked it to do anything, the loading screen just opens and that's it.
What extra code do I need to make it open MainWindow?
Ideally I would like to update the progress bar as this happens but that is not nessecary for now I just want to know how to open MainWindow immediately from the loading screen, thanks.
from PyQt5 import QtCore, QtGui, QtWidgets
import time
class Ui_SplashScreen(object):
def setupUi(self, SplashScreen):
SplashScreen.setObjectName("SplashScreen")
SplashScreen.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(SplashScreen)
self.centralwidget.setObjectName("centralwidget")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(190, 220, 461, 91))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
SplashScreen.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(SplashScreen)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
SplashScreen.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(SplashScreen)
self.statusbar.setObjectName("statusbar")
SplashScreen.setStatusBar(self.statusbar)
self.retranslateUi(SplashScreen)
QtCore.QMetaObject.connectSlotsByName(SplashScreen)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
time.sleep(10)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(310, 180, 341, 161))
font = QtGui.QFont()
font.setPointSize(40)
self.label.setFont(font)
self.label.setObjectName("label")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "MY APP"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
SplashScreen = QtWidgets.QMainWindow()
ui = Ui_SplashScreen()
ui.setupUi(SplashScreen)
SplashScreen.show()
sys.exit(app.exec_())
You should not modify the code generated by pyuic so you must recreate those files that for my solution will be splash_ui.py and main_ui.py.
The idea is that the time consuming task should not run in the main thread but in a secondary thread and emit signals when it starts and ends that show and hide the splashscreen.
import threading
import sys
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow
from main_ui import Ui_MainWindow
from splash_ui import Ui_SplashScreen
def long_running_function():
import time
time.sleep(10)
class Worker(QObject):
started = pyqtSignal()
finished = pyqtSignal()
def start(self):
threading.Thread(target=self._execute, daemon=True).start()
def _execute(self):
self.started.emit()
# FIXME
long_running_function()
self.finished.emit()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class SplashScreen(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_SplashScreen()
self.ui.setupUi(self)
def main():
app = QApplication(sys.argv)
splash_screen = SplashScreen()
main_window = MainWindow()
worker = Worker()
worker.started.connect(splash_screen.show)
worker.finished.connect(splash_screen.close)
worker.finished.connect(main_window.show)
worker.start()
sys.exit(app.exec_())
I have a very simple program and the structure must be preserved (it is a condition of the exercise).
We start with an interface formed by a button and a Canvas, like shown in the figure above.
Once the button is clicked, a background task is initiated, which calls a function called animation. In here we start a process that generates random data every time waiting_time.
We want to update the plot everytime there is a new x and y variable.
The code is the following:
from PyQt5 import QtCore, QtWidgets
from mplwidget import MplWidget
import threading
import time
import numpy as np
import sys
class RandomDataGeneration():
"""
Mandatory Class. This Class must exist.
"""
def __init__(self):
pass
def data_generation(self):
while True:
waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
print(waiting_time)
time.sleep(waiting_time)
self.x = np.random.rand(10)
self.y = np.random.rand(10)
print(self.x)
print(self.y)
#self.update_plot()
def update_plot(self):
self.MplWidget.canvas.axes.clear()
self.MplWidget.canvas.axes.set_title('GRAPH')
self.MplWidget.canvas.axes.plot(x, y, marker='.', linestyle='')
self.MplWidget.canvas.axes.legend(('random'), loc='upper right')
self.MplWidget.canvas.draw()
def animation():
"""
This function initiates the RandomDataGeneration
"""
app = RandomDataGeneration()
app.data_generation()
class Ui_MainWindow():
def __init__(self):
super().__init__()
def start_download(self):
download_info = threading.Thread(target=animation)
download_info.start()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1280, 1024)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(880, 80, 221, 32))
self.pushButton.setObjectName("pushButton")
self.MplWidget = MplWidget(self.centralwidget)
self.MplWidget.setGeometry(QtCore.QRect(49, 39, 771, 551))
self.MplWidget.setObjectName("MplWidget")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
self.pushButton.clicked.connect(self.start_download)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
To run the code is necessary to have in the same folder the following code with name mplwidget.py.
# ------------------------------------------------------
# -------------------- mplwidget.py --------------------
# ------------------------------------------------------
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)
Since you are creating a pyqt5 application I guess your best bet is to use a QThread for the blocking part and emit a signal every time new data is generated. One way would be to make RandomDataGeneration a subclass of QThread and implement run, e.g.
class RandomDataGeneration(QtCore.QThread):
"""
Mandatory Class. This Class must exist.
"""
new_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
def data_generation(self):
while True:
waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
print(waiting_time)
time.sleep(waiting_time)
self.x = np.random.rand(10)
self.y = np.random.rand(10)
print(self.x)
print(self.y)
self.new_data.emit()
def run(self):
self.data_generation()
To use the thread you could subclass QMainWindow and create an instance of RandomDataGeneration in there. Subclassing QMainWindow has an additional advantage that you can move the gui setup and the signal-slot connections in there as well, e.g.
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.download_thread = RandomDataGeneration(self)
self.download_thread.new_data.connect(self.plot_data)
self.ui.pushButton.clicked.connect(self.start_download)
def start_download(self):
if not self.download_thread.isRunning():
self.download_thread.start()
def plot_data(self):
self.ui.MplWidget.update_plot(self.download_thread.x, self.download_thread.y)
The main part then becomes
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = MyMainWindow()
MainWindow.show()
sys.exit(app.exec_())
I am trying to run a progress bar on a thread and a function on another thread. The following is my approach and it is working fine, until I add a QMessageBox.
I created two new classes for QThread, one handles the progress bar, another my function. They are being called when the button is pressed using the onButtonClicked function
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QMessageBox, QLineEdit, QProgressBar, QLabel, QFileDialog, QCheckBox, QMenuBar, QStatusBar
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.msg = QMessageBox()
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.Actionlistenr()
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def test(self):
if self.lineEdit.text() == "":
self.msg.setIcon(QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
return self.msg.exec()
# If this was just a regular print statement,
# then it would work, or any other statement that
# does not involve a QMessageBox
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
self.calc2 = External2(self)
self.calc2.start()
def onCountChanged(self, value):
self.progressBar.setValue(value)
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
class External2(QThread, object):
"""
Runs a counter thread.
"""
def __init__(self, outer_instance):
super().__init__()
self.outer_instance = outer_instance
def run(self):
self.outer_instance.test()
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_())
I am getting QObject::setParent: Cannot set parent, new parent is in a different thread Error when doing this, only when I add a QMessageBox in my test function. I am assuming that this is happening because QMessagebox is running on the main thread, not my External2() class, how can I fix this?
The first thing you have to do is verify if the requirements are met (in this case the QLineEdit is not empty) for the heavy duty to start. In these cases I prefer to use the worker thread approach. To launch the heavy task, the method must be invoked asynchronously, for example using QTimer.singleShot(), and to pass the additional arguments use functools.partial(), you must also use #pyqtSlot to be sure that the tasks are executed in the thread Right.
On the other hand you should not modify the class generated by Qt Designer(1) but create another class that inherits from the widget and use the first one to fill it.
from PyQt5 import QtCore, QtGui, QtWidgets
from functools import partial
import time
TIME_LIMIT = 100
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(300, 60, 47, 13))
self.label.setObjectName("label")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setGeometry(QtCore.QRect(270, 100, 113, 20))
self.lineEdit.setObjectName("lineEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(290, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(280, 210, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
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.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.msg = QtWidgets.QMessageBox()
self.Actionlistenr()
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.countChanged.connect(self.progressBar.setValue)
self.m_worker.moveToThread(thread)
def Actionlistenr(self):
self.pushButton.clicked.connect(self.onButtonClick)
def request_information(self):
filename = self.lineEdit.text()
if filename:
wrapper = partial(self.m_worker.task, filename)
QtCore.QTimer.singleShot(0, wrapper)
else:
self.msg.setIcon(QtWidgets.QMessageBox.Critical)
self.msg.setText("Please select a document first!")
self.msg.setWindowTitle("Error")
self.msg.exec_()
#QtCore.pyqtSlot()
def onButtonClick(self):
self.request_information()
class Worker(QtCore.QObject):
countChanged = QtCore.pyqtSignal(int)
#QtCore.pyqtSlot(str)
def task(self, filename):
# execute heavy task here
print("start")
print(filename)
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.countChanged.emit(count)
print("finished")
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
(1) http://pyqt.sourceforge.net/Docs/PyQt5/designer.html
Okay this per-sae does a bit more than you asked but it also does a bit less also -- however it does include all the bits and pieces and how they interconnect so you should be able to extrapolate from this and get it to do whatever it is you are wanting to get done. Note your program had a few issues so I could not duplicate it verbatim -- one of those issues pertaining to your specific problem is that you cannot run anything that inherits from QWidgets from within a thread -- I solved this by creating a multi-process to handle the 2nd Window situation. Still with how I have outlined the Threading you can see that you do not need to have that QMessageBox within the Thread but you could cause something from within that Thread to launch the QMessageBox back in the QMainWindow -- as I have pointed out -- if you need the Thread to launch that QMessageBox -- This works although you might need to add a bit to show the functionality going on within the Threads -- I know this because I have already tested that aspect of this.
from sys import exit as sysExit
from time import sleep as tmSleep
from PyQt5.QtCore import Qt, QObject, QThread, QRunnable, pyqtSignal, pyqtSlot
# from PyQt5.QtGui import ??
#Widget Container Objects
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDockWidget
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QMenuBar, QStatusBar, QLabel
#Widget Action Objects
from PyQt5.QtWidgets import QMessageBox, QFileDialog, QPushButton, QLineEdit
from PyQt5.QtWidgets import QProgressBar, QCheckBox, QAction, QStyleFactory
# Part of Threading
# Note be very careful with Signals/Slots as they are prone to Memory Leaks
class ThreadSignals(QObject):
ObjctSignal = pyqtSignal(object)
IntgrSignal = pyqtSignal(int)
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class Processor(QWidget):
def __init__(self, Id):
QWidget.__init__(self)
self.ThreadActive = True
self.RunProcess = False
self.Id = Id
self.Name = '---- Threaded Process ' + str(self.Id)
self.Msg = self.Name
def Connect(self, sigHandle, sigFlag):
self.QueData = queQue()
cnt = 0
self.Flag = sigFlag
sigHandle.emit(self)
tmSleep(0.005) # 5 Milliseconds
# This simulates a continuously running process
# The waits are necessary to allow the OS to do stuff because
# python IS NOT multiprocessing due to the GIL -- look it up
self.lstData = []
while self.ThreadActive:
while self.RunProcess:
cnt += 1
if cnt % 10 == 0:
self.lstData.append(cnt)
if cnt % 100 == 0:
self.Msg = self.Name + ' : Loop ' + str(cnt)
self.QueData.put(self.Msg)
self.QueData.put(self.lstData.copy())
sigFlag.emit(cnt)
self.lstData = []
tmSleep(0.005) # 5 Milliseconds
tmSleep(0.005) # 5 Milliseconds
def GetData(self):
RetData = []
if not self.QueData.empty():
RetData = list(self.QueData.get())
return RetData
def StartProcess(self):
self.RunProcess = True
self.Msg = self.Name + ' Started'
self.Flag.emit(-1)
def StopProcess(self):
self.RunProcess = False
self.Msg = self.Name + ' Stopped'
self.Flag.emit(-1)
def DisConnect(self):
self.RunProcess = False
self.ThreadActive = False
self.Msg = self.Name + ' Disconnected'
# Part of Threading -- if its a Class that does pretty much the same thing then should only have one
class WorkerProcess(QRunnable):
def __init__(self, StartrFunc, Id):
super(WorkerProcess, self).__init__()
self.StartrFunc = StartrFunc
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
self.setAutoDelete(False)
self.Id = Id
self.name = '----- WorkerProcess ' + str(Id)
# Create Signal (aka Sender) Here
self.signals = ThreadSignals()
self.sigHndl = self.signals.ObjctSignal
self.sigFlag = self.signals.IntgrSignal
#pyqtSlot()
def run(self):
print('Inside ',self.name)
self.StartrFunc(self.sigHndl, self.sigFlag)
# def StarterFunc(self):
# self.ProcessObject = Processor(#)
# self.ProcessObject.Connect(sigHandle, sigFlag)
print('******************************')
print('--- Process Completed')
# Note while this process has completed this thread is still active
def DisConnect(self):
# This disconnects all of its Signals
self.signals.disconnect()
# This is your Menu and Tool Bar class it does not handle the Tool Bar
# at this time but it could be expanded to do so fairly easily just
# keep in mind everything on a Tool Bar comes from the Menu Bar
class MenuToolBar(QDockWidget):
def __init__(self, parent):
QDockWidget.__init__(self)
self.Parent = parent
self.MainMenu = parent.menuBar()
# This is used to have a handle to the Menu Items
# should you implement a Tool Bar
self.MenuActRef = {'HelloAct':0,
'ResetAct':0}
# ******* Create the World Menu *******
self.WorldMenu = self.MainMenu.addMenu('World')
# ******* Create World Menu Items *******
self.HelloAct = QAction('&Hello', self)
# In case you have or want to include an Icon
# self.HelloAct = QAction(QIcon('Images/hello.ico'), '&Hello', self)
self.HelloAct.setShortcut("Ctrl+H")
self.HelloAct.setStatusTip('Say Hello to the World')
self.HelloAct.triggered.connect(self.SayHello)
self.MenuActRef['HelloAct'] = self.HelloAct
self.ResetAct = QAction('&Reset', self)
# self.ResetAct = QAction(QIcon('Images/reset.ico'), '&Hello', self)
self.ResetAct.setShortcut("Ctrl+H")
self.ResetAct.setStatusTip('Reset the Dialog')
self.ResetAct.triggered.connect(self.ResetWorld)
self.MenuActRef['ResetAct'] = self.ResetAct
# ******* Setup the World Menu *******
self.WorldMenu.addAction(self.HelloAct)
self.WorldMenu.addSeparator()
self.WorldMenu.addAction(self.ResetAct)
self.InitToolBar()
def InitToolBar(self):
# If you create a Tool Bar initialize it here
pass
# These are the Menu/Tool Bar Actions
def SayHello(self):
self.Parent.MenuSubmit()
def ResetWorld(self):
self.Parent.MenuReset()
# Its easiest and cleaner if you Class the Center Pane
# of your MainWindow object
class CenterPanel(QWidget):
def __init__(self, parent):
QWidget.__init__(self)
self.Parent = parent
self.Started = False
#-----
self.lblTextBox = QLabel()
self.lblTextBox.setText('Text Box Label')
#-----
self.lneTextBox = QLineEdit()
#-----
self.btnPush = QPushButton()
self.btnPush.setText('Start')
self.btnPush.clicked.connect(self.Starter)
#-----
self.btnTest = QPushButton()
self.btnTest.setText('Test')
self.btnTest.clicked.connect(self.TestIt)
#-----
HBox = QHBoxLayout()
HBox.addWidget(self.btnPush)
HBox.addWidget(self.btnTest)
HBox.addStretch(1)
#-----
self.pbrThusFar = QProgressBar()
self.pbrThusFar.setProperty('value', 24)
#-----
VBox = QVBoxLayout()
VBox.addWidget(self.lblTextBox)
VBox.addWidget(self.lneTextBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addLayout(HBox)
VBox.addWidget(QLabel(' ')) # just a spacer
VBox.addWidget(self.pbrThusFar)
VBox.addStretch(1)
#-----
self.setLayout(VBox)
def Starter(self):
if self.Started:
self.btnPush.setText('Start')
self.Started = False
self.Parent.OnStart()
else:
self.btnPush.setText('Reset')
self.Started = True
self.pbrThusFar.setProperty('value', 24)
self.Parent.OnReset()
def TestIt(self):
# Note this cannot be handled within a Thread but a Thread can be
# designed to make a call back to the MainWindow to do so. This
# can be managed by having the MainWindow pass a handle to itself
# to the Thread in question or by using a Signal/Slot call from
# within the Thread back to the MainWindow either works
Continue = True
if self.lneTextBox.text() == '':
DocMsg = QMessageBox()
DocMsg.setIcon(QMessageBox.Critical)
DocMsg.setWindowTitle("Error")
DocMsg.setText("There is no Document. Do you want to Quit?")
DocMsg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
DocMsg.setDefaultButton(QMessageBox.No)
DocMsg.setWindowFlags(Qt.WindowStaysOnTopHint)
MsgReply = DocMsg.exec_()
if MsgReply == QMessageBox.Yes:
sysExit()
def HandleSubmit(self):
self.lneTextBox.setText('Center Panel Menu Submit')
# This is sort of your Main Handler for your interactive stuff as such
# it is best to restrict it to doing just that and let other classes
# handle the other stuff -- it also helps maintain overall perspective
# of what each piece is designed for
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle('Main Window')
# Sometimes its best to place the window where you want but just setting its size works too
# Still I do this in two lines to make it clear what each position is
WinLeft = 150; WinTop = 150; WinWidth = 400; WinHight = 200
# self.setGeometry(WinLeft, WinTop, WinWidth, WinHight)
self.resize(WinWidth, WinHight)
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
# The Menu and Tool Bar for your MainWindow should be classed as well
self.MenuBar = MenuToolBar(self)
self.SetStatusBar(self)
# Not exactly sure what all this does yet but it does remove
# oddities from the window so I always include it - for now
self.setStyle(QStyleFactory.create('Cleanlooks'))
# Part of Threading
self.Thread1Connected = False
self.Thread2Connected = False
# Create Handles for the Threads
# I used this methodology as it was best for my program but
# there are other ways to do this it depends on your needs
self.Thread1Hndl = QObject()
self.Thread2Hndl = QObject()
# This is used to start the Thread 1
self.MyThread1 = WorkerProcess(self.Threader1, 1)
# Create Slots (aka Receivers) Here
self.MyThread1.signals.ObjctSignal.connect(self.Thread1_Hndl)
self.MyThread1.signals.IntgrSignal.connect(self.Thread1_Flag)
# This is used to start the Thread 2
self.MyThread2 = WorkerProcess(self.Threader2, 2)
# Create Slots (aka Receivers) Here
self.MyThread2.signals.ObjctSignal.connect(self.Thread2_Hndl)
self.MyThread2.signals.IntgrSignal.connect(self.Thread2_Flag)
def MenuSubmit(self):
self.CenterPane.HandleSubmit()
def MenuReset(self):
self.CenterPane.lineEdit.setText('Main Window Menu Reset')
def SetStatusBar(self, parent):
StatusMsg = ''
parent.StatBar = parent.statusBar()
if len(StatusMsg) < 1:
# This verbiage will disappear when you view menu items
StatusMsg = 'Ready'
parent.StatBar.showMessage(StatusMsg)
def OnStart(self):
if self.Thread1Connected:
self.Thread1Hndl.StartProcess()
if self.Thread2Connected:
self.Thread2Hndl.StartProcess()
def OnReset(self):
pass
# Part of Threading
def Thread1_Hndl(self, sigHandle):
self.Thread1Hndl = sigHandle
print('******************************')
print('--- Thread 1 Handle Sent Back Validation')
print(self.Thread1Hndl.Msg)
self.Thread1Connected = True
def Thread1_Flag(self, sigFlag):
print('******************************')
print('--- Thread 1 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread1Hndl.Msg)
self.DoStuffT1()
if sigFlag > 1000:
self.Thread1Connected = False
self.Thread1Hndl.DisConnect()
print(self.Thread1Hndl.Msg)
def Thread2_Hndl(self, Handle):
self.Thread2Hndl = Handle
print('******************************')
print('--- Thread 2 Handle Sent Back Validation')
print(self.Thread2Hndl.Msg)
self.Thread2Connected = True
def Thread2_Flag(self, sigFlag):
print('******************************')
print('--- Thread 2 Loop Id Sent Back Validation')
print('----- Current Loop : ', sigFlag)
print(self.Thread2Hndl.Msg)
self.DoStuffT2()
if sigFlag > 1000:
self.Thread2Connected = False
self.Thread2Hndl.DisConnect()
print(self.Thread2Hndl.Msg)
def DoStuffT1(self):
# Just a place holder function for demonstration purposes
# Perhaps handle this here for one of the Threads
# self.CenterPane.pbrThusFar.setValue(value)
pass
def DoStuffT2(self):
# Just a place holder function for demonstration purposes
pass
# Part of Threading
# These Functions are being passed into completely Separate Threads
# do not try to print from within as stdout is not available
# Also keep in mind you cannot use anything within a Thread that
# inherits from QWidgets and a few from QGui as well
# Create the entire object within the Thread allowing for complete
# autonomy of its entire functionality from the Main GUI
def Threader1(self, sigHandle, sigFlag):
self.Thrdr1Obj = Processor(1) # Create Threader 1 Object from Class
self.Thrdr1Obj.Connect(sigHandle, sigFlag)
def Threader2(self, sigHandle, sigFlag):
self.Thrdr2Obj = Processor(2) # Create Threader 2 Object from Class
self.Thrdr2Obj.Connect(sigHandle, sigFlag)
if __name__ == "__main__":
# It is best to keep this function to its bare minimum as its
# main purpose is to handle pre-processing stuff
#
# Next you did not appear to be using sys.argv but if you od need
# to use command line arguments I strongly suggest you look into
# argparse its a python library and very helpful for this as such
# also much cleaner than dealing with them via regular means
MainThred = QApplication([])
MainGUI = MainWindow()
MainGUI.show()
sysExit(MainThred.exec_())
Finally if you have any questions on this do ask but I did try to include explanatories within the code. Also I did a bit of extra cross object calling so you could see how it might be done -- this is not a production version but more a proof of concept that demonstrates numerous concepts within it