How to connect pyqtSignal between two different objects (classes) PROPERLY? I mean best practice.
Look what I have done to achieve the goal: The Thermometer class is notified when Pot increases its temperature:
from PyQt4 import QtCore
class Pot(QtCore.QObject):
temperatureRaisedSignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(Pot, self).__init__(parent)
self.temperature = 1
def Boil(self):
self.temperature += 1
self.temperatureRaisedSignal.emit()
def RegisterSignal(self, obj):
self.temperatureRaisedSignal.connect(obj)
class Thermometer():
def __init__(self, pot):
self.pot = pot
self.pot.RegisterSignal(self.temperatureWarning)
def StartMeasure(self):
self.pot.Boil()
def temperatureWarning(self):
print("Too high temperature!")
if __name__ == '__main__':
pot = Pot()
th = Thermometer(pot)
th.StartMeasure()
Or is there any easier / better way to do it?
I also insist (if possible) on using "new" style PyQt signals.
from PyQt4 import QtCore
class Pot(QtCore.QObject):
temperatureRaisedSignal = QtCore.pyqtSignal()
def __init__(self, parent=None):
QtCore.QObject.__init__(self)
self.temperature = 1
def Boil(self):
self.temperatureRaisedSignal.emit()
self.temperature += 1
class Thermometer():
def __init__(self, pot):
self.pot = pot
self.pot.temperatureRaisedSignal.connect(self.temperatureWarning)
def StartMeasure(self):
self.pot.Boil()
def temperatureWarning(self):
print("Too high temperature!")
if __name__ == '__main__':
pot = Pot()
th = Thermometer(pot)
th.StartMeasure()
This is how I would've done it according to the docs:
http://www.riverbankcomputing.com/static/Docs/PyQt4/html/new_style_signals_slots.html
Related
Does PySide2 signal emit waits for the receiving end to finish?
I always thought that emit just sends a message and ends there, but when I wrote this code I can see that isn't
from PySide2.QtCore import QObject, Signal, Slot
class MySignal(QObject):
data_signal = Signal(int)
class FirstClass:
def __init__(self):
self.sig = MySignal()
self.data_signal = self.sig.data_signal
def next_line(self):
self.data_signal.emit(1)
class SecondClass:
def __init__(self):
self.count = 0
self.first_class = FirstClass()
self.first_class.data_signal.connect(self.get_data)
self.get_data(1)
#Slot(int)
def get_data(self, data):
print(self.count)
if not self.count > 100:
self.first_class.next_line()
self.count += 1
sec = SecondClass()
I get a long list of 0 (the counter never increases)
and at the end this error
>>> RecursionError: maximum recursion depth exceeded while calling a Python object
What's the best to performe this in PySide2, I know the for this example if I just preformed a yield or return I wouldn't have this problem but I want to understand PySide2 signals and slots.
By default the connection type is Qt :: AutoConnection which decides the type of connection depending on which thread the sender and receiver belong to. In this case, they both belong to the same thread, so using the Qt :: DirectConnection connection that will invoke the slot at the same moment that the signal is emitted, that is, your code is equivalent to:
#Slot(int)
def get_data(self, data):
print(self.count)
if not self.count > 100:
self.get_data(1)
self.count += 1
And obviously that code is recursive since self.count += 1 never executed and therefore the while loop does not end.
The solution is to make the execution not direct but an instant later (when the eventloop is executed) for this the connection must be of type Qt::QueuedConnection and use QCoreApplication.
from PySide2.QtCore import QObject, Signal, Slot, QCoreApplication, Qt
class MySignal(QObject):
data_signal = Signal(int)
class FirstClass:
def __init__(self):
self.sig = MySignal()
self.data_signal = self.sig.data_signal
def next_line(self):
self.data_signal.emit(1)
class SecondClass:
def __init__(self):
self.count = 0
self.first_class = FirstClass()
self.first_class.data_signal.connect(self.get_data, Qt.QueuedConnection)
self.get_data(1)
#Slot(int)
def get_data(self, data):
print(self.count)
if not self.count > 100:
self.first_class.next_line()
self.count += 1
app = QCoreApplication()
sec = SecondClass()
app.exec_()
I have to program a table view that should be able to handle tens thousands of cells containing images (image is different for every cell). All in python using PySide2.
I implemented the loading of my images using a thread pool.
The problem is that I have to asynchronusly notify the view that the image has been loaded for a given index so it can reload the display. Using the dataChanged signal works but there are just too many of them to process and the UI does not show up until all the indexes are processed by the thread pool.
I provide a working example below that reproduces the issue (no images, juste text).
For now, I solved the problem by letting the threads sleep a little (just uncomment the time.sleep(1) line in the Work.run method) but it feels more like a dirty hack than a real solution to me.
I thought about the following solutions:
Try to make dataChanged signal behave asynchronusly. I suspect the default connection between dataChanged and whatever slot that update the view to be a AutoConnection. Is there any way to do this?
Gather the modified indexes in a buffer and update the view at regular time intervals. I would like to avoid this solution because finding a good time interval between two evaluation of the buffer is quite a hard task.
Do you have any other idea on how to avoid this blocking behavior?
Thank you for your advice!
import math
from random import choice
import string
import time
from PySide2.QtCore import QModelIndex
from PySide2.QtCore import Qt
from PySide2.QtCore import QObject
from PySide2.QtCore import Signal
from PySide2.QtCore import QThread
from PySide2.QtCore import QThreadPool
from PySide2.QtCore import QMutex
from PySide2.QtCore import QAbstractTableModel
from PySide2.QtWidgets import QTableView
class Notifier(QObject):
finished = Signal(QModelIndex, str)
class Work(QThread):
def __init__(self, *args, **kwargs):
super(Work, self).__init__(*args, **kwargs)
self.work = []
self._stopped = False
self._slept = False
def run(self):
while True:
try:
work = self.work.pop(0)
except IndexError:
work = None
if not work:
if self._slept:
break
self.msleep(500)
self._slept = True
continue
# Uncomment the following line to make the UI responsive
# time.sleep(1)
if work[0]:
c = ''.join(choice(string.ascii_uppercase + string.digits)
for _ in range(6))
work[0].finished.emit(work[1], c)
def reset(self):
self.work = []
self._stopped = True
class WorkPool(object):
def __init__(self):
self.thread_count = QThreadPool().maxThreadCount()
self.thread_pool = []
self.thread_cpt = 0
self.mutex = QMutex()
for c in range(0, self.thread_count):
self.thread_pool.append(Work())
def add_work(self, notifier, index):
new_thread = divmod(self.thread_cpt, self.thread_count)[1]
thread = self.thread_pool[new_thread]
self.thread_cpt += 1
thread.work.append((notifier, index))
if not thread.isRunning():
thread.start()
def terminate(self):
self.mutex.lock()
for t in self.thread_pool:
t.reset()
for t in self.thread_pool:
t.wait()
self.mutex.unlock()
class TableModel(QAbstractTableModel):
def __init__(self, items, *args, **kwargs):
super(TableModel, self).__init__(*args, **kwargs)
self.items = items
self.works = []
self.loader = WorkPool()
def index(self, row, column, parent=QModelIndex()):
pos = row * self.columnCount() + column
try:
return self.createIndex(row, column,self.items[pos])
except IndexError:
return QModelIndex()
def data(self, index, role):
if not index.isValid():
return None
if role == Qt.DisplayRole:
return index.internalPointer()
def columnCount(self, parent=QModelIndex()):
return 10
def rowCount(self, parent=QModelIndex()):
return int(math.ceil(float(len(self.items)) / self.columnCount()))
def refresh_content(self):
# Launch a thread to update the content of each index
for r in range(0, self.rowCount()):
for c in range(0, self.columnCount()):
index = self.index(r, c)
notifier = Notifier()
notifier.finished.connect(self.setData)
self.loader.add_work(notifier, index)
def setData(self, index, value):
if not index.isValid():
return False
self.items[index.row() * self.columnCount() + index.column()] = value
self.dataChanged.emit(index, index)
return True
class TableView(QTableView):
def closeEvent(self, *args, **kwargs):
self.model().loader.terminate()
super(TableView, self).closeEvent(*args, **kwargs)
if __name__ == '__main__':
from PySide2.QtWidgets import QApplication
app = QApplication([])
tv = TableView()
model = TableModel([None] * 99999)
tv.setModel(model)
model.refresh_content()
tv.show()
app.exec_()
Hi all and thanks for the help.
I am using a PyQt GUI to read and write voltages to mass flow controls and a heat flux gage. I am using QThreads to have a thread running in the background continuously updating the measurements displayed in the GUI. I am running into trouble having the background thread actually update the GUI as it does not have access to the GUI thread's functions. From what I have read online there is something about "moving" on thread into the other but am not sure what is meant by this. Can someone clarify this/point me in the right direction?
Here is my code:
from PyQt4 import QtGui, QtCore
import sys
import design4
import piplates.DAQC2plate as DAQC2
import time
import threading
air = 0.0
propane = 0
tolerance = .1
n = 1 #number of air controllers
class App4(QtGui.QMainWindow, design4.Ui_MainWindow):
def __init__(self, parent =None):
super(self.__class__, self).__init__()
self.setupUi(self)
self.sendFlow.clicked.connect(self.inputClicked)
self.displayFlow.clicked.connect(self.outputClicked)
self.hfButton.clicked.connect(self.heatFluxRead)
self.myThread = Worker()
self.myThread.start()
def inputClicked(self):
air = float(self.AirFlowInput.text())
propane = self.PropaneFlowInput.text()
if air <= 160 and air > 0:
acv = air / 20.0 / n *2 +.05#compute air control voltage for each controller
DAQC2.setDAC(0,0,acv) #changing voltage signal for channel 0
DAQC2.setDAC(0,1,acv)
else:
DAQC2.setDAC(0,0,0)
print DAQC2.getDAC(0,1)
print DAQC2.getDAC(0,0)
def outputClicked(self):
airRead = float(DAQC2.getADC(0,0)-.01) * 40 / n #converting 0-4Volts to flow
self.airFlowMeasured.display(airRead)
#print DAQC2.getADC(0,0)
#propRead = float(DAQC2.getADC(0,2)-.01) * 20
#self.propaneFlowMeasured.display(propRead)
#print DAQC2.getADC(0,1)
#palette = self.airFlowMeasured.palette()
#role = self.airFlowMeasured.backgroundRole()
#if float(DAQC2.getADC(0,0)) < (air*(1-tolerance):
# palette.setColor(role, QColor('yellow'))
#else if
def heatFluxRead(self):
heatFlux = float(DAQC2.getADC(0,7)) * 5800.0 #converts volts to kW/m^2
self.hfNumber.display(heatFlux)
print heatFlux
print self.getADC2(0,7)
def getADC2(self,addr,channel):
DAQC2.VerifyADDR(addr)
DAQC2.VerifyAINchannel(channel)
resp= DAQC2.ppCMD(addr,0x30,channel,0,2)
value=(256*resp[0]+resp[1])
if (channel==8):
value=value*5.0*2.4/65536
else:
value=(value*24.0/65536)-12.0
value=round(value*DAQC2.calScale[addr][channel]+DAQC2.calOffset[addr][channel],5)
return value
def main():
app = QtGui.QApplication(sys.argv)
form = App4()
form.show()
app.exec_()
class Update(QtCore.QObject):
sendUpdate = QtCore.pyqtSignal()
class Worker(QtCore.QThread):
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.initSignal()
def initSignal(self):
self.u = Update()
self.u.sendUpdate.connect(self.outputClicked)
def run(self):
while True:
self.u.sendUpdate.emit()
print 1
time.sleep(.25)
if __name__ == '__main__':
main()
Thanks a ton for any help!
I think that reading through DAQC2.getADC() function does not consume much time so it is not necessary to use threads, what you want to do is a periodic task, so the most appropriate option is to use QTimer:
class App4(QtGui.QMainWindow, design4.Ui_MainWindow):
def __init__(self, parent =None):
super(self.__class__, self).__init__()
self.setupUi(self)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.outputClicked)
self.timer.setInterval(250)
self.sendFlow.clicked.connect(self.inputClicked)
self.displayFlow.clicked.connect(self.timer.start)
self.hfButton.clicked.connect(self.heatFluxRead)
def inputClicked(self):
air = float(self.AirFlowInput.text())
propane = self.PropaneFlowInput.text()
if air <= 160 and air > 0:
acv = air / 20.0 / n *2 +.05#compute air control voltage for each controller
DAQC2.setDAC(0,0,acv) #changing voltage signal for channel 0
DAQC2.setDAC(0,1,acv)
else:
DAQC2.setDAC(0,0,0)
print DAQC2.getDAC(0,1)
print DAQC2.getDAC(0,0)
def outputClicked(self):
airRead = float(DAQC2.getADC(0,0)-.01) * 40 / n #converting 0-4Volts to flow
self.airFlowMeasured.display(airRead)
#print DAQC2.getADC(0,0)
#propRead = float(DAQC2.getADC(0,2)-.01) * 20
#self.propaneFlowMeasured.display(propRead)
#print DAQC2.getADC(0,1)
#palette = self.airFlowMeasured.palette()
#role = self.airFlowMeasured.backgroundRole()
#if float(DAQC2.getADC(0,0)) < (air*(1-tolerance):
# palette.setColor(role, QColor('yellow'))
#else if
def heatFluxRead(self):
heatFlux = float(DAQC2.getADC(0,7)) * 5800.0 #converts volts to kW/m^2
self.hfNumber.display(heatFlux)
print heatFlux
print self.getADC2(0,7)
def getADC2(self,addr,channel):
DAQC2.VerifyADDR(addr)
DAQC2.VerifyAINchannel(channel)
resp= DAQC2.ppCMD(addr,0x30,channel,0,2)
value=(256*resp[0]+resp[1])
if (channel==8):
value=value*5.0*2.4/65536
else:
value=(value*24.0/65536)-12.0
value=round(value*DAQC2.calScale[addr][channel]+DAQC2.calOffset[addr][channel],5)
return value
Im stuck in one situation where my first few signals are getting passed and later on no signals are getting emitted.
I'll elaborate it in detail:
I've a job which required heavy processing and ram and can take upto 2-4 hours to complete it without threading in linear way, so I decided to use QTheadPool, so I created (while testing) 355 QRunners which gets started by QThreadPool. And few of them(QRunners) are depends on another QRunners to finish. Its all working fine, I'm able to derive dependencies by emitting signals and catching them. Its all working absolutely perfect when I run this without GUI
For example (below codes are not tested, i just typed here):
from PyQt4 import QtCore
class StreamPool(QtCore.QObject):
def __init__(self, inputs, q_app):
super(....)
self.inputs = inputs
self.q_app = q_app
self.pool = QtCore.QThreadPool()
def start(self):
for each_input in self.inputs:
runner = StreamRunner(each_input, self.q_app)
runner.signal.operation_started.connect(self.mark_as_start)
runner.signal.operation_finished.connect(self.mark_as_start)
self.pool.start(runner)
def mark_as_start(self, name):
print 'operation started..', name
# Some operation...
def mark_as_finish(self, name):
print 'operation finished..', name
# Some operation...
class StreamRunner(QtCore.QRunnable):
def __init__(self, input, q_app):
super(..)
self.input = input
self.q_app = q_app
self.signal = WorkSignals()
def run(self):
self.signal.operation_started.emit(input)
self.q_ap
# Doing some operations
self.signal.operation_finished.emit(input)
self.q_app.processEvents()
class WorkSignals(QtCore.QObject):
operation_started = QtCore.pyqtSignal(str)
operation_finished= QtCore.pyqtSignal(str)
if __name__ == '__main__':
app = QtGui.QApplication([])
st = StreamPool(['a', 'b', 'c'], app)
st.start()
app.exec_()
It works brilliant in above case.
And I want to show the statuses in ui as there can be hundreds of task can be executed, so I wrote a simple ui, and running StreamPool() from another QThread lets say named - StreamWorkThread() which is getting spawned by ui, and StreamWorkThread catching StreamPool.signal.* and sending them back to ui, but in this case StreamPool can only emit few of them, the starting 4 or 5, though the tasks are still getting executed but the dependent task are not getting initialized due to this behavior and no status updates are getting displayed in ui.
I cant share the code with you guys, as its from my work place, I can write similar approach here
class StreamWorkThread (QtCore.QThread):
def __init__(self, inputs, q_app):
super(..)
self.signal = WorkSignals()
self.stream_pool = StreamPool(inputs, q_app)self.stream_pool.signal.operation_started.connect(self.signal.operation_started.emit)
self.stream_pool.signal.operation_finished.connect(self.signal.operation_finished.emit)
def run(self):
self.stream_pool.start()
def print_start(name):
print 'Started --', name
def print_finished(name):
print 'Finished --', name
if __name__ == '__main__':
app = QtGui.QApplication([])
th = StreamWorkThread (['a', 'b', 'c'], app)
th.signal.operation_started.connect(print_start)
th.signal.operation_finshed.connect(print_finished)
th.start()
app.exec_()
Consolidated code:
from PyQt4 import QtCore
class StreamPool(QtCore.QObject):
def __inti__(self, inputs, q_app):
super(StreamPool, self).__init()
self.inputs = inputs
self.q_app = q_app
self.pool = QtCore.QThreadPool()
def start(self):
for each_input in self.inputs:
runner = StreamRunner(each_input, self.q_app)
runner.signal.operation_started.connect(self.mark_as_start)
runner.signal.operation_finished.connect(self.mark_as_start)
self.pool.start(runner)
def mark_as_start(self, name):
print 'operation started..', name
# Some operation...
def mark_as_finish(self, name):
print 'operation finished..', name
# Some operation...
class StreamRunner(QtCore.QRunnable):
def __init__(self, input, q_app):
super(StreamRunner, self).__init()
self.input = input
self.q_app = q_app
self.signal = WorkSignals()
def run(self):
self.signal.operation_started.emit(input)
self.q_ap
# Doing some operations
self.signal.operation_finished.emit(input)
self.q_app.processEvents()
class WorkSignals(QtCore.QObject):
operation_started = QtCore.pyqtSignal(str)
operation_finished= QtCore.pyqtSignal(str)
class StreamWorkThread (QtCore.QThread):
def __init__(self, inputs, q_app):
super(StreamWorkThread, self).__init()
self.signal = WorkSignals()
self.stream_pool = StreamPool(inputs,q_app)
self.stream_pool.signal.operation_started.connect(self.signal.operation_started.emit)
self.stream_pool.signal.operation_finished.connect(self.signal.operation_finished.emit)
def run(self):
self.stream_pool.start()
def print_start(name):
print 'Started --', name
def print_finished(name):
print 'Finished --', name
if __name__ == '__main__':
app = QtGui.QApplication([])
th = StreamWorkThread (['a', 'b', 'c'], app)
th.signal.operation_started.connect(print_start)
th.signal.operation_finshed.connect(print_finished)
th.start()
app.exec_()
Please guys help me, im not getting what exactly the problem here..! :(
Okay, I got the solution for this behavior.
The root cause of blocking the signal is inheriting QObject in StrealPool, when I replace QObject with QThread it worked seamlessly.
Here are the changes I made, only tow places
class StreamPool(**QtCore.QThread**):
def __inti__(self, inputs, q_app):
super(StreamPool, self).__init()
self.inputs = inputs
self.q_app = q_app
self.pool = QtCore.QThreadPool()
def **run**(self):
for each_input in self.inputs:
runner = StreamRunner(each_input, self.q_app)
runner.signal.operation_started.connect(self.mark_as_start)
runner.signal.operation_finished.connect(self.mark_as_start)
self.pool.start(runner)
and that set, it worked ! :D
very difficult without the source code, but the problem is probably when "app.exec_()" is run, the mainloop of the gui is started and interferes with your Streamx classes
I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.
My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?
I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!
Thanks for any help that anybody can provide!
Main File:
import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *
class StartMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartMainWindow, self).__init__(parent)
self.ui = TestGUI()
self.ui.setupUi(self)
self.ui.serialTextEditBox.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
# print some debug statements to console
if (event.key() == QtCore.Qt.Key_Tab):
print ('Tab pressed')
print ('key pressed: %s' % event.text())
print ('code pressed: %d' % event.key())
# do i emit a signal here? how do i catch it in thread?
self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
return True
return QtGui.QTextEdit.eventFilter(self, source, event)
def serialConnectCallback(self):
self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
self.miniterm.start()
temp = self.SerialSettings.Port + 1
self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Cleanlooks")
myapp = StartMainWindow()
myapp.show()
sys.exit(app.exec_())
SerialMiniterm.py:
import serial
from PyQt4 import QtGui, QtCore
def character(b):
return b
class SerialMiniterm(object):
def __init__(self, ui, SerialSettings):
self.SerialSettings = SerialSettings
self.ui = ui
self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
self.repr_mode = self.SerialSettings.RxMode
self.convert_outgoing = self.SerialSettings.NewlineMode
self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
self.dtr_state = True
self.rts_state = True
self.break_state = False
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
self.receiver_thread.join()
def updateTextBox(self, q):
self.ui.serialTextEditBox.insertPlainText(q)
self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
#print "got here with value %s..." % q
def start(self):
self.alive = True
self._start_reader()
# how do i handle transmitter thread?
def stop(self):
self.alive = False
def join(self, transmit_only=False):
self.transmitter_thread.join()
if not transmit_only:
self.receiver_thread.join()
class ReaderThread(QtCore.QThread):
def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
QtCore.QThread.__init__(self, parent)
self.alive = alive
self._reader_alive = _reader_alive
self.repr_mode = repr_mode
self.convert_outgoing = convert_outgoing
self.serial = serial
def __del__(self):
self.wait()
def run(self):
"""loop and copy serial->console"""
while self.alive and self._reader_alive:
data = self.serial.read(self.serial.inWaiting())
if data: #check if not timeout
q = data
self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)
Something like this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Terminal(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
print event.text()
return QtGui.QPlainTextEdit.keyPressEvent(self, event)
term = Terminal()
term.show()