Custom signals and multithreading - python

I've been trying to make this code work, but I still can't see where the flaw is.
I'm trying to emit the signal from a new thread, so the main receives the signal and executes a function.
If I try to do it within the same thread, everything works fine - but with this code, the thread is created, but the signal is never connected.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying"))
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()

Edited based on comments.
There is one main problem with your code. You're not actually starting the Qt application, so there is no main event loop running. You must add the following lines:
app = QApplication(sys.argv)
app.exec_()
Also, you should use the new-style Qt signals/slots if possible, or if you stick with the old-style, know that the Qt Signal should be in the form of a C function if you want it to also work with PySide. To change this to work with PySide, it would be QtCore.SIGNAL("trying()"), not QtCore.SIGNAL("trying"). See the comments (specifically mine and #ekhumoro's comments) for details.
Here's a working version of your code (using the old-style signals/slots), I tried to change the least amount possible so that you could see the small changes. I had to use PySide instead, but it should work with PyQt as well:
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.emit(QtCore.SIGNAL("trying()"))
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.connect(self.get_thread, QtCore.SIGNAL("trying()"), self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()
And here's a version using the new signal/slot style (see #three_pineapples comment). This is the recommended way to implement signals/slots in PyQt/PySide.
from PySide.QtCore import *
from PySide.QtGui import *
from PySide import QtCore
import sys
class WorkThread(QtCore.QThread):
ranThread = QtCore.Signal()
# for PyQt, use QtCore.pyqtSignal() instead
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
print("From thread")
self.ranThread.emit()
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
self.get_thread.quit()
self.get_thread.wait()
QApplication.quit()
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.ranThread.connect(self.handle_trigger)
self.get_thread.start()
a = Foo()
a.new_thread()
app = QApplication(sys.argv)
app.exec_()

I muddled through this myself and I think this will work for you.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4 import QtCore
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
tryThis = QtCore.Signal(str) #added this variable
def run(self):
print("From thread")
x = "trying"
self.tryThis.emit(QtCore.SIGNAL(x)) #pass your new variable like this
return
class Foo(QObject):
def handle_trigger(self):
print ("trigger signal received")
def new_thread(self):
self.get_thread = WorkThread()
self.get_thread.start()
self.get_thread.tryThis.connect(self.handle_trigger,QtCore.Qt.QueuedConnection) #passed it here
a = Foo()
a.new_thread()

Related

how to block signals reaching to custom slots

blocking signals to reach QObjects is fairly simple with using the QSignalBlocker Class
like
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.clickbuton.blockSignals(True)
what about custom slots like def printsomething(self): ?
trying the same operation but with blocking def printsomething(self): from printing
def blockprint(self):
self.printsomething.blockSignals(True)
will give a AttributeError: 'function' object has no attribute 'blockSignals'
it looks like this method works only for QObjects
how can I block def printsomething(self): from printing without using disconnect while its connected to the clicked signal?
code example
"""
Testing Template for throw away experiment
"""
import sys
import os
from PyQt5 import QtWidgets as qtw
from PyQt5 import QtCore as qtc
from PyQt5 import QtGui as qtg
class MainWindow(qtw.QWidget):
def __init__(self):
super().__init__()
# widget
self.clickbuton = qtw.QPushButton("click me")
# set the layout
layout = qtw.QVBoxLayout()
layout.addWidget(self.clickbuton)
self.setLayout(layout)
# functionality
self.clickbuton.clicked.connect(self.printsomething)
self.clickbuton.clicked.connect(self.blockprint)
def printsomething(self):
print("dude")
def blockprint(self):
self.printsomething.blockSignals(True)
# self.m_blocker = qtc.QSignalBlocker(self.clickbuton)
if __name__ == '__main__':
app = qtw.QApplication(sys.argv)
main = MainWindow()
main.show()
sys.exit(app.exec_())
What you're trying to do is to block a slot which is impossible.
You can only block signals of objects inherited from QObject.
As a solution you can instead of
def blockprint(self):
self.printsomething.blockSignals(True)
do this
def blockprint(self):
self.blockSignals(True)
that would block mainwindow signals until you turn blockSignals flag to false again.

Redirecting stdout from a secondary thread (multithreading with a function instead of class?)

I am trying to get my stdout displayed on a QTextEdit made via Qt Designer (PyQt5). Actually I made it work yet it doesn't show the info at the same time it was made. Instead it waits for the process to completely end and only then it shows all the information at once. I understand that this should be solved via threading. Also since the QTextEdit (itself) is a GUI element i need a different approach. I found the answer I was looking for here:
This question is referenced to:
Redirecting stdout and stderr to a PyQt4 QTextEdit from a secondary thread
#three_pineapples provided the answer.
My question is pretty much exactly the same, thus the answer is also correct. But my scenario is a little different and I'm having trouble making it work.
In all the threading answers I only see them using Classes. But the thing is in my main class I have a function that does all the stuff that would be printed on the QTextEdit. Sometimes it takes minutes to complete. I am looking for a way for the example code below to work using the answer provided by #three_pineapples.
Here is the example code:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.test_write)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self): # this is a long, complicated function. its nested in this class. I don't have a way to get it out as a different class.
print("something written")
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Is there a way to get the provided solution to work -directly- on the (test_write) function in my main class? How would I implement it?
To make it more clear, from the reference link "LongRunningThing" class is not available for me. The function that needs to run on a separate thread is within the main class (named Form() in the example code). Perhaps a nested class could be used that encapsulates the test_write function inside my main class? Is that even possible?
For this case you can use the native threading of python:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_text.clicked.connect(self.start_task)
self.finished.connect(lambda: self.ui.pushButton_text.setEnabled(True))
def start_task(self):
var = self.ui.lineEdit.text()
self.thread = threading.Thread(target=self.test_write, args=(args, ))
self.thread.start()
self.ui.pushButton_text.setEnabled(False)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def test_write(self, *args):
var1 = args[0]
print("something written")
time.sleep(5) # simulate expensive task
print("something written ----")
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()

PyQt5: Timer in a thread

Problem Description
I'm trying to make an application that collects data, processes it, displays it, and some actuation (open/close valves, etc). As a practice for future applications where I have some stricter time constraints, I want to run the data capture and processing in a separate thread.
My current problem is that it's telling me I cannot start a timer from another thread.
Current code progress
import sys
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QThread, pyqtSignal
# This is our window from QtCreator
import mainwindow_auto
#thread to capture the process data
class DataCaptureThread(QThread):
def collectProcessData():
print ("Collecting Process Data")
#declaring the timer
dataCollectionTimer = PyQt5.QtCore.QTimer()
dataCollectionTimer.timeout.connect(collectProcessData)
def __init__(self):
QThread.__init__(self)
def run(self):
self.dataCollectionTimer.start(1000);
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self) # gets defined in the UI file
self.btnStart.clicked.connect(self.pressedStartBtn)
self.btnStop.clicked.connect(self.pressedStopBtn)
def pressedStartBtn(self):
self.lblAction.setText("STARTED")
self.dataCollectionThread = DataCaptureThread()
self.dataCollectionThread.start()
def pressedStopBtn(self):
self.lblAction.setText("STOPPED")
self.dataCollectionThread.terminate()
def main():
# a new app instance
app = QApplication(sys.argv)
form = MainWindow()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Any advice on how to get this to work would be appreciated!
You have to move the QTimer to the DataCaptureThread thread, in addition to that when the run method ends, the thread is eliminated so the timer is eliminated, so you must avoid running that function without blocking other tasks. QEventLoop is used for this:
class DataCaptureThread(QThread):
def collectProcessData(self):
print ("Collecting Process Data")
def __init__(self, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dataCollectionTimer = QTimer()
self.dataCollectionTimer.moveToThread(self)
self.dataCollectionTimer.timeout.connect(self.collectProcessData)
def run(self):
self.dataCollectionTimer.start(1000)
loop = QEventLoop()
loop.exec_()

PyQt5 GUI only updates when screen is clicked off and back on

I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)

QtWebkit segfaults when passed a QObject

QtWebkit segfaults whenever I return a QObject from a method of another QObject.
Here's the simplest test case that I can come up with (it works for both PySide and PyQt4):
#!/usr/bin/env python2
import sys
try:
from PyQt4.QtCore import QObject, QVariant, pyqtProperty
from PyQt4.QtGui import QApplication
from PyQt4.QtWebKit import QWebView
Property = pyqtProperty
except ImportError:
from PySide.QtCore import QObject, Property
from PySide.QtGui import QApplication
from PySide.QtWebKit import QWebView
class TestObject(QObject):
#Property(str)
def property(self):
return 'foo'
class TestObjectContainer(QObject):
#Property(QObject) # Swapping QObject with TestObject doesn't fix it
def object(self):
return TestObject()
if __name__ == '__main__':
application = QApplication(sys.argv)
browser = QWebView()
frame = browser.page().mainFrame()
frame.addToJavaScriptWindowObject('container', TestObjectContainer())
frame.addToJavaScriptWindowObject('test_object', TestObject())
browser.show()
#frame.evaluateJavaScript('test_object.property')
frame.evaluateJavaScript('container.object.property')
sys.exit(application.exec_())
Here is the odd part:
test_object.property; // This doesn't segfault
container.object.property; // This does
Is there any way to return QObjects like this? Am I doing something wrong, or is this a bug?
I ended up creating a simple wrapper class with the help of #HYRY's answer:
class WebkitQObject(QObject):
def __init__(self):
super(WebkitQObject, self).__init__()
self.__cache__ = []
def store(self, item):
self.__cache__.append(item)
return self.__cache__[-1]
Now, this code works with a slight modification:
class TestObjectContainer(WebkitQObject):
#Property(QObject)
def object(self):
return self.store(TestObject())
You need a Python reference to the QObject to keep it alive. You can test this by changing the code as following,
class TestObjectContainer(QObject):
#Property(QObject)
def object(self):
self.tmp = TestObject()
return self.tmp

Categories

Resources