In Qt, we can create custom signals by making them static variables. and then we use self.signame instead classname.signame.
So that creates an instance variable in the class.
I wanted to know the theory beyond this pattern.
here's some pseudo-code that I have tried that was recorded in most of the sources:
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
sig=QtCore.pyqtSignal([bool])
def __init__(self):
super().__init__([])
# self.sig=QtCore.pyqtSignal([bool]) #1
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
When i tred to access the signal test.sig instead of self.sig ,
from PyQt5 import QtWidgets,QtCore
class test(QtWidgets.QApplication):
def __init__(self):
super().__init__([])
self.sig=QtCore.pyqtSignal([bool])
self.window=QtWidgets.QWidget()
self.sig.connect(lambda x=True:print('Hello World'))
self.bt=QtWidgets.QPushButton(self.window,text='test',clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
i get this error:
test.sig.connect(lambda x=True:print('Hello World'))
AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect'
and when I tried to make the signal as an instance variable instead of the static variable I get that error (uncommenting the #1 in psudeo code)
I get the same error (as before).
May I know the reason behind this
Source: PyQt5
It should be noted that pyqtSignal is declared as an attribute of the class but it is not the same object that is used in the connection as indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. This is the same mechanism that Python itself
uses to create bound methods from class functions.
A bound signal has connect(), disconnect() and emit() methods that
implement the associated functionality. It also has a signal attribute
that is the signature of the signal that would be returned by Qt’s
SIGNAL() macro.
In other words, sig = QtCore.pyqtSignal([bool]) is an unbound signal but self.sig is the bound signal and that can be verified with the following lines:
from PyQt5 import QtWidgets, QtCore
class test(QtWidgets.QApplication):
sig = QtCore.pyqtSignal([bool])
print(type(sig))
def __init__(self):
super().__init__([])
self.window = QtWidgets.QWidget()
print(type(self.sig))
self.sig.connect(lambda x=True: print("Hello World"))
self.bt = QtWidgets.QPushButton(self.window, text="test", clicked=self.sig)
self.window.setLayout(QtWidgets.QVBoxLayout())
self.window.layout().addWidget(self.bt)
self.window.show()
test().exec()
Output
<class 'PyQt5.QtCore.pyqtSignal'>
<class 'PyQt5.QtCore.pyqtBoundSignal'>
In conclusion, the attribute of the class "sig" is a pyqtSignal that does not have the connect method and that is used to construct the attribute "self.x" which is a pyqtBoundSignal that does have the connect method.
Related
When I try to define two QWidget var as class members, an error information is reported as the following,
"QWidget: Must construct a QApplication before a QWidget"
To define QWidget obj as class members is for the reason that these two 'QWidget' obj need share data with each other, if I use the way like 'self.compUI1', they can not be accessed in class member functions.
Here is something (similar) what I planned to do,
`
class TabUISheet(QTabWidget):
#define UI component as member
compUI1 = PicUI()
compUI2 = TxtUI()
def
...
def fun1():
#here I need to get txt data
def fun2():
#here I need to update txt data to compUI1 (QWiget based)
`
Does that mean QWidgets can not be defined as class member?
Actually, 'TabUISheet' is called after calling 'QApplication()'
`
def main():
app = QApplication(sys.argv)
...
TabUISheet()
`
I have tried use self.compUI1, but failed because as I mentioned before this can be accessed by 'instance' rather than 'Class' or class member.
Not sure, if there is other way to realize that to share data between two QWidget based UI components.
So normally you can overwrite a class method by doing something like this.
class A():
def test(self):
return 1+1
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
a=A()
a.test = edit_patch(a.test)
Now a.test will return 'this test worked' instead of 2. I'm trying to do something similar in my pyqt6 application. The function below belongs to the "main" class in my code and is connected to a button click. This function is meant to instantiate another class (which is another window in pyqt6). That part works, but I would like to alter the behavior of the select function in this instance. However the method above doesn't seem to work as the select function continues to exhibit the default behavior.
def edit_proj(self):
self.psearch=PSearch(conn=self.conn,parent=self)
self.psearch.select = edit_patch(self.psearch.select)
self.psearch.show()
Any help on this would be great
As requested, here is an MRE
from PyQt6 import QtCore, QtGui, QtWidgets
def edit_patch(func):
def inner(*args,**kwargs):
print('this test worked')
return inner
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(50, 50)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.EditProjButton = QtWidgets.QPushButton(self.centralwidget)
self.EditProjButton.setObjectName("EditProjButton")
self.EditProjButton.clicked.connect(self.nextwindow)
def nextwindow(self):
print('hello from main window')
self.newwindow=Ui_ProjSearchForm(QtWidgets.QWidget())
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.show()
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def setupUi(self, ProjSearchForm):
ProjSearchForm.setObjectName("ProjSearchForm")
ProjSearchForm.resize(100, 100)
self.gridLayout = QtWidgets.QGridLayout(ProjSearchForm)
self.gridLayout.setObjectName("gridLayout")
self.SearchButton = QtWidgets.QPushButton(ProjSearchForm)
self.SearchButton.setObjectName("SearchButton")
self.gridLayout.addWidget(self.SearchButton, 0, 2, 1, 1)
def select(self):
print('this is default behavior')
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
Signal connections work by passing a reference to a callable, and that reference is an "internal" pointer to that function. Overwriting the name of that function will have absolutely no result.
Take this example:
class Test(QPushButton):
def __init__(self):
super().__init__('Click me!')
self.clicked.connect(self.doSomething)
self.doSomething = lambda: print('bye!')
def doSomething(self):
print('hello!')
The code above will always print "hello!", because you passed the reference to the instance method doSomething that existed at the time of the connection; overwriting it will not change the result.
If you need to create a connection that can be overwritten, you have different possibilities.
Pass the function to the constructor
You can set the function as an optional argument in the __init__ and then connect it if specified, otherwise use the default behavior:
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
if func is not None:
self.SearchButton.clicked.connect(func)
else:
self.SearchButton.clicked.connect(self.select)
Create a method for the connection
In this case we pass the reference to a specific method that will create the connection, eventually disconnecting any previous connection (remember that signals can be connected to multiple functions, and even the same function multiple times). This is similar to the approach above.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.setSelectFunc(edit_patch(self.newwindow.select))
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(self.select)
def select(self):
print('this is default behavior')
def setSelectFunc(self, func):
try:
self.SearchButton.clicked.disconnect(self.select)
except TypeError:
pass
self.select = func
self.SearchButton.clicked.connect(self.select)
Use a lambda
As said above, the problem was in trying to overwrite the function that was connected to the signal: even if the connected function is a wrapper, the direct reference for the connection is not actually overwritten.
If you, instead, connect to a lambda that finally calls the instance method, it will work as expected, because the lambda is dynamically computed at the time of its execution and at that time self.select will be a reference to the overwritten function.
def nextwindow(self):
self.newwindow = Ui_ProjSearchForm()
self.newwindow.select = edit_patch(self.newwindow.select)
class Ui_ProjSearchForm(QtWidgets.QWidget):
def __init__(self, func=None):
super().__init__()
self.setupUi(self)
self.SearchButton.clicked.connect(lambda: self.select())
def select(self):
print('this is default behavior')
Some unrelated but still important notes:
You should never edit pyuic generated files, nor try to merge their code into your script or mimic their behavior. Instead, follow the official guidelines about using Designer.
Passing a new QWidget instance as argument is pointless (other than wrong and potentially dangerous); if you want to create a new window for the new widget, just avoid any parent at all, otherwise use QDialog for modal windows.
Only classes and constants should have capitalized names, everything else should be named starting with lowercase letters (this includes object names created in Designer); read more about this and other important topics in the official Style Guide for Python Code.
Ok I think I've figured out a solution (in the MRE posted in question). There's some shenanigans that go on in the back ground once you connect a button in the UI to a function. It's not a "live" connection like in the a.test example, so edits to the function later don't have an impact on how the button functions.
So, if we replace
self.newwindow.select = edit_patch(self.newwindow.select)
with
self.newwindow.SearchButton.clicked.disconnect()
self.newwindow.select = edit_patch(self.newwindow.select)
self.newwindow.SearchButton.clicked.connect(self.newwindow.select)
we suddenly get the desired behavoir from the button. This was entirely too frustrating.
Well I was scripting a software designed via Python which I'll be using signals and slots too much in an PyQt5 application. I got an idea of creating a dictionary where all signals come in and each signal will have its own key in order to access (or basically to connect it to a function). The problem is that I get this error 'AttributeError: 'PyQt5.QtCore.pyqtSignal' object has no attribute 'connect' for some reason. I read about this error and found out that I have to declare the signals outside the constructor to get it working but unfortunately that will break my idea so that, I came here so somebody can solve my issue.
Here is the code if you still don't understand:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtCore import QRunnable, pyqtSlot, QThreadPool, QObject, pyqtSignal
class WorkerSignals(QObject):
signals = {}
def __init__(self, **kwargs):
QObject.__init__(self)
if (kwargs is not None):
for key, value in kwargs.items():
self.signals[key] = value
class Worker(QRunnable):
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
self.fn = fn
self.args = args
self.kwargs = kwargs
#pyqtSlot()
def run(self):
self.fn(*self.args, **self.kwargs)
And an example of creating signals:
worker_signals = WorkerSignals(result=pyqtSignal(str), error=pyqtSignal(str))
worker_signals.signals['result'].connect(self.on_receive_result)
worker_signals.signals['error'].connect(self.on_receive_error)
As indicated in the docs:
A signal (specifically an unbound signal) is a class attribute. When a
signal is referenced as an attribute of an instance of the class then
PyQt5 automatically binds the instance to the signal in order to
create a bound signal. [...]
So it is not only necessary that is it declared outside the constructor but it must be a static attribute since it serves as a prototype to create the signals that belong to the instance. A possible solution is to use type to create dynamic classes:
from PyQt5 import QtCore
d = {
"result": QtCore.pyqtSignal(str),
"error": QtCore.pyqtSignal(str)
}
WorkerSignals = type("WorkerSignals", (QtCore.QObject,), d)
if __name__ == '__main__':
import sys
app = QtCore.QCoreApplication(sys.argv)
worker_signals = WorkerSignals()
def on_result(text):
print("result:", text)
def on_error(text):
print("error:", text)
worker_signals.result.connect(on_result)
worker_signals.error.connect(on_error)
def emit_result():
worker_signals.result.emit(" 1+1=2 ")
def emit_error():
worker_signals.error.emit(" :( ")
QtCore.QTimer.singleShot(1000, emit_result)
QtCore.QTimer.singleShot(2000, emit_error)
QtCore.QTimer.singleShot(3000, app.quit)
sys.exit(app.exec_())
I am building an API that I would like to use in applications using pyqt and other guis.
If a programmer is using a gui other that Qt I don't want her to have to import pyqt etc.
My problem is that I use a task to subscribe to messages and pass them through to the main task.
I accomplish this by passing a parameter (qt) which is either True or False depending on what gui we are implementing.
I then create a class that generates a SubscriberParent class of either a QtCore.QThread or a Thread. The ultimate SubscriberThread
is of type SubscriberThreadParent.
The main thread either polls the interTaskQueue or uses Qt's signals and slots to process the message.
class Gui(object):
def __init__(self, qt):
if qt:
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, pyqtSignal
class SubScriberThreadParent(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
else:
from threading import Thread
class SubScriberThreadParent(Thread):
def __init__(self):
Thread.__init__(self)
class SubscriberThread(Gui.SubScriberThreadParent):
def __init__(self, qt, dsParam, SubScriberThreadParent):
if qt:
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, pyqtSignal
pubIn = pyqtSignal(str, str)
SubScriberThreadParent.__init__(self)
self.interTaskQueue = dsParam.interTaskQueue
.
.
.
.
#
# Pass the message on to the main application
#
def alertMainApp(self, bodyTuple):
if self.qt:
btz = '{0}'.format(bodyTuple)
self.pubIn.emit(btz)
LOGGER.info("Emitted Alert ")
else:
if self.interTaskQueue != None:
self.interTaskQueue.put(bodyTuple) # Pass it to the main thread
LOGGER.info("Queued Alert.")
else:
LOGGER.error("No Inter-task data transfer method available to the subscriber task!")
The error I get with this approach is: "AttributeError: type object 'Gui' has no attribute 'SubScriberThreadParent'"
How can I make this work?
I also would like to know the scope of the conditional imports.
In order for one class to inherit from Gui.SubScriberThreadParent you will have to have created an instance of Gui and assigned something to a class variable.
Consider this code:
class Gui:
def __init__(self, qt):
if qt:
class Foo:
att = 'Foo'
Gui.Parent = Foo
else:
class Bar:
att = 'Bar'
Gui.Parent = Bar
def makeThread():
class Thread(Gui.Parent):
def __init__(self):
self.att = Gui.Parent.att
return Thread()
def main():
Gui(False)
t = makeThread()
print(t.att)
main()
Output:
With Gui(False) as above, the output is:
Bar
When changed to Gui(True), the output is:
Foo
I can not understand one's probably a very simple thing. How from one module (Main.py) make changes (call functions ) in other module (module2.py), which is connected as QDockWidget to MainWindow, and see these changes immediately?
You have to declare a signal in the class then connect the signal to a function.
class MyClass(QtCore.QObject): # Could be QWidget must be inherited from QObject
mysignal = QtCore.pyqtSignal(int, int, int, int) # types to pass values to the method call
...
myclass = MyClass()
other = QtGui.QMainWindow()
myclass.mysignal.connect(other.setGeometry)
myclass.mysignal.emit(0, 0, 1, 1)
http://pyqt.sourceforge.net/Docs/PyQt4/new_style_signals_slots.html