Python PyQt5 signals & slots - python

I am having some trouble applying the new pyqt5 signals and slots into a script thats purpose is to test/invoke another problem I've been trying to solve, GUI freezing/crashing ... the aim is so that once these signals and slots are functioning correctly the GUI will not crash after +/- 30 seconds of runtime, and just continue on counting numbers until the end of time. I have provided a pyqt4 example although it would be great to have a pyqt5 solution. Thanks :)
from time import sleep
import os
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtGui import *
import random
import os
import time
class Cr(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
while True:
rndInt = random.randint(1, 100000)
timesleep = random.random()
time.sleep(timesleep)
for i in range(120):
self.emit(QtCore.SIGNAL('host_UP'), 'foo' + str(rndInt), i)
QtGui.QApplication.processEvents()
class Main_Window(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.relativePath = os.path.dirname(sys.argv[0])
self.Main_Window = uic.loadUi("Main_Window.ui", self)
self.Main_Window.show()
self.Main_Window.move(790, 300)
self.GU = []
ProgressThreads = self.printThreads
self.details_label = []
for i in range(120):
self.details_label.insert(i, 0)
self.details_label[i] = QLabel(" ")
ProgressThreads.addWidget(self.details_label[i])
ProgressThreads.addSpacing(6)
self.details_label[i].setText(Tools.Trim.Short('Idle', 7))
self.GU.insert(i, Cr())
self.GU[i].start()
self.connect(self.GU, QtCore.SIGNAL("host_UP"), self.UpdateHost)
def UpdateHost(self, str1, pos1):
self.details_label[pos1].setText(str1)
class guiUpdate():
def GUI_main(self):
self.GUI = GUI
if __name__ == "__main__":
app = QApplication(sys.argv)
guiUpdate.GUI_main.GUI = Main_Window()
sys.exit(app.exec_())
Thank you for the help :)
UPDATE
The script below is a hopefully correct PyQt5 version of the script above. However the issue of crashing and 'not responding' message is still unresolved
from time import sleep
import os
from PyQt5 import QtCore, QtGui, uic
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QObject, pyqtSignal
import random
import os
import time
import Tools
import sys
class Cr(QtCore.QThread):
def __init__(self, sam):
QtCore.QThread.__init__(self)
self.sam = sam
def run(self):
while True:
rndInt = random.randint(1, 100000)
timesleep = random.random()
time.sleep(timesleep)
for i in range(5):
#time.sleep(1)
self.sam.connect_and_emit_trigger('foo' + str(rndInt), i)
#self.emit(QtCore.SIGNAL('host_UP'), 'foo' + str(rndInt), i)
#QtGui.QApplication.processEvents()
class Main_Window(QWidget):
def __init__(self, *args):
QWidget.__init__(self, *args)
self.relativePath = os.path.dirname(sys.argv[0])
self.Main_Window = uic.loadUi("Main_Window.ui", self)
self.Main_Window.show()
self.Main_Window.move(790, 300)
sam = Foo()
self.GU = []
ProgressThreads = self.ProgressThreads
self.details_label = []
for i in range(5):
self.details_label.insert(i, 0)
self.details_label[i] = QLabel(" ")
ProgressThreads.addWidget(self.details_label[i])
ProgressThreads.addSpacing(6)
self.details_label[i].setText(Tools.Trim.Short('Idle', 7))
self.GU.insert(i, Cr(sam))
self.GU[i].start()
class Foo(QObject):
# Define a new signal called 'trigger' that has no arguments.
trigger = pyqtSignal()
def connect_and_emit_trigger(self, str, i):
self.str = str
self.i = i
self.trigger.connect(self.handle_trigger)
self.trigger.emit()
def handle_trigger(self):
guiUpdate.GUI_main.GUI.details_label[self.i].setText(self.str)
class guiUpdate():
def GUI_main(self):
self.GUI = GUI
if __name__ == "__main__":
app = QApplication(sys.argv)
guiUpdate.GUI_main.GUI = Main_Window()
sys.exit(app.exec_())

The new recommended way to use threads (and the one I got the best results with) is to use moveToThread() instead of directly subclassing QThread. In short:
write a QObject subclass doing the actual work (let's call it QMyWorker). This will likely look a bit like your existing qthread subclass, with a start() or run() method etc.
create a parent-less instance of QMyWorker
create a parent-less instance of QThread
use QMyWorker.moveToThread(your_thread_instance) (I go by memory, double check the API in doc).
call your QMyWorker.start()
This approach worked for me for very long jobs (4GB files etc).

I used QThreadPool, QRunnable with a worker, can make more workers per thread.
Very good example with explanation here
https://martinfitzpatrick.name/article/multithreading-pyqt-applications-with-qthreadpool/
My PYQT5 was freezing up also, now i fine tune it with printing a TimeStamp

Related

Is it possible logging another GUI in the QThread

import sys
from PyQt5.QtWidgets import *
import pyqtgraph as pg
import logging
import threading
from tqdm import tqdm
import time
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.plot_widget = pg.PlotWidget()
self.setCentralWidget(self.plot_widget)
self.plot_data = None
self.setupPlotLogger(self.plot_widget)
threading.Thread(target=self.foo).start()
def foo(self):
for i in tqdm(range(100)):
time.sleep(0.1)
logging.getLogger('test').debug(i)
def setupPlotLogger(self, widget):
pl = PlotLogger()
pl.comp = widget
pl.data = self.plot_data
logging.getLogger('test').setLevel(level=logging.DEBUG)
logging.getLogger('test').addHandler(pl)
def do_task(self, value):
logging.getLogger('test').info(value)
class PlotLogger(logging.Handler):
def emit(self, record):
record = float(self.format(record))
if self.data is not None:
self.data.append(record)
else:
self.data = [record]
self.comp.plot(self.data)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindow()
window.show()
app.exec_()
In above code, I have got warning message which is "QObject::startTimer: Timers cannot be started from another thread".
In this case, I wanna logging another gui component(PlotWidget) while thread something.
what i searching is the problem is call another UI component(might be PlotWidget).
Is there solution for what i want to do?
Although it might be look funny, i was findout solution myself.
so, i am shared how can i solve this problem.
the key thing is using 'invokeMethod',
# make some slot function for invokeMethod
#QtCore.Slot(str)
def test(self, value):
logging.getLogger('test').info(float(value))
def foo(self):
for i in tqdm(range(100)):
time.sleep(0.1)
# call invokeMethod
QtCore.QMetaObject.invokeMethod(self,
"test",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(str, str(i)))

How do I direct console output to a pyqt5 plainTextEdit widget with Python?

I am trying to display console output of a python script in a QplainTextEdit widget in PyQt5.
I am getting this error:
TypeError: Error when calling the metaclass bases
metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
I have defined my objects in the pyqt GUI file and I believe that I have all the imports.
Update
I have amended the code in this question:
from PyQt5.QtCore import QRectF, Qt
from PyQt5.QtWidgets import QFileDialog, QPlainTextEdit
from PyQt5 import QtCore, QtGui, QtWidgets
from PIL import Image, ImageQt, ImageEnhance
# from PyQt5.QtGui import Qt
from pyqtgraph.examples.text import text
from covid19gui_V3 import Ui_MainWindow
import os
import sys
input_img = Image.open("/home/ironmantis7x/Documents/Maverick_AI/Python/keras-covid-19/maverickAI30k.png")
text_edit = QPlainTextEdit()
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
textWritten = QtCore.pyqtSignal(str)
def __init__(self, parent=None, **kwargs):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.ShowIButton.clicked.connect(self.do_test)
self.chooseStudy.clicked.connect(self.do_choosestudy)
self.RunButton_3.clicked.connect(self.do_runstudy)
self.scene = QtWidgets.QGraphicsScene(self)
self.graphicsView.setScene(self.scene)
w, h = input_img.size
self.pixmap_item = self.scene.addPixmap(QtGui.QPixmap())
# self.graphicsView.fitInView(QRectF(0, 0, w, h), Qt.KeepAspectRatio)
self.graphicsView.update()
self.plainTextEdit.update()
self.level = 1
self.enhancer = None
self.timer = QtCore.QTimer(interval=500, timeout=self.on_timeout)
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
def write(self, text):
self.textWritten.emit(str(text))
#QtCore.pyqtSlot()
def do_test(self):
# input_img = Image.open("/home/ironmantis7x/Documents/Maverick_AI/Python/keras-covid-19/maverickAI30k.png")
self.enhancer = ImageEnhance.Brightness(input_img)
self.timer.start()
self.ShowIButton.setDisabled(True)
#QtCore.pyqtSlot()
def on_timeout(self):
if self.enhancer is not None:
result_img = self.enhancer.enhance(self.level)
qimage = ImageQt.ImageQt(result_img)
self.pixmap_item.setPixmap(QtGui.QPixmap.fromImage(qimage))
if self.level > 7:
self.timer.stop()
self.enhancer = None
self.level = 0
self.ShowIButton.setDisabled(False)
self.level = 1
self.ShowIButton.setDisabled(False)
#QtCore.pyqtSlot()
def do_choosestudy(self):
dlg = QFileDialog()
dlg.setFileMode(QFileDialog.AnyFile)
if dlg.exec_():
filenames = dlg.selectedFiles()
f = open(filenames[0], 'r')
#QtCore.pyqtSlot()
def do_runstudy(self):
os.system("df -h")
# filetext = open('screenout.txt').read()
# filetext.close()
# textViewValue = self.plainTextEdit.toPlainText()
# QPlainTextEdit.appendPlainText(self, str(textViewValue))
# sys.stdout = self(textWritten=self.textWritten)
self.normalOutputWritten(text_edit)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text_edit):
#cursor = self.plainTextEdit.textCursor()
#cursor.movePosition(QtGui.QTextCursor.End)
#cursor.insertText(text_edit)
self.plainTextEdit.appendPlainText(text_edit)
#self.plainTextEdit.ensureCursorVisible()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
How can I make this work correctly?
Update 2
I indeed DID do research into the topic and this is one of the main resources I used to try to solve the issue before I posted my question: How to capture output of Python's interpreter and show in a Text widget?
Update 3
I have revised my code in the post to reflect code suggestions in the link I used to help me with my issue.
I am still unable to get this to run correctly. I now get this error:
self.plainTextEdit.appendPlainText(text_edit) TypeError:
appendPlainText(self, str): argument 1 has unexpected type
'QPlainTextEdit'
I have a user interface, TableManagerWindow, that I've been maintaining and developing in Qt designer. After converting via pyuic to a *.py file, I was able to implement what Ferdinand Beyer had suggested in the link you provided above. Simple button to print text to terminal and it indeed does get appended to the QTextEdit widget via append(). Not sure this fits the bill for you for some reason, but I can vouch that it worked for me as well. I'm not savvy enough to get the nuance that is causing your issue, but figured I'd put this here just in case. Admins feel free to delete this if it's extraneous, but it works.
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
# Define a stream, custom class, that reports data written to it, with a Qt signal
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Ui_TableManagerWindow(object):
def setupUi(self, TableManagerWindow):
#define all of my widgets, layout, etc here
.
.
.
# Install a custom output stream by connecting sys.stdout to instance of EmmittingStream.
sys.stdout = EmittingStream(textWritten=self.output_terminal_written)
# Create my signal/connections for custom method
self.source_dir_button.clicked.connect(self.sourceDirButtonClicked)
self.retranslateUi(TableManagerWindow)
QtCore.QMetaObject.connectSlotsByName(TableManagerWindow)
def retranslateUi(self, TableManagerWindow):
.
.
.
#custom method that prints to output terminal. The point is to have this emmitted out to my QTextEdit widget.
def sourceDirButtonClicked(self):
for i in range(10):
print("The Source DIR button has been clicked " + str(i) + " times")
#custom method to write anything printed out to console/terminal to my QTextEdit widget via append function.
def output_terminal_written(self, text):
self.output_terminal_textEdit.append(text)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
TableManagerWindow = QtWidgets.QMainWindow()
ui = Ui_TableManagerWindow()
ui.setupUi(TableManagerWindow)
TableManagerWindow.show()
sys.exit(app.exec_())

window freezes when I try to show an evolving value in a Qt window using threads with python

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_())

buttonClicked signal of QMessageBox isn't working

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

PyQt5 threading GUI does not work

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.

Categories