How to debug pyqt signal/slot connections, probably threading issue - multithreading

I have the following piece of example code of my problem. Running this, I would expect that (if you type something in the lineedit) the A.updateValue slot would be called twice and thus show 'a.updatevalue called' and 'a2.updatevalue called'
However, it is only called once, namely for the self.a2 object and not for the self.a object, the latter which is sent from a worker thread to the GUI thread. How can I fix this so that this piece of code also triggers the slot for the self.a object?
Thank you,
David
import os, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class A(QObject):
def __init__(self, name):
QObject.__init__(self)
self.name = name
def updateValue(self, value):
print(self.name + ".updatevalue called")
class workerthread(QThread):
def __init__(self, parent=None):
QThread.__init__(self, parent)
def run(self):
a = A('a')
QObject.emit(self, SIGNAL("mySignal"), a)
class Main(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.centralwidget = QWidget(self)
self.hbox = QHBoxLayout()
self.centralwidget.setLayout(self.hbox)
def update(self, a):
self.a = a
edit = QLineEdit("", self)
self.hbox.addWidget(edit)
edit.textChanged.connect(self.a.updateValue)
self.a2 = A('a2')
edit.textChanged.connect(self.a2.updateValue)
if __name__ == "__main__":
app = QApplication(sys.argv)
gui = Main()
worker = workerthread()
worker.connect(worker, SIGNAL('mySignal'), gui.update)
worker.start()
gui.show()
sys.exit(app.exec_())

Define a when you initialize workerThread
class workerthread(QThread):
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.a = A('a')
def run(self):
QObject.emit(self, SIGNAL("mySignal"), self.a)

Related

How to let sibling windows accept event?

I write a simple window, when cursor in QLineEdit and press Enter Key, I want the QGraphicsRectItem, QGraphicsScene, QGraphicsView and QWidget also accept QKeyEvent or MyEvent(customize event).I have no idea to do it,Could someone have good method to do this?
Code Sample
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class MyEvent(QEvent):
Type = QEvent.registerEventType()
def __init__(self):
super().__init__(MyEvent.Type)
self._data = "test"
class Item(QGraphicsRectItem):
def __init__(self):
super().__init__()
self.setRect(0 ,0, 100, 100)
self.setBrush(Qt.red)
self.setFlags(QGraphicsItem.ItemIsFocusable)
def keyPressEvent(self, event: QKeyEvent) -> None:
print("Item KeyPress", event.key())
return super().keyPressEvent(event)
class Scene(QGraphicsScene):
def keyPressEvent(self, event: QKeyEvent) -> None:
print("Scene KeyPress", event.key())
return super().keyPressEvent(event)
class View(QGraphicsView):
def keyPressEvent(self, event: QKeyEvent) -> None:
print("View KeyPress", "do something work here", event.key())
return super().keyPressEvent(event)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
lay = QVBoxLayout()
view = View()
scene = Scene()
scene.addItem(Item())
view.setScene(scene)
lay.addWidget(view)
lay.addWidget(QLineEdit("Cursor In here, post Enter Event to QGraphicsView"))
self.setLayout(lay)
self.show()
self.view = view
def keyPressEvent(self, e: QKeyEvent) -> None:
print("QWidget KeyPress", e.key())
# myEvent = MyEvent()
# QApplication.postEvent(myEvent)
return super().keyPressEvent(e)
app = QApplication([])
m = MainWindow()
app.exec()
How let others item also get the event?

PyQt5 multithreading: How to combine output of multiple threads

I am trying to build a Python GUI using PyQt5 which incorporates multi-threading. Using various snippets of code (acquired mainly from Stack Overflow), I have come up with the code below which reads data from a csv file into a pandas dataframe and updates the GUI (triggered from a QTimer).
I would like to implement a 2nd thread which reads from a different csv file and a function which accepts both dataframes and does some computation on them before updating the GUI. I'm not sure how I can do this within the signals/slots framework of PyQt5. Any help is much appreciated!
import time
import traceback
import sys
import pandas as pd
import numpy as np
from PyQt5.QtWidgets import (QWidget, QTabWidget, QGridLayout, QApplication, QMainWindow, QStatusBar, QTableView)
from PyQt5.QtCore import (Qt, QTimer, QAbstractTableModel, QThread, QVariant, QObject, QRect, pyqtSlot, pyqtSignal)
class PandasModel(QAbstractTableModel):
"""
Class to populate a table view with a pandas dataframe
"""
def __init__(self, data, parent=None):
QAbstractTableModel.__init__(self, parent)
self._data = np.array(data.values)
self._cols = data.columns
self.r, self.c = np.shape(self._data)
def rowCount(self, parent=None):
return self.r
def columnCount(self, parent=None):
return self.c
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.DisplayRole:
return self._data[index.row(), index.column()]
return None
def headerData(self, p_int, orientation, role):
if role == Qt.DisplayRole:
try:
if orientation == Qt.Horizontal:
return self._cols[p_int]
elif orientation == Qt.Vertical:
return p_int
except(IndexError, ):
return QVariant()
else:
return QVariant()
class WorkerSignals(QObject):
"""
Defines the signals available from a running worker thread.
Supported signals are:
finished: No data
error: `tuple` (exctype, value, traceback.format_exc() )
result: `object` data returned from processing, anything
progress: `int` indicating % progress
"""
finished = pyqtSignal()
error = pyqtSignal(tuple)
result = pyqtSignal(object)
progress = pyqtSignal(int)
class Worker(QObject):
"""
Worker thread
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
:param callback: The function callback to run on this worker thread. Supplied args and
kwargs will be passed through to the runner.
:type callback: function
:param args: Arguments to pass to the callback function
:param kwargs: Keywords to pass to the callback function
"""
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
self.signals = WorkerSignals()
# Add the callback to our kwargs
# kwargs['progress_callback'] = self.signals.progress
#pyqtSlot()
def run(self):
"""
Initialise the runner function with passed args, kwargs.
"""
# Retrieve args/kwargs here; and fire processing using them
try:
result = self.fn(*self.args, **self.kwargs)
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
self.signals.result.emit(result) # Return the result of the processing
finally:
self.signals.finished.emit() # Done
class MyTabWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tab screen
self.tabs = QTabWidget()
self.tab1 = QWidget()
self.tabs.resize(300, 200)
# Add tabs
self.tabs.addTab(self.tab1, "Tab 1")
# Create tabs
self.tab1.layout = QGridLayout(self)
self.tab1.setLayout(self.tab1.layout)
# Create table widgets
self.table_widget1 = MyTableWidget(self)
# Add tables to tabs
self.tab1.layout.addWidget(self.table_widget1)
# Add tabs to widget
self.layout.addWidget(self.tabs)
self.setLayout(self.layout)
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QGridLayout(self)
# Initialize tables
self.table = QTableView()
# Add tabs to widget
self.layout.addWidget(self.table)
self.setLayout(self.layout)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
# Create a central Widgets
self.central_widget = QWidget()
# Create tab Widgets
self.tab_widget = MyTabWidget(self)
# Create a Layout for the central Widget
self.central_layout = QGridLayout()
# Set the Layout
self.central_widget.setLayout(self.central_layout)
# Set the Widget
self.setCentralWidget(self.central_widget)
self.central_layout.addWidget(self.tab_widget)
self.setGeometry(QRect(0, 100, 2000, 1500))
self.setWindowTitle('CSV viewer')
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
self.proxy = None
self.model = None
self.show()
# Setup worker thread
self.worker_thread = QThread(self)
self.worker = Worker(self.read_csv)
self.worker.moveToThread(self.worker_thread)
self.worker_thread.started.connect(self.worker.run)
# Connect worker signals to appropriate ports
self.worker.signals.result.connect(self.process_csv)
self.worker.signals.finished.connect(self.worker_thread.quit)
self.worker.signals.finished.connect(self.print_refresh_time)
# Setup timer to repetitively trigger start of worker thread e.g. every 5000ms
self.timer = QTimer()
self.timer.timeout.connect(self.worker_thread.start)
self.timer.start(5000)
def process_csv(self, df):
self.model = PandasModel(df)
self.tab_widget.table_widget1.table.setModel(self.model)
self.tab_widget.table_widget1.table.resizeColumnsToContents()
def print_refresh_time(self):
self.status_bar.showMessage('Last updated: ' + time.strftime("%d/%m/%Y %H:%M:%S", time.localtime()))
#staticmethod
def read_csv():
df = pd.read_csv("C:\\Users\\Brian\\Desktop\\E0.csv")
return df
def main():
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
if __name__ == '__main__':
main()

PySide QThread moveToThread subclass locks up main thread

I'm having an issue when I try to subclass my worker class the main UI thread locks up and becomes unresponsive until the Worker Thread has finished. I'm at a loss here and suspect something with how the GIL interacts with inheritance?
In MainWindow.clicked() these two lines:
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
Here is my sample code that reproduces this:
from PySide import QtGui, QtCore
import sys
import time
class Worker(QtCore.QObject):
started = QtCore.Signal()
progress = QtCore.Signal(int)
finished = QtCore.Signal(bool)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.lock = QtCore.QMutex()
#QtCore.Slot()
def start(self):
self.flag_to_quit = False
try:
self.started.emit()
time.sleep(5)
time.sleep(0.5)
for i in range(10):
self.progress.emit(i)
time.sleep(1)
with QtCore.QMutexLocker(self.lock):
print(self.flag_to_quit)
if self.flag_to_quit:
raise RuntimeError("Requested to quit.")
self.finished.emit(True)
except Exception as ex:
print(ex)
self.finished.emit(False)
finally:
print("start() finished!")
##QtCore.Slot()
def stop(self):
print("Stopping...")
with QtCore.QMutexLocker(self.lock):
self.flag_to_quit = True
#QtCore.Slot()
def __quit_handler(self):
self.flag_to_quit = True
class SubWorker(Worker):
def __init__(self):
Worker.__init__(self)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.label = QtGui.QLabel("")
self.layout().addWidget(self.label)
self.button = QtGui.QPushButton("Start Thread")
self.layout().addWidget(self.button)
self.button.clicked.connect(self.clicked)
def closeEvent(self, event):
if hasattr(self, "_thread") and self._thread:
self.worker.flag_to_quit = True
self._thread.quit()
print("waiting for stop...")
self._thread.wait()
self._thread.deleteLater()
#QtCore.Slot()
def worker_started(self):
self.label.setText("Started!")
#QtCore.Slot(int)
def worker_progress(self, interval):
self.label.setText(str(interval))
#QtCore.Slot()
def worker_finished(self, success):
msg = "Finished "
msg += "successfully!" if success else "with errors"
self.label.setText(msg)
#QtCore.Slot()
def clicked(self):
if hasattr(self, "_thread") and self._thread:
self.worker.stop()
self._thread.quit()
print("waiting for stop...")
self._thread.wait()
self._thread.quit()
self._thread.deleteLater()
self._thread = QtCore.QThread()
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
self.worker.started.connect (self.worker_started, QtCore.Qt.QueuedConnection)
self.worker.progress.connect(self.worker_progress, QtCore.Qt.QueuedConnection)
self.worker.finished.connect(self.worker_finished, QtCore.Qt.QueuedConnection)
self.worker.moveToThread(self._thread)
self._thread.started.connect(self.worker.start)
self._thread.finished.connect(self.worker.stop)
self._thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
sys.exit()
Version information:
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
edit:
Looks like QThread::moveToThread() is failing with Subclasses. this is output of QtCore.QThread.currentThread() in various places:
#self.worker = Worker():
Main:<PySide.QtCore.QThread object at 0x7ff89df384d0>
Real thread: <PySide.QtCore.QThread object at 0x7ff89df383f8>
Worker:<PySide.QtCore.QThread object at 0x7ff89df384d0>
#self.worker = SubWorker()
Main:<PySide.QtCore.QThread object at 0x7f968602c3b0>
Real thread: <PySide.QtCore.QThread object at 0x7f968602c488>
Worker:<PySide.QtCore.QThread object at 0x7f968602c488>
Okay I managed to fix this by doing it the "Wrong" way by inheriting QThread and overriding the run method. I'm not sure why or how but it works.
from PySide import QtGui, QtCore
import sys
import time
class Worker(QtCore.QThread):
_started = QtCore.Signal()
progress = QtCore.Signal(int)
_finished = QtCore.Signal(bool)
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.lock = QtCore.QMutex()
#QtCore.Slot()
def run(self):
self.flag_to_quit = False
print("Worker:" + str(QtCore.QThread.currentThread()))
try:
self._started.emit()
time.sleep(2)
for i in range(10):
self.progress.emit(i)
time.sleep(1)
with QtCore.QMutexLocker(self.lock):
if self.flag_to_quit:
raise RuntimeError("Requested to quit.")
self._finished.emit(True)
except Exception as ex:
print(ex)
self._finished.emit(False)
finally:
print("start() finished!")
#QtCore.Slot()
def stop(self):
with QtCore.QMutexLocker(self.lock):
self.flag_to_quit = True
#QtCore.Slot()
def __quit_handler(self):
self.flag_to_quit = True
class SubWorker(Worker):
def __init__(self, parent=None):
Worker.__init__(self, parent)
#QtCore.Slot()
def start(self):
Worker.start(self)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.label = QtGui.QLabel("")
self.layout().addWidget(self.label)
self.button = QtGui.QPushButton("Start Thread")
self.layout().addWidget(self.button)
self.button.clicked.connect(self.clicked)
def closeEvent(self, event):
self.hide()
if hasattr(self, "worker") and self.worker:
self.worker.stop()
self.worker.quit()
self.worker.wait()
self.worker.deleteLater()
#QtCore.Slot()
def worker_started(self):
self.label.setText("Started!")
#QtCore.Slot(int)
def worker_progress(self, interval):
self.label.setText(str(interval))
#QtCore.Slot()
def worker_finished(self, success):
msg = "Finished "
msg += "successfully!" if success else "with errors!"
self.label.setText(msg)
def clicked(self):
if hasattr(self, "worker") and self.worker:
self.worker.stop()
self.worker.quit()
self.worker.wait()
self.worker.deleteLater()
print("Main:" + str(QtCore.QThread.currentThread()))
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
self.worker._started.connect (self.worker_started)
self.worker.progress.connect(self.worker_progress)
self.worker._finished.connect(self.worker_finished)
self.worker.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
sys.exit()

PySide: Threading causes GUI to crash

I'm trying to learn the basics of threading with PySide, and so put together the below code. What I'm trying to do is launch a thread that will update a QPlainTextEdit widget using a list of string, with a delay between each string. Instead what I'm getting is a crash to desktop, and I can't understand why:
import sys
import time
from PySide import QtCore, QtGui
class Worker(QtCore.QThread):
to_log = QtCore.Signal(str)
def __init__(self, txt, parent=None):
super(Worker, self).__init__(parent)
self.txt = txt
def run(self):
for i in self.txt:
self.to_log.emit(i)
time.sleep(1)
class TestThreadsApp(QtGui.QWidget):
def __init__(self):
super(TestThreadsApp, self).__init__()
self.initUI()
def initUI(self):
self.log = QtGui.QPlainTextEdit()
self.pb = QtGui.QPushButton('Go')
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.log)
hbox.addWidget(self.pb)
self.setLayout(hbox)
self.setGeometry(300, 300, 300, 150)
self.setWindowTitle('Test')
self.show()
self.pb.clicked.connect(self.get_worker)
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
def to_log(self, txt):
self.log.appendPlainText(txt)
def main():
app = QtGui.QApplication(sys.argv)
ex = TestThreadsApp()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
If I update the get_worker() method to the below, it will run, but the QPlainTextEdit widget is updated with all the strings simultaneously, where as the behavior I'm wanting is for the widget to be updated by the threaded processes as each string is emitted - not altogether after both have been emitted:
def get_worker(self):
self.proceed = False
worker = Worker(['This is a test', 'to understand threading'])
worker.to_log.connect(self.to_log)
worker.start()
while not worker.isFinished():
pass
You need to keep a reference to the thread, otherwise it will be garbage-collected as soon as get_worker returns.
So do something like this, instead:
def get_worker(self):
self.worker = Worker(['This is a test', 'to understand threading'])
self.worker.to_log.connect(self.to_log)
self.worker.start()

How to avoid the child QMainWindow disappearing?

When I click the button, "ChildWindow" will flash and disappear. Then If I add the function exec_(), it will report an "AttributeError" because "QMainWindow" don't have this attribute.
How to modify the function "showChildWindow" so that it can work well? Thank you.
import sys
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle("MainWindow Window!")
self.setGeometry(400, 400, 100, 100)
self.centerWidget = QtGui.QWidget()
self.setCentralWidget(self.centerWidget)
self.pushButton = QtGui.QPushButton("&Button")
layout = QtGui.QVBoxLayout()
layout.addWidget(self.pushButton)
self.centerWidget.setLayout(layout)
class ChildWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle("Child Window!")
def showChildWindow():
mw = MainWindow()
child_win = ChildWindow(mw)
child_win.show()
#child_win.exec_() #AttributeError: 'ChildWindow' object has no attribute 'exec_'
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
QtCore.QObject.connect(myapp.pushButton,QtCore.SIGNAL("clicked()"),showChildWindow)
sys.exit(app.exec_())
This program can work well.
import sys
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle("MainWindow Window!")
self.setGeometry(400, 400, 100, 100)
self.centerWidget = QtGui.QWidget()
self.setCentralWidget(self.centerWidget)
pushButton = QtGui.QPushButton("&Button")
layout = QtGui.QVBoxLayout()
layout.addWidget(pushButton)
self.centerWidget.setLayout(layout)
QtCore.QObject.connect(pushButton,QtCore.SIGNAL("clicked()"),self.showChildWindow)
def showChildWindow(self):
self.child_win = ChildWindow(self)
self.child_win.show()
class ChildWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle("Child Window!")
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myapp = MainWindow()
myapp.show()
sys.exit(app.exec_())
The real question appears to be: how do I access the main-window from other modules in my application?
My preferred general solution to this problem is to sub-class QApplication and add a simple accessor method, like this:
class Application(QtGui.QApplication):
def __init__(self):
QtGui.QApplication.__init__(self, sys.argv)
self._window = None
def window(self):
if self._window is None:
self._window = MainWindow()
return self._window
if __name__ == "__main__":
app = QtGui.qApp = Application()
app.window().show()
sys.exit(app.exec_())
With that in place, you can easily get access the methods of the main-window in other modules like this:
from PyQt4 import QtGui
QtGui.qApp.window().showChildWindow()
I had the same problem. The child QMainWindow would randomly disappear while interacting with the parent window.
This is the bad source code that causes this problem:
def showChildWindow(self):
child_win = ChildWindow()
child_win.show()
This is the good source code that fixes the problem:
def showChildWindow(self):
child_win = ChildWindow(self)
child_win.show()
When you instantiate the child window, make sure you pass the parent self.

Resources