I want to replace the built-in method closeEvent of QMainWindow class instance that handles the form close event.
CODE #1
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
app = QApplication(sys.argv)
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
if not ui_file.open(QIODevice.ReadOnly):
print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
sys.exit(-1)
loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()
if not window:
print(loader.errorString())
sys.exit(-1)
def MainFormCloseEvent(event):
print(event)
event.ignore()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code does not cause the MainFormCloseEvent function to be called when the form closes.
This code print the following information:
<built-in method closeEvent of PySide2.QtWidgets.QMainWindow object at 0x000000000573BF80>
<function MainFormCloseEvent at 0x0000000002C37430>
But this code works well
CODE #2
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
from PySide2.QtWidgets import QMainWindow
def MainFormCloseEvent(event):
print(event)
event.ignore()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
def closeEvent(self, event):
print('Original class method')
app = QApplication(sys.argv)
window = MainWindow()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code print the following information:
<bound method MainWindow.closeEvent of <main.MainWindow(0x51522b0) at 0x0000000004E8AF40>>
<function MainFormCloseEvent at 0x0000000002C37430>
<PySide2.QtGui.QCloseEvent object at 0x0000000004E8F340>
I can't understand the fundamental difference between these codes. I replace the class instance method in the same way, but in the first case it does not work, but in the second it works.
I only noticed the difference that in the first code, the closeEvent method is built-in and in the second code, the closeEvent method is bound. But I did not find in Google what it means and how to make the first code work.
No, you're not replacing the method in the same way.
While creating a method as class attribute and overwriting an instance method at runtime usually has similar results, at the lower level it's not the same, which is extremely important when using complex modules like python binding to libraries written in other languages.
In general, attribute overwriting for methods should be done with extreme care only when it's safe to do it (and you do know what you're doing). It's also important to note that doing it for event handlers is risky, it makes debugging confusing and it also makes calling the default implementation more complex and awkward (you cannot call super()).
Unfortunately, PySide doesn't directly supports setting the UI on an existing widget instance, which is what you would do with PyQt and using a proper class, like in your second example), but there is a possible workaround, as explained in this related post.
class UiLoader(QtUiTools.QUiLoader):
_baseinstance = None
def createWidget(self, classname, parent=None, name=''):
if parent is None and self._baseinstance is not None:
widget = self._baseinstance
else:
widget = super().createWidget(classname, parent, name)
if self._baseinstance is not None:
setattr(self._baseinstance, name, widget)
return widget
def loadUi(self, uifile, baseinstance=None):
self._baseinstance = baseinstance
widget = self.load(uifile)
QtCore.QMetaObject.connectSlotsByName(baseinstance)
return widget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
ui_file.open(QIODevice.ReadOnly)
loader = UiLoader()
loader.loadUi(ui_file, self)
def closeEvent(self, event):
print('Original class method')
Still, you shouldn't overwrite the closeEvent with a basic function, instead you should probably opt for signals, further subclassing, or implement alternate ways to change the behavior (ie, using instance attributes).
I'm trying to figure out how to interact with the main thread in PyQt5 from another thread running in another file.
In the example below I'm trying to make a button green from the followup.py file's initialize function which is a thread started in the workbot_main.py file.
From the followup.py file I can't call 'workbot_main.widget' because it doesn't recognize it and I can't call workbot_main.MainWindow because it doesn't recognize it as an instantiated class so 'self' doesn't work and therefore most things within the class.
How am I supposed to interact with the MainWindow thread from another file?
I tried using slots and signals but I can't get that to work either.
Help would be massively appreciated.
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#QtCore.pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
#followup.py file
def make_green():
workbot_main.MainWindow.p1_followup_button_color_green()
initialize():
make_green()
do other stuff
Edit:
I've tried doing a great many things, the only thing I could get to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(self.Change_green.green) # <--- this works
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
But that is useless to me since the signal is sent from inside the main thread.
What I need to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
#starts the initialize function in followup.py which calls the Change_green class above
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
# followup.py
import workbot_main
color = workbot_main.Change_green()
def initialize():
print("initializing")
color.green()
But if I call the Change_green() function from followup.py, the Change_green() function gets called properly because "clicked" gets printed but there is no following "connected" being printed from the main thread.
It's as if self.setgreen.emit() only works when called from within the main thread.
I couldn't execute your script since you used a QTDesigner project to create the GUI. Also, I don't know which modules are workbot or followup, so I didn't tried to install them as they might be private modules.
I can see that you used the threading module from python. In many tutorials available on the internet, they tell you to not use this module when dealing with Qt. If need multithreading, use the QThread class instead.
The reason is that Qt schedules its own events. When dealing with raw data, that might be ok. But when dealing with QWidgets, their methods are not thread safe, so executing them from another thread might lead you to many undesired problems.
As I recommend using QThreads, I provided a minor example below explaining how to use it (it's not so easy to start using QThreads, I had and still have a hard time when dealing with them).
If you need, you can place the QThread and QObject classes in another file, then import them from main file. There's no problem about that, just be careful to not let their instances' references be collected by the python's garbage collector.
I keep them out of reach by using self.thread = ChangeColorThread(...) inside the Scene's constructor. In this way, the thread's instance is bound to the Scene, and it's reference will not go out of scope when the Scene's constructor block ends.
from PySide2.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PySide2.QtCore import QObject, QThread, Signal, QMutex
import random
# I use PySide2 which is the same thing as PyQt5 with the exception of a few variable names
# that might change from one module to another. One of them might be `Signal` (PySide2) to
# `pyqtSignal` (PyQt5) for example.
#
# The imports should also change:
# from PySide2.QtWidgets import ...
# to
# from PyQt5.QtWidgets import ...
#
# But the idea is the same for both modules.
# -------------------------------------------------------------------------------------
# After a few researches on the Multi-Threading topic, I liked the option of using the
# `QObject.moveToThread(thread)` method to create threads based on QObjects.
#
# There are others:
# - Using QThreadPool and QRunnable
# - Using QThread only (by overwriting run() method. Not recommended.)
#
# For me, I got everything working so far using the `moveToThread` method, so that's
# the one I will be using on this example.
# -------------------------------------------------------------------------------------
# Here we create the Worker Object. Everything inside `Worker.main(self)` will be
# executed in another thread: ChangeColorThread.
#
# As we might inspect / change the value of `Worker.running` on both threads at the same time
# (Main Thread and ChangeColorThread), I recommend using a QMutex to restrict access
# to this variable.
class Worker(QObject):
# Note the signal possesses a `object` as a parameter. It signalizes the QObject
# that you want to pass an object type to the signal's receiver.
#
# In this case, it's a tuple (red, green, blue) as `Scene.setRandomColor` has the
# `color` parameter.
produce = Signal(object)
# Called from main thread only to construct the Worker instance.
def __init__(self, delay):
QObject.__init__(self)
self.running = True
self.delay = delay
self.runningLock = QMutex()
# Might be called from both threads.
def stop(self):
self.runningLock.lock()
self.running = False
self.runningLock.unlock()
# Might be called from both threads.
def stillRunning(self):
self.runningLock.lock()
value = self.running
self.runningLock.unlock()
return value
# Executes on ChangeColorThread only.
def main(self):
while (self.stillRunning()):
# Generate the random colors from the ChangeColorThread.
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
# Emit the `produce` signal to safely call the `Scene.setRandomColor()`
# on the Main Thread.
#
# In order to update any GUI or call any QWidget method, you must emit
# a connected signal to the main thread, so you don't raise any exceptions
# or segmentation faults (or worse, silent crashes).
self.produce.emit((red, green, blue))
# Tell the ChangeColorThread to sleep for delay microseconds
# (aka a value of 1000 == 1 second)
self.thread().msleep(self.delay)
print('Quit from ChangeColorThread Worker.main()')
self.thread().quit()
# Here we have the other thread we will instantiate. The thread by itself
# is nothing special. It acts like a QObject until `QThread.start()` is
# called. It also has a few important properties you might want to take a look:
# - started (calls one or more connected functions once the thread starts executing).
# - finished (calls one or more connected functions once the thread properly finishes).
#
# When `start()` is called, as we connected `self.started` to `Worker.main`,
# `Worker.main` will start executing on the other thread until a `stop` call
# is requested.
class ChangeColorThread(QThread):
def __init__(self, produce_callback, delay=100):
QThread.__init__(self)
self.worker = Worker(delay)
self.worker.moveToThread(self)
self.started.connect(self.worker.main)
self.worker.produce.connect(produce_callback)
# Stops the worker object's main loop from the main thread.
def stop(self):
self.worker.stop()
# Here we will create the GUI. I kept it simple, it contains a single QLabel.
#
# It also starts the ChangeColorThread thread.
class Scene(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel("<h3>See the Color Change?</h3>")
layout.addWidget(self.label)
self.thread = ChangeColorThread(self.setRandomColor, 500)
self.thread.start()
# This function will execute on the Main Thread only.
#
# However, this function is not called by the user. It is scheduled to execute at some point
# by PyQt5 once `Worker.produce` is emitted.
def setRandomColor(self, color):
self.label.setStyleSheet("background-color:rgb(%d,%d,%d)" % (color[0], color[1], color[2]))
# As we don't know wether ChangeColorThread has stopped or not, we signalize
# it to stop before closing the application.
#
# This step is important, because even if PyQt5 / PySide2 tries its best to close the
# remaining thread, once the window is closed, this process can fail. The result is a
# hidden process still running even after the program is closed.
def sceneInterceptCloseEvent(self, evt):
self.thread.stop()
self.thread.wait()
evt.accept()
# Here we create the Main Window. It contains a single scene with the QLabel inside it.
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.scene = Scene()
self.setCentralWidget(self.scene)
# We call Scene.sceneInterceptCloseEvent in order to signalize the running thread
# to stop its main loop before the application is closed.
def closeEvent(self, evt):
self.scene.sceneInterceptCloseEvent(evt)
if __name__ == '__main__':
app = QApplication()
win = Window()
win.show()
app.exec_()
So the idea of this example is simple. We have 2 threads:
The Main Thread: which handles the GUI, scene creation, user input, and thread management. Whenever the user tries to exit the application, it's on this thread that the other ones will be signalized to be stopped.
ChangeColorThread: which is an infinite loop that executes on the background, to change the QLabel's color at every few seconds, set by the delay variable. It can be signalized to stop by the Main Thread.
Once the application starts executing, the label's background color should change based on the emission of Worker.produce signal, which sets which random color should be used for the label.
You can also move the window or resize it. By doing these manual operations, it proves that the ChangeColorThread is really executing at the background of the application. Otherwise the application would become unresponsive (frozen).
As stated on the script, I used PySide2 instead of PyQt5. But its almost the same thing, as just a few variable names should change from one to another. If you find any problems because of this, just let me know on the comments.
Edit: How to make your script work with both ways
What confused me about your post was that you rewrote your workbot_main.py script 3x, then wrote followup.py 2x, one at the middle and one at the end. I was not understanding your examples. It's hard to keep track of a lot of code, specially if you provide a whole script when you just change one line or another.
After checking them with more paciency, I was able to understand what you wanted to do. I think...
You're trying to this right?
You click on self.ui.followup_start_button
This button is connected to threads.thread_launch_followup_initialize
Which calls Change_green.green()
Which emits the signal setgreen
setgreen is connected to MainWindow.p1_followup_button_color_green
This function on MainThread changes the background color of self.ui.p1_followup_button to the green color.
The main problem I found with your script is that you're creating two different instances of Change_green:
First one: in your followup.py file.
Second one: inside your MainWindow.__init__(self).
So everytime that you tried to access the object created on MainWindow from your thread, you were in fact accessing a different instance of the same class. After solving this problem and making a few tweaks on your script, it worked without any errors so far. (Yes, the button's background color did changed to green.)
Also, you say that you can't solve your problem using QThreads. In the final example below, I also use your own classes (with one additional method and Signal) and a custom QThread I created, to reproduce the same type of idea, but now changing the button color to red.
I put all Threading Classes into followup.py, and left only Scene and Logic Classes on workbot_main.py.
Here is your new workbot_main.py:
# from workbot import * # Can't import from workbot as I don't have this module on my side
import followup
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PySide2.QtCore import QThread, Signal, QObject, Slot, Qt
class Ui(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.followup_start_button = QPushButton("Start FollowUp (Green)")
self.qthread_button = QPushButton("Start QThread (Red)")
self.p1_followup_button = QPushButton("Check me Turn Out Green or Red")
layout.addWidget(self.followup_start_button)
layout.addWidget(self.qthread_button)
layout.addWidget(self.p1_followup_button)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# --------------------------------------------
# # As I don't have your QTDesigner file, I cannot
# # load it.
# # UI_MainWindow is from the workbot.py file which is generated from QTDesigner:
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
# --------------------------------------------
# So I created my own "UI" to load both buttons on the screen
# and execute your script.
self.ui = Ui()
self.setCentralWidget(self.ui)
# --------------------------------------------
# threading module (green)
# --------------------------------------------
self.Change_green = followup.Change_green()
self.Change_green.setgreen.connect(lambda: print("connected green"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
# Starts the initialize function in followup.py which calls the Change_green class above
#
# After creating the Change_green instance, we initialize the thread
# from another file here, and pass the instance to the thread, so it
# can access it from there.
self.threads = followup.Threads(self.Change_green)
self.followup_start_button = self.ui.followup_start_button
self.followup_start_button.clicked.connect(self.threads.thread_launch_followup_initialize)
# This line was wrong. But you probably meant to assign this variable to
# your class. After fixing it your script does not raises any error.
self.p1_followup_button = self.ui.p1_followup_button
# --------------------------------------------
# QThread class (red)
# --------------------------------------------
# With QThread you solve this in one line given that everying is configured
# on MyThread(QThread) class.
self.qthread = followup.MyThread(self.Change_green, self.qthread_button_color_red)
self.ui.qthread_button.clicked.connect(self.qthread.start)
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def p1_followup_button_color_green(self):
self.p1_followup_button.setStyleSheet("background-color: green")
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def qthread_button_color_red(self):
self.p1_followup_button.setStyleSheet("background-color: red")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
Here is your new followup.py:
from PySide2.QtCore import QObject, QThread, Signal, Slot
import threading
# ------------------------------------------------------------
# - Fixing your script to use threading module
# ------------------------------------------------------------
# We don't need to import workbot_main anymore.
#
# `color` is the same object being referenced from `MainWindow.Change_green`,
# which was passed by parameter when the thread was initialized on
# `MainWindow.__init__(self)`.
#
# Now the code should work as expected.
def initialize(color):
print("initializing: ", color)
color.green()
# To make the example simpler, I've put all threading scripts here on followup.py
class Threads:
def __init__(self, color):
self.color = color
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target=initialize, args=[self.color])
t1.start()
# Including this object here which will run in another thread.
#
# As I use PySide2 I changed
# "pyqtSignal" -> "Signal"
# "pyqtSlot" -> "Slot"
class Change_green(QObject):
setgreen = Signal()
setred = Signal()
#Slot()
def green(self):
self.setgreen.emit()
print("clicked on green")
#Slot()
def red(self):
self.setred.emit()
print("clicked on red")
self.thread().quit()
# ------------------------------------------------------------
# - Using QThreads instead of python threading module
# ------------------------------------------------------------
class MyThread(QThread):
def __init__(self, change_green, qthread_button_color_red):
QThread.__init__(self)
self.Change_green = change_green
self.Change_green.setred.connect(lambda: print("connected red"))
self.Change_green.setred.connect(qthread_button_color_red)
# This is important. It sets the QObject to run on another thread.
self.Change_green.moveToThread(self)
# Set Change_green.red method to be runned on the other thread
self.started.connect(self.Change_green.red)
# Set the main thread to wait for the other thread, once this
# even is fired.
self.Change_green.setred.connect(self.wait)
Again, I still use PySide2 instead of PyQt5, so instead of using pyqtSignal and pyqtSlot, I used Signal and Slot. This change also happened on the module imports.
I left the QThread example here (Original Answer before the edit) in case you want to compare both of them.
I am currently trying to implement some threading functionality in my PySide6 GUI application. I followed a tutorial to try to get started (link is here), and I cannot seem to get it to work. Although that tutorial uses PyQt not PySide, the classes and structure is still similar, and it does seem to launch on another thread. Still though, it freezes the main GUI, which is not desired when this actually faces users.
Here is a sample of my code:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self, file):
"""Long-running task." that calls a separate class for computation""
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.thread = QThread()
self.worker = Worker()
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
def qthread_test(self):
file = 'some_file.txt'
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run(file))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
return
This does accomplish the work that is in the Worker class and spit out the desired results, but it freezes the GUI. I am not really sure what I am doing wrong, as this approach is what has been suggested to prevent freezing GUIs for heavy computation.
Is there something that I am straight up missing? Or am I going about this the wrong way? Any help or guidance is appreciated
I am assuming that you make the appropriate calls to the super class during __init__ for your subclasses of QMainWindow and the QObject.
When your code executes self.thread.started.connect(self.worker.run(file)) that line it runs the function self.worker.run(file) immediately and assigns the result of that function, which is None, as the connected slot to the thread.started signal. Instead of passing the file path as a parameter you can assign it to the worker instance and have the run method grab the path from self during execution.
For example you can try something like this:
class Worker(QObject):
finished = Signal(str)
progress = Signal(int)
def run(self):
"""Long-running task." that calls a separate class for computation"""
file = self.some_file
b = SeparateClass()
b.doComputation()
self.finished.emit()
class DataPlotting(QMainWindow):
def __init__(self):
self.report_builder = QPushButton('Call class that threads')
self.report_builder.setEnabled(False)
self.report_builder.clicked.connect(self.qthread_test)
self.threads = []
def qthread_test(self):
worker = Worker()
thread = QThread()
worker.some_file = 'some_file.txt'
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
thread.start()
self.threads.append(thread)
return
I wrote simple program which has pyQt interface with 2 buttons (start and cancel). Start button runs some calculations in the background (by starting update function) and thanks to threading I can still use UI.
But the application crashes after 10sec - 2 minutes. UI just dissapears, program shutdown.
when I use pythonw to run app without console thread crashes after ~25 sec but gui still works.
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.show()
def update(self,stop_event):
while True and not stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
self.guiUpdate()
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update, args=(self.stop_event,))
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
if __name__ == '__main__':
main()
I dont know if I'm doing threading right. I found example like this on stack. I'm quite new to python and I'm using threading for the first time.
It is most likely due to calling a GUI function in your separate thread. PyQt GUI calls like setText() on a QLineEdit are not allowed from a thread. Anything that has PyQt painting outside of the main thread will not work. One way to get around this is to have your thread emit a signal to update the GUI when data is ready. The other way is to have a timer periodically checking for new data and updating the paintEvent after a certain time.
========== EDIT ==========
To Fix this issue I created a library named qt_thread_updater. https://github.com/justengel/qt_thread_updater This works by continuously running a QTimer. When you call call_latest the QTimer will run the function in the main thread.
from qt_thread_updater import get_updater
lbl = QtWidgets.QLabel('Value: 1')
counter = {'a': 1}
def run_thread():
while True:
text = 'Value: {}'.format(counter['a'])
get_updater().call_latest(lbl.setText, text)
counter['a'] += 1
time.sleep(0.1)
th = threading.Thread(target=run_thread)
th.start()
========== END EDIT ==========
#!/usr/bin/python
import threading
import sys
from PyQt4 import QtGui, QtCore
import time
import os
class Class(QtGui.QWidget):
display_update = QtCore.pyqtSignal() # ADDED
def __init__(self):
#Some init variables
self.initUI()
def initUI(self):
#some UI
self.display_update.connect(self.guiUpdate) # ADDED
self.show()
def update(self):
while True and not self.stop_event.isSet():
self.updateSpeed()
self.updateDistance()
self.printLogs()
self.saveCSV()
# self.guiUpdate()
self.display_update.emit() # ADDED
time.sleep(1)
#gui button function
def initiate(self):
self.stop_event = threading.Event()
self.c_thread = threading.Thread(target = self.update)
self.c_thread.start()
#Also gui button function
def cancelTracking(self):
self.stop_event.set()
self.close()
def main():
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
# ex.update() # - this does nothing
if __name__ == '__main__':
main()
The other thing that could be happening is deadlock from two threads trying to access the same variable. I've read that this shouldn't be possible in python, but I have experienced it from the combination of PySide and other Python C extension libraries.
May also want to join the thread on close or use the QtGui.QApplication.aboutToQuit signal to join the thread before the program closes.
The Qt documentation for QThreads provides two popular patterns for using threading. You can either subclass QThread (the old way), or you can use the Worker Model, where you create a custom QObject with your worker functions and run them in a separate QThread.
In either case, you can't directly update the GUI from the background thread, so in your update function, the guiUpdate call will most likely crash Qt if it tries to change any of the GUI elements.
The proper way to run background processes is to use one of the two QThread patterns and communicate with the main GUI thread via Signals and Slots.
Also, in the following bit of code,
app = QtGui.QApplication(sys.argv)
ex = Class()
sys.exit(app.exec_())
ex.update()
app.exec_ starts the event loop and will block until Qt exits. Python won't run the ex.update() command until Qt has exited and the ex window has already been deleted, so you should just delete that command.
I have read a lot on threads, but I really need help with this one:
I have a PyQt Main GUI that runs an optimization with scipy.minimize...
As I cannot not make an example of this I use a "placeholder" process
to show what my problem is.
I want to let the Main GUI remain stoppable by the User, if the Process takes too long to give
a result.
My working example is this one, where I use an integration with sympy
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
import time, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class IntegrationRunner(QObject):
'Object managing the integration'
def __init__(self):
super(IntegrationRunner, self).__init__()
self._isRunning = True
def longRunning(self):
# reset
if not self._isRunning:
self._isRunning = True
print("preparing Integration")
#this is known to be time consuming and returning not the right integral
#this is a placeholder for a "time consuming" operation that sould
#be handled by a thread outside the main GUI
#imagine an optimization process or any other time consuming operation
#that would freeze the Main GUI
t=sp.symbols('t')
exp=sp.sqrt((3*t+1)/t)
sol=sp.integrate(exp,t)
print(sol)
print('finished...')
def stop(self):
self._isRunning = False
#this is displayed when the "stop button" is clicked
#but actually the integration process won´t stop
print("Integration too long - User terminated")
class SimulationUi(QDialog):
'PyQt interface'
def __init__(self):
super(SimulationUi, self).__init__()
self.goButton = QPushButton('Run Integration')
self.stopButton = QPushButton('Stop if too long')
self.layout = QHBoxLayout()
self.layout.addWidget(self.goButton)
self.layout.addWidget(self.stopButton)
self.setLayout(self.layout)
self.simulThread = QThread()
self.simulThread.start()
self.simulIntegration = IntegrationRunner()
self.simulIntegration.moveToThread(self.simulThread)
#self.simulIntegration.stepIncreased.connect(self.currentStep.setValue)
# call stop on Integr.Runner from this (main) thread on click
self.stopButton.clicked.connect(lambda: self.simulIntegration.stop())
self.goButton.clicked.connect(self.simulIntegration.longRunning)
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())
After clicking the "start" button and the "stop" button before the integration stops automatically
i get this output:
>>preparing Integration
>>Integration too long - User terminated
>>Integral(sqrt((3*t + 1)/t), t)
>>finished...
This isn´t exactly my problem but I want to understand how I can use a thread where I can put
time consuming calculations and stop them in order to try other "parameters" or "initial guesses"
when using something like Scipy Minimize
Is it even possible to stop an iterative function from "outside" and restart it without getting into "non responding"?
Any help to improve here is appreciated.
I took these example here as a guideline
how-to-signal-from-a-running-qthread-back-to-...
and pastebin