import time
import sys, threading
from PyQt4 import QtGui, QtCore
from PyQt4.QtGui import QApplication
class Global:
def __init__(self):
for c in MyClass, MainWindow:
cl = c()
setattr(self, c.__name__, cl)
setattr(cl, 'global_', self)
class MyClass:
def work(self):
for r in range(100):
if r == 2:
self.global_.MainWindow.SignalBox.emit('MyClass NO PAUSE') # need pause !!!
else:
print(r)
time.sleep(1)
class MainWindow(QtGui.QWidget):
Signal = QtCore.pyqtSignal(str)
SignalBox = QtCore.pyqtSignal(str)
def __init__(self):
QtGui.QWidget.__init__(self)
self.resize(300, 300)
self.lab = QtGui.QLabel()
lay = QtGui.QGridLayout()
lay.addWidget(self.lab)
self.setLayout(lay)
self.msgBox = lambda txt: getattr(QtGui.QMessageBox(), 'information')(self, txt, txt)
self.Signal.connect(self.lab.setText)
self.SignalBox.connect(self.msgBox)
def thread_no_wait(self):
self.global_.MainWindow.SignalBox.emit('MyClass PAUSE OK')
threading.Thread(target=self.global_.MyClass.work).start()
def thread_main(self):
def my_work():
for r in range(100):
self.Signal.emit(str(r))
time.sleep(1)
threading.Thread(target=my_work).start()
if __name__ == '__main__':
app = QApplication(sys.argv)
g = Global()
g.MainWindow.show()
g.MainWindow.thread_main()
g.MainWindow.thread_no_wait()
app.exec_()
how to run a QMessageBox from another process?
MyClass.work performed in a separate thread without join is necessary to MyClass suspended if it causes QMessageBox If we define QMessageBox is the MainWindow , the error will be called
QObject :: startTimer: timers can not be started from another thread
QApplication: Object event filter can not be in a different thread.
And in this call QMessageBox invoked in a separate thread , and the work is not suspended
It sounds like you want to pause execution of a thread until certain operations in the main thread complete. There are a few ways you can do this. A simple way is to use a shared global variable.
PAUSE = False
class MyClass:
def work(self):
global PAUSE
for r in range(100):
while PAUSE:
time.sleep(1)
if r == 2:
self.global_.MainWindow.SignalBox.emit('MyClass NO PAUSE')
PAUSE = True
...
class MainWindow(...)
def msgBox(self, txt):
QtGui.QMessageBox.information(self, txt, txt)
global PAUSE
PAUSE = False
If you use QThreads instead of python threads, you can actually call functions in another thread and block until that function completes using QMetaObject.invokeMethod
#QtCore.pyqtSlot(str)
def msgBox(self, txt):
...
...
if r == 2:
# This call will block until msgBox() returns
QMetaObject.invokeMethod(
self.global_.MainWindow,
'msgBox',
QtCore.Qt.BlockingQueuedConnection,
QtCore.Q_ARG(str, 'Text to msgBox')
)
Related
I have this code (if you have pyqt5, you should be able to run it yourself):
import sys
import time
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
def __init__(self):
super().__init__()
self.thread = None
class Tab(QObject):
def __init__(self, _main):
super().__init__()
self._main = _main
class WorkerOne(Worker):
finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot(str)
def print_name(self, name):
for _ in range(100):
print("Hello there, {0}!".format(name))
time.sleep(1)
self.finished.emit()
self.thread.quit()
class SomeTabController(Tab):
def __init__(self, _main):
super().__init__(_main)
self.threads = {}
self._main.button_start_thread.clicked.connect(self.start_thread)
# Workers
self.worker1 = WorkerOne()
#self.worker2 = WorkerTwo()
#self.worker3 = WorkerThree()
#self.worker4 = WorkerFour()
def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
thread = QThread()
thread.setObjectName('thread_' + worker.__class__.__name__)
# store because garbage collection
self.threads[worker] = thread
# give worker thread so it can be quit()
worker.thread = thread
# objects stay on threads after thread.quit()
# need to move back to main thread to recycle the same Worker.
# Error is thrown about Worker having thread (0x0) if you don't do this
worker.moveToThread(QThread.currentThread())
# move to newly created thread
worker.moveToThread(thread)
# Can now apply cross-thread signals/slots
#worker.signals.connect(self.slots)
if signals:
for signal, slot in signals.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
#self.signals.connect(worker.slots)
if slots:
for slot, signal in slots.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
thread.started.connect(lambda: fn(*args)) # fn needs to be slot
thread.start()
#pyqtSlot()
def _receive_signal(self):
print("Signal received.")
#pyqtSlot(bool)
def start_thread(self):
name = "Bob"
signals = {self.worker1.finished: self._receive_signal}
self._threaded_call(self.worker1, self.worker1.print_name, name,
signals=signals)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 400)
self.button_start_thread = QPushButton()
self.button_start_thread.setText("Start thread.")
form_layout.addWidget(self.button_start_thread)
self.controller = SomeTabController(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
_main = MainWindow()
_main.show()
sys.exit(app.exec_())
However WorkerOne still blocks my GUI thread and the window is non-responsive when WorkerOne.print_name is running.
I have been researching a lot about QThreads recently and I am not sure why this isn't working based on the research I've done.
What gives?
The problem is caused by the connection with the lambda method since this lambda is not part of the Worker so it does not run on the new thread. The solution is to use functools.partial:
from functools import partial
...
thread.started.connect(partial(fn, *args))
Complete Code:
import sys
import time
from functools import partial
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
def __init__(self):
super().__init__()
self.thread = None
class Tab(QObject):
def __init__(self, _main):
super().__init__()
self._main = _main
class WorkerOne(Worker):
finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot(str)
def print_name(self, name):
for _ in range(100):
print("Hello there, {0}!".format(name))
time.sleep(1)
self.finished.emit()
self.thread.quit()
class SomeTabController(Tab):
def __init__(self, _main):
super().__init__(_main)
self.threads = {}
self._main.button_start_thread.clicked.connect(self.start_thread)
# Workers
self.worker1 = WorkerOne()
#self.worker2 = WorkerTwo()
#self.worker3 = WorkerThree()
#self.worker4 = WorkerFour()
def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
thread = QThread()
thread.setObjectName('thread_' + worker.__class__.__name__)
# store because garbage collection
self.threads[worker] = thread
# give worker thread so it can be quit()
worker.thread = thread
# objects stay on threads after thread.quit()
# need to move back to main thread to recycle the same Worker.
# Error is thrown about Worker having thread (0x0) if you don't do this
worker.moveToThread(QThread.currentThread())
# move to newly created thread
worker.moveToThread(thread)
# Can now apply cross-thread signals/slots
#worker.signals.connect(self.slots)
if signals:
for signal, slot in signals.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
#self.signals.connect(worker.slots)
if slots:
for slot, signal in slots.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
thread.started.connect(partial(fn, *args)) # fn needs to be slot
thread.start()
#pyqtSlot()
def _receive_signal(self):
print("Signal received.")
#pyqtSlot(bool)
def start_thread(self):
name = "Bob"
signals = {self.worker1.finished: self._receive_signal}
self._threaded_call(self.worker1, self.worker1.print_name, name,
signals=signals)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 400)
self.button_start_thread = QPushButton()
self.button_start_thread.setText("Start thread.")
form_layout.addWidget(self.button_start_thread)
self.controller = SomeTabController(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
_main = MainWindow()
_main.show()
sys.exit(app.exec_())
To avoid blocking the gui for a slideshow function showing images i run the following
simple code.
A background Thread Class to wait one second, then signal to the gui waiting is finished.
class Waiter(QThread):
result = pyqtSignal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
while self.isRunning:
self.sleep(1)
self.result.emit("waited for 1s")
Then in main window connect the slideshow start and stop button to start and stop methods of main app and connect the nextImage function to the signal the Waiter Thread emits.
self.actionstopSlideShow.triggered.connect(self.stopSlideShow)
self.actionslideShowStart.triggered.connect(self.startSlideShow)
self.waitthread = Waiter()
self.waitthread.result.connect(self.nextImage)
Then two methods of main app allow to start and stop the time ticker
def startSlideShow(self):
"""Start background thread that waits one second,
on wait result trigger next image
use thread otherwise gui freezes and stop button cannot be pressed
"""
self.waitthread.start()
def stopSlideShow(self):
self.waitthread.terminate()
self.waitthread.wait()
Up to now i have no problems subclassing from QThread in pyqt5, gui changes are all handled inside the main (gui) thread.
My main objective is to show a constantly evolving value on a Qt-window textEdit. (this window contains only a checkBox and a textEdit).
Sadly, I cannot click on the checkbox and the window is frozen until I shutdown the terminal.
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui,uic
class MyThread(Thread):
def __init__(self):
Thread.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.a = randint (1, 10)
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow,Thread):
def __init__(self):
Thread.__init__(self)
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.checkBox.stateChanged.connect(self.checkeven)
self.show()
#i show the value only if the checkbox is checked
def checkeven(self):
while self.checkBox.isChecked():
self.textEdit.setText(str(myThreadOb1.a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
myThreadOb1 = MyThread()
myThreadOb2 = MyWindow()
# Start running the threads!
myThreadOb1.start()
myThreadOb2.start()
sys.exit(app.exec_())
At the moment I'm using a thread to set a random value to a, but at the end it is supposed to be a bit more complex as I will have to take the values from an automation.
Do you have any clue why my code is acting like this?
Thank you very much for your help.
The problem is that the while self.checkBox.isChecked() is blocking, preventing the GUI from handling other events.
Also you should not run a PyQt GUI on another thread than the main one.
If you want to send data from one thread to another, a good option is to use the signals.
Doing all these considerations we have the following:
import sys
from threading import Thread
from random import randint
import time
from PyQt4 import QtGui, uic, QtCore
class MyThread(Thread, QtCore.QObject):
aChanged = QtCore.pyqtSignal(int)
def __init__(self):
Thread.__init__(self)
QtCore.QObject.__init__(self)
#function to continually change the targeted value
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
time.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
An option more to the style of Qt would be to use QThread since it is a class that handles a thread and is a QObject so it handles the signals easily.
import sys
from random import randint
from PyQt4 import QtGui, uic, QtCore
class MyThread(QtCore.QThread):
aChanged = QtCore.pyqtSignal(int)
def run(self):
for i in range(1, 20):
self.aChanged.emit(randint(1, 10))
secondsToSleep = 1
QtCore.QThread.sleep(secondsToSleep)
class MyWindow(QtGui.QMainWindow):
def __init__(self):
super(MyWindow,self).__init__()
uic.loadUi('mywindow.ui',self)
self.thread = MyThread()
self.thread.aChanged.connect(self.on_a_changed, QtCore.Qt.QueuedConnection)
self.thread.start()
self.show()
#i show the value only if the checkbox is checked
#QtCore.pyqtSlot(int)
def on_a_changed(self, a):
if self.checkBox.isChecked():
self.textEdit.setText(str(a))
# Run following code when the program starts
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
# Declare objects of MyThread class
w = MyWindow()
sys.exit(app.exec_())
I would like use a Qmessagebox in order to display some info about a running computation and as a stop function when I click on the OK button.
However when I use the signal buttonClicked nothing is happenning and hte function connect with it is never called
Here a code to illustrate my issue:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class SenderObject(QObject):
something_happened = pyqtSignal( )
class myfunc():
updateTime = SenderObject()
def __init__(self):
self.i = 0
self.stop = True
def run(self):
while self.stop :
self.i+=1
if self.i%100 == 0:
self.updateTime.something_happened.emit()
print('infinit loop',self.i)
class SurfViewer(QMainWindow):
def __init__(self, parent=None):
super(SurfViewer, self).__init__()
self.parent = parent
self.setFixedWidth(200)
self.setFixedHeight(200)
self.wid = QWidget()
self.setCentralWidget(self.wid)
self.groups = QHBoxLayout() ####
self.Run = QPushButton('Run')
self.groups.addWidget(self.Run)
self.wid.setLayout(self.groups)
self.Run.clicked.connect(self.run)
self.myfunc = myfunc()
self.myfunc.updateTime.something_happened.connect(self.updateTime)
def run(self):
self.msg = QMessageBox()
self.msg.setText('Click Ok to stop the loop')
self.msg.setWindowTitle(" ")
self.msg.setModal(False)
self.msg.show()
self.myfunc.run()
self.msg.buttonClicked.connect(self.Okpressed)
def Okpressed(self):
self.myfunc.stop = False
#pyqtSlot( )
def updateTime(self ):
self.msg.setText('Click Ok to stop the loop\ni = '+str(self.myfunc.i))
self.parent.processEvents()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = SurfViewer(app)
ex.setWindowTitle('window')
ex.show()
sys.exit(app.exec_( ))
So theself.msg.buttonClicked.connect(self.Okpressed) line never call the function Okpressed. Therefore, myfunc.run is never stopped.
Somebody could help on this?
write
self.msg.buttonClicked.connect(self.Okpressed)
before
self.myfunc.run()
If you call run function before subscribing click event, curse will stuck into infinite while loop. so your click event never subscribed.
First subscribe click event and then call "run" function of "myfunc"
And yes never do this -
from PyQt4.QtGui import *
from PyQt4.QtCore import *
Its vbad programming practice. You can write like
from PyQt4 import QtGui
And use into code like
QtGui.QMessagebox
I am trying to load some data which takes 30+ seconds. During this time I wish the user to see a small GUI which says "Loading .", then "Loading ..", then "Loading ...", then "Loading ." etc. I have done some reading and I think I have to put this in a separate thread. I found someone who had a similar problem suggesting the solution was this in the right spot:
t = threading.Thread(target=self.test)
t.daemon = True
t.start()
In a lower part of the file I have the test function
def test(self):
tmp = InfoMessage()
while True:
print(1)
and the InfoMessage function
from PyQt5 import uic, QtCore, QtGui, QtWidgets
import sys
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
try:
super(InfoMessage, self).__init__(parent)
uic.loadUi('ui files/InfoMessage.ui',self)
self.setWindowTitle(' ')
self.o_msg = msg
self.msg = msg
self.info_label.setText(msg)
self.val = 0
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
self.show()
except BaseException as e:
print(str(e))
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
QtWidgets.QApplication.processEvents()
def main():
app = QtWidgets.QApplication(sys.argv) # A new instance of QApplication
form = InfoMessage('Loading ') # We set the form to be our MainWindow (design)
app.exec_() # and execute the app
if __name__ == '__main__': # if we're running file directly and not importing it
main() # run the main function
When I run the InfoMessage function alone it works fine and it updates every 0.5 seconds etc. However, when I fun this as part of the loading file the GUI is blank and incorrectly displayed. I know it is staying in the test function because of the print statement in there.
Can someone point me in the right direction? I think I am missing a couple of steps.
First, there are two ways of doing this. One way is to use the Python builtin threading module. The other way is to use the QThread library which is much more integrated with PyQT. Normally, I would recommend using QThread to do threading in PyQt. But QThread is only needed when there is any interaction with PyQt.
Second, I've removed processEvents() from InfoMessage because it does not serve any purpose in your particular case.
Finally, setting your thread as daemon implies your thread will never stop. This is not the case for most functions.
import sys
import threading
import time
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtCore import QThread
def long_task(limit=None, callback=None):
"""
Any long running task that does not interact with the GUI.
For instance, external libraries, opening files etc..
"""
for i in range(limit):
time.sleep(1)
print(i)
if callback is not None:
callback.loading_stop()
class LongRunning(QThread):
"""
This class is not required if you're using the builtin
version of threading.
"""
def __init__(self, limit):
super().__init__()
self.limit = limit
def run(self):
"""This overrides a default run function."""
long_task(self.limit)
class InfoMessage(QtWidgets.QDialog):
def __init__(self, msg='Loading ', parent=None):
super(InfoMessage, self).__init__(parent)
uic.loadUi('loading.ui', self)
# Initialize Values
self.o_msg = msg
self.msg = msg
self.val = 0
self.info_label.setText(msg)
self.show()
self.timer = QtCore.QTimer()
self.timer.setInterval(500)
self.timer.timeout.connect(self.update_message)
self.timer.start()
def update_message(self):
self.val += 1
self.msg += '.'
if self.val < 20:
self.info_label.setText(self.msg)
else:
self.val = 0
self.msg = self.o_msg
def loading_stop(self):
self.timer.stop()
self.info_label.setText("Done")
class MainDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
# QThread Version - Safe to use
self.my_thread = LongRunning(limit=10)
self.my_thread.start()
self.my_loader = InfoMessage('Loading ')
self.my_thread.finished.connect(self.my_loader.loading_stop)
# Builtin Threading - Blocking - Do not use
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10}
# )
# self.my_thread.start()
# self.my_loader = InfoMessage('Loading ')
# self.my_thread.join() # Code blocks here
# self.my_loader.loading_stop()
# Builtin Threading - Callback - Use with caution
# self.my_loader = InfoMessage('Loading ')
# self.my_thread = threading.Thread(
# target=long_task,
# kwargs={'limit': 10,
# 'callback': self.my_loader}
# )
# self.my_thread.start()
def main():
app = QtWidgets.QApplication(sys.argv)
dialog = MainDialog()
app.exec_()
if __name__ == '__main__':
main()
Feel free to ask any follow up questions regarding this code.
Good Luck.
Edit:
Updated to show how to run code on thread completion. Notice the new parameter added to long_task function.
I have the following two files:
import sys
import time
from PyQt4 import QtGui, QtCore
import btnModule
class WindowClass(QtGui.QWidget):
def __init__(self):
super(WindowClass, self).__init__()
self.dataLoaded = None
# Widgets
# Buttons
thread = WorkerForLoop(self.runLoop)
# thread.start()
self.playBtn = btnModule.playpauselBtnClass \
('Play', thread.start)
# Layout
layout = QtGui.QHBoxLayout()
layout.addWidget(self.playBtn)
self.setLayout(layout)
# Window Geometry
self.setGeometry(100, 100, 100, 100)
def waitToContinue(self):
print self.playBtn.text()
while (self.playBtn.text() != 'Pause'):
pass
def runLoop(self):
for ii in range(100):
self.waitToContinue()
print 'num so far: ', ii
time.sleep(0.5)
class WorkerForLoop(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
super(WorkerForLoop, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
print 'let"s run it'
self.function(*self.args, **self.kwargs)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
wmain = WindowClass()
wmain.show()
sys.exit(app.exec_())
and the second file btnModule.py:
from PyQt4 import QtGui, QtCore
class playpauselBtnClass(QtGui.QPushButton):
btnSgn = QtCore.pyqtSignal()
def __init__(self, btnName, onClickFunction):
super(playpauselBtnClass, self).__init__(btnName)
self.clicked.connect(self.btnPressed)
self.btnSgn.connect(onClickFunction)
def btnPressed(self):
if self.text() == 'Play':
self.setText('Pause')
self.btnSgn.emit()
print 'Changed to pause and emited signal'
elif self.text() == 'Pause':
self.setText('Continue')
print 'Changed to Continue'
elif self.text() == 'Continue':
self.setText('Pause')
print 'Changed to Pause'
If in the first file I remove the comment at thread.start() it works as expected, it starts the thread and then hangs until I click Play on the UI. However, I thought it should work even if I didn't start it there as the signal is btnSgn is connected to onClickFunction which in this case takes the value thread.start.
Used this as a reference
http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
I think the reason it doesn't work when you try to call thread.start() in your second file (via self.btnSgn.emit()) is that the thread object goes out of scope outside of the init function where you created it. So you are calling start() on an already deleted thread.
Just changing thread -> self.thread (i.e. making the thread object a member of the WindowClass object) works fine when I tried it, since thread is then kept alive till the end of the program.