How to use Slot+Signal in different classes? - python

I must say it is a very beginner's question. I have read and tried a lot, but still don't understand how Slot+Signal work.
In my following code I want to transfer three variables from MyApp Class into Worker Class, when the button is clicked.
The code does not work.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Worker(QObject):
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
#pyqtSlot(str, str, int)
def onJob(self, strA, strB, int1):
print(strA, strB, int1)
for i in range(40):
print(i)
class MyApp(QWidget):
def __init__(self, parent= None):
super(MyApp, self).__init__(parent)
self.initUI()
def initUI(self):
self.btn = QPushButton("start", self)
self.btn.clicked.connect(self.start)
self.show()
def start(self):
otherClass = Worker()
self.signal = pyqtSignal(str, str, int)
self.signal.emit("foo", "baz", 10)
self.signal.connect(otherClass.onJob)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyApp()
window.show()
sys.exit(app.exec_())

Your code has the following errors:
A signal must not be declared in any method of the class, it must be on the same level as the methods.
If I send a signal before connecting it to any slot then nobody will listen to the information so the data will be lost, that is, the transmission of data is almost instantaneous.
In the following code I have implemented the necessary modifications to make it work:
class MyApp(QWidget):
signal = pyqtSignal(str, str, int)
def __init__(self, parent= None):
super(MyApp, self).__init__(parent)
self.initUI()
def initUI(self):
self.btn = QPushButton("start", self)
self.btn.clicked.connect(self.start)
self.show()
def start(self):
otherClass = Worker()
self.signal.connect(otherClass.onJob)
self.signal.emit("foo", "baz", 10)

Related

PYQT5 Gui crashes sometimes when I start a thread [duplicate]

I have the following code but it's complaining that I cannot access the UI data from my thread. In my example code below, What is the best way I can access the userInputString value so my threading can run?
self.nameField is a PyQt QLineEdit.
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
import myUI
class MainUIClass(QtGui.QMainWindow, myUI.Ui_MainWindow):
def __init__(self, parent=None):
super(MainUIClass, self).__init__(parent)
self.setupUi(self)
self.startbutton.clicked.connect(self.do_work)
self.workerThread = WorkerThread()
self.connect(self.workerThread, SIGNAL("myThreading()"), self.myThreading, Qt.DirectConnection)
def do_work(self):
self.userInputString = self.nameField.Text()
self.workerThread.start()
def myThreading(self):
if userInputString is not None:
#Do something
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
self.emit(SIGNAL("myThreading()"))
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
app = MainUIClass()
app.show()
a.exec_()
Not sure if it's what you need but here is a working QThread exemple using Qt5
import time
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkerThread()
self.worker_thread.job_done.connect(self.on_job_done)
self.create_ui()
def create_ui(self):
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.start_thread)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def start_thread(self):
self.worker_thread.gui_text = self.button.text()
self.worker_thread.start()
def on_job_done(self, generated_str):
print("Generated string : ", generated_str)
self.button.setText(generated_str)
class WorkerThread(QtCore.QThread):
job_done = QtCore.pyqtSignal('QString')
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.gui_text = None
def do_work(self):
for i in range(0, 1000):
print(self.gui_text)
self.job_done.emit(self.gui_text + str(i))
time.sleep(0.5)
def run(self):
self.do_work()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
app.exec_()

PyQt5 signal emit between two class

I am just trying to send a signal from the class A to class B but it does not work, I don't see the print.
I am certainly doing something wrong but I did know what. Here is a quick code to show you the problem, thank you.
from PyQt5 import QtWidgets, QtCore
import sys
class B(object):
def __init__(self, parent=None):
super(B, self).__init__(parent)
self._initSlot()
def _initSlot(self):
a = A()
a.assetSelectionChanged.connect(self._doSomething)
#QtCore.pyqtSlot()
def _doSomething(self):
print('do something')
class A(QtWidgets.QWidget):
assetSelectionChanged = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(A, self).__init__(parent)
self._initUI()
def _initUI(self):
self.treeWidgetAssets = QtWidgets.QTreeWidget()
for i in range(1, 11, 1):
QtWidgets.QTreeWidgetItem(self.treeWidgetAssets, [str(i)])
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
self.mainLayout.addWidget(self.treeWidgetAssets)
self.treeWidgetAssets.itemSelectionChanged.connect(self.onAssetSelectionChanged)
def onAssetSelectionChanged(self):
self.assetSelectionChanged.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
dlg = A()
dlg.show()
sys.exit(app.exec_())
There are various problems with that code:
No instance of B is ever created
direct object subclasses don't require calling super().__init__(), and even if you do that you should certainly not add arbitrary arguments, as they won't be accepted, resulting in a crash
B should inherit from QObject
No new instance should be declared in _initSlot: there already exists one, and the one you're creating there will be immediately garbage collected because its reference is local and will be deleted immediately after _initSlot returns
class B(QtCore.QObject):
def __init__(self, parent=None):
super(B, self).__init__(parent)
self._initSlot()
def _initSlot(self):
if self.parent():
self.parent().assetSelectionChanged.connect(self._doSomething)
#QtCore.pyqtSlot()
def _doSomething(self):
print('do something')
class A(QtWidgets.QWidget):
assetSelectionChanged = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(A, self).__init__(parent)
self._initUI()
self.b = B(self)
# ...
As an variant. I've marked the lines that I changed for you.
import sys
from PyQt5 import QtWidgets, QtCore
class B(QtCore.QObject):
def __init__(self, parent=None):
super(B, self).__init__(parent)
# self._initSlot()
# def _initSlot(self):
# a = A()
# a.assetSelectionChanged.connect(self._doSomething)
#QtCore.pyqtSlot(str)
def _doSomething(self, text):
print(f'do something: clicked -> {text}')
class A(QtWidgets.QWidget):
assetSelectionChanged = QtCore.pyqtSignal(str) # 1. + str
def __init__(self, parent=None):
super(A, self).__init__(parent)
self._initUI()
self.b = B(self) # !!!
self.assetSelectionChanged[str].connect(self.b._doSomething) # 3. + str
def _initUI(self):
self.treeWidgetAssets = QtWidgets.QTreeWidget()
for i in range(1, 11, 1):
QtWidgets.QTreeWidgetItem(self.treeWidgetAssets, [str(i)])
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
self.mainLayout.addWidget(self.treeWidgetAssets)
self.treeWidgetAssets.itemSelectionChanged.connect(self.onAssetSelectionChanged)
def onAssetSelectionChanged(self):
text = self.treeWidgetAssets.selectedItems()[0].text(0)
self.assetSelectionChanged.emit(text) # 2. + text
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
dlg = A()
dlg.show()
sys.exit(app.exec_())

PYQT5 crashing when calling .text() on a QLineEdit widget

When I run the code below my program crashes, and I believe this is to-do with the .text() called when the Line edit has something typed in it. I need to assign a variable to what is entered here.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
usernameBox = QtWidgets.QLineEdit()
usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = usernameBox.text()
print(x)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = loginScreen()
sys.exit(app.exec_())
If you observe usernameBox it is created as a local variable so it can not be accessed by the other methods of the class, in your case there are 2 solutions:
Make a usernameBox attribute of the class.
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.usernameBox = QtWidgets.QLineEdit()
self.usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(self.usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = self.usernameBox.text()
print(x)
Or use sender() that obtains the object that emits the signal, in your case the QLineEdit.
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
usernameBox = QtWidgets.QLineEdit()
usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = self.sender().text()
print(x)

PyQt: How to get UI data from a QThread

I have the following code but it's complaining that I cannot access the UI data from my thread. In my example code below, What is the best way I can access the userInputString value so my threading can run?
self.nameField is a PyQt QLineEdit.
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
import myUI
class MainUIClass(QtGui.QMainWindow, myUI.Ui_MainWindow):
def __init__(self, parent=None):
super(MainUIClass, self).__init__(parent)
self.setupUi(self)
self.startbutton.clicked.connect(self.do_work)
self.workerThread = WorkerThread()
self.connect(self.workerThread, SIGNAL("myThreading()"), self.myThreading, Qt.DirectConnection)
def do_work(self):
self.userInputString = self.nameField.Text()
self.workerThread.start()
def myThreading(self):
if userInputString is not None:
#Do something
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
self.emit(SIGNAL("myThreading()"))
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
app = MainUIClass()
app.show()
a.exec_()
Not sure if it's what you need but here is a working QThread exemple using Qt5
import time
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkerThread()
self.worker_thread.job_done.connect(self.on_job_done)
self.create_ui()
def create_ui(self):
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.start_thread)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def start_thread(self):
self.worker_thread.gui_text = self.button.text()
self.worker_thread.start()
def on_job_done(self, generated_str):
print("Generated string : ", generated_str)
self.button.setText(generated_str)
class WorkerThread(QtCore.QThread):
job_done = QtCore.pyqtSignal('QString')
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.gui_text = None
def do_work(self):
for i in range(0, 1000):
print(self.gui_text)
self.job_done.emit(self.gui_text + str(i))
time.sleep(0.5)
def run(self):
self.do_work()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
app.exec_()

how to get subclassed QWidgets from QToolBox?

I wrote a few customized widget classes by subclassing QWidget. I created a few customized widgets using them and added them to a QToolBox.
class BaseWidget(QtGui.QWidget):
def __init__(self, parent=None):
# initialize
def mymethod():
pass
class AWidget(BaseWidget):
def __init__(self, parent=None):
# initialize
def mymethod():
print "A"
class BWidget(BaseWidget):
def __init__(self, parent=None):
# initialize
def mymethod():
print "B"
Now I want to loop over all the widgets added to the QToolBox and call a method of these customized widgets:
class toolboxWidget(QtGui.QToolBox):
def __init__(self, parent=None):
super(toolboxWidget, self).__init__(parent=parent)
a = AWidget(self)
b = BWidget(self)
self.addItem(a, "A")
self.addItem(b, "B")
def printMethod(self):
for i in range(self.count()):
self.widget(i).mymethod()
However, since widget() method of QToolBox only returns objects of QWidget type, when calling printMethod() of toolboxWidget object, it gives the following error:
AttributeError: 'QWidget' object has no attribute 'mymethod'.
Is there a way I can convert the QWidget returned by widget() to BaseWidget objects? Thanks.
After fixing all the obvious errors and omissions in your example code, I was able to get it working without any problems.
If the QToolBox.widget method didn't return an instance of one of your BaseWidget subclasses, it would be a bug in PyQt (or sip).
Here is a script that works for me using sip 4.15.4 and PyQt 4.10.3:
from PyQt4 import QtCore, QtGui
class BaseWidget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
def mymethod(self):
pass
class AWidget(BaseWidget):
def __init__(self, parent=None):
BaseWidget.__init__(self, parent)
def mymethod(self):
print "A"
class BWidget(BaseWidget):
def __init__(self, parent=None):
BaseWidget.__init__(self, parent)
def mymethod(self):
print "B"
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.toolbox = QtGui.QToolBox(self)
a = AWidget(self.toolbox)
b = BWidget(self.toolbox)
self.toolbox.addItem(a, "A")
self.toolbox.addItem(b, "B")
self.button = QtGui.QPushButton('Test', self)
self.button.clicked.connect(self.printMethod)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.toolbox)
layout.addWidget(self.button)
def printMethod(self):
for i in range(self.toolbox.count()):
self.toolbox.widget(i).mymethod()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 300)
window.show()
sys.exit(app.exec_())

Categories