Using a user defined slot in PyQt - pyqt

I have a problem using signal and slot to use a push button to trigger an event that calls "gc.speed_rpm"(a used defined slot, or my own method/function) and display the output of it on a text browser widget.
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL(_fromUtf8("clicked()")), self.textBrowser_2, gc.speed_rpm)
I received the following error:
arguments did not match any overloaded call:
QObject.connect(QObject, SIGNAL(), QObject, SLOT(), Qt.ConnectionType=Qt.AutoConnection): argument 4 has unexpected type 'instancemethod'
QObject.connect(QObject, SIGNAL(), callable, Qt.ConnectionType=Qt.AutoConnection): argument 3 has unexpected type 'QTextBrowser'
What could go wrong? Any ideas.
Thanks in advance.

Try this:
def __init__(self):
# Connect the clicked action of the push button to a custom slot
self.pushButton.clicked.connect(self.display_speed_rpm)
def display_speed_rpm(self):
# Use of setPlainText or setHtml depends on the output of gc.speed_rpm()
self.textBrowser_2.setPlainText(gc.speed_rpm())
Edit: you can also use the decorator way to connect a widget signal to a slot. You must name your slot according to the widget name and signal name:
#QtCore.pyqtSlot()
def on_pushButton_clicked(self):
# Use of setPlainText or setHtml depends on the output of gc.speed_rpm()
self.textBrowser_2.setPlainText("{}".format(gc.speed_rpm()))

Related

Python - How to pass a QSpinBox value together with other parameters to a slot?

It's quite straightforward to process a valueChanged signal of a single QSpinBox with a dedicated slot:
class MainWindow(QMainWindow):
self.ui.spinbox.valueChanged.connect(self.slot)
def slot(self,value):
print(value)
Now imagine I have many spinboxes (e.g. each of them controls some parameter in a different instance of the same class) and would like to use the same slot to process them. I would like to do something like this:
class MainWindow(QMainWindow):
self.ui.spinbox1.valueChanged.connect(lambda: self.slot(1))
self.ui.spinbox2.valueChanged.connect(lambda: self.slot(2))
self.ui.spinbox3.valueChanged.connect(lambda: self.slot(3))
def slot(self,instance_id,value):
...
Unfortunately it doesn't work because the spinbox value is not passed through the lambda.
So, what is the best way to pass the value if I'd like to use one single slot to process many similar signals?
You can use the self.sender to identify the spinbox and get its value..
class MainWindow(QMainWindow):
self.ui.spinbox1.valueChanged.connect(self.slot)
self.ui.spinbox2.valueChanged.connect(self.slot)
self.ui.spinbox3.valueChanged.connect(self.slot)
def slot(self):
spinbox = self.sender()
value = spinbox.value()

Python - GUI checkbox cannot assign the function with arguments to variable

I'm having trouble getting my head around assigning a function to a variable when the function uses arguments. The arguments appear to be required but no matter what arguments I enter it doesn't work.
The scenario is that I'm creating my first GUI which has been designed in QT Designer. I need the checkbox to be ticked before the accept button allows the user to continue.
Currently this is coded to let me know if ticking the checkbox returns anything (which is does) however I don't know how to pass that result onto the next function 'accept_btn'. I thought the easiest way would be to create a variable however it requires positional arguments and that's where I'm stuck.
My code:
class MainWindow(QtWidgets.QMainWindow, Deleter_Main.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.ConfirmBox.stateChanged.connect(self.confirm_box)
self.Acceptbtn.clicked.connect(self.accept_btn)
def confirm_box(self, state):
if self.ConfirmBox.isChecked():
print("checked")
else:
print("not checked")
checked2 = confirm_box(self, state)
def accept_btn(self):
if checked2 == True:
print("clicked")
else:
print("not clicked")
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
The code gets stuck on 'checked2' with the error:
NameError: name 'self' is not defined
I thought there might be other solutions for running this all within one function but I can't seem to find a way whilst the below is required.
self.ConfirmBox.stateChanged.connect(self.confirm_box)
Would extra appreciate if anyone could help me understand exactly why I need the 'self' argument in the function and variable.
Thanks in advance,
If you just need to enable a button when the checkbox is checked, it can be easily done within the signal connection:
self.ConfirmBox.toggled.connect(self.Acceptbtn.setEnabled)
QWidget.setEnabled requires a bool argument, which is the argument type passed on by the toggled signal, so the connection is very simple in this case.
Apart from this, there are some mistakes in your understanding of classes in Python: it seems like you are thinking in a "procedural" way, which doesn't work well with general PyQt implementations and common python usage, unless you really need some processing to be done when the class is created, for example to define some class attributes or manipulate the way some methods behave. But, even in this case, they will be class attributes, which will be inherited by every new instance.
The line checked2 = confirm_box(self, state) will obviously give you an error, since you are defining checked2 as a class atribute. This means that its value will be processed and assigned when the class is being created: at this point, the instance of the class does not exist yet, Python just executes the code that is not part of the methods until it reaches the end of the class definition (its primary indentation). When it reaches the checked2 line, it will try to call the confirm_box method, but the arguments "self" and "state" do not exist yet, as they have not been defined in the class attributes, hence the NameError exception.
Conceptually, what you have done is something similar to this:
class SomeObject(object):
print(something)
This wouldn't make any sense, since there is no "something" defined anywhere.
self is a python convention used for class methods: it is a keyword commonly used to refer to the instance of a class, you could actually use any valid python keyword at all.
The first argument of any class method is always the reference to the class instance, the only exceptions are classmethod and staticmethod decorators, but that's another story. When you call a method of an instanciated class, the instance object is automatically bound to the first argument of the called method: the self is the instance itself.
For example, you could create a class like this:
class SomeObject(object):
def __init__(Me):
Me.someValue = 0
def setSomeValue(Myself, value):
Myself.someValue = value
def multiplySomeValue(I, multi):
I.setSomeValue(I.someValue * multi)
return I.someValue
But that would be a bit confusing...

How do I connect a PyQt5 slot to a signal function in a class?

I'm trying to set up a pyqt signal between a block of UI code and a separate python class that is just for handling event responses. I don't want to give the UI code access to the handler (classic MVC style). Unfortunately, I am having difficulty connecting the slot to the signal. Here is the code:
from PyQt5 import QtCore
from PyQt5.QtCore import QObject
class UiClass(QObject):
mySignal = QtCore.pyqtSignal( str )
def __init__(self):
QObject.__init__(self)
def send_signal(self):
self.mySignal.emit("Hello world!")
class HandlerClass():
currentMessage = "none"
def register(self, mySignal):
mySignal.connect(self.receive_signal)
#QtCore.pyqtSlot(str)
def receive_signal(self, message):
self.currentMessage = message
print(message)
ui = UiClass()
handler = HandlerClass()
handler.register(ui.mySignal)
ui.send_signal()
When I run this code it fails at the handler.register line. Here is the error:
Traceback (most recent call last):
File "C:\git\IonControl\testpyqtslot.py", line 25, in
handler.register(ui.mySignal)
File "C:\git\IonControl\testpyqtslot.py", line 17, in register
mySignal.connect(self.receive_signal)
TypeError: connect() failed between UiClass.mySignal[str] and receive_signal()
I'd like this code to successfully register the signal to the slot and have the handler print "hello world" at the end. What did I do wrong here?
My basic question is this: how do I connect a signal to a slot function that is part of a class?
The error happens becuase you are using pyqtSlot decorator in a class which doesn't inherit from QObject. You can fix the problem by either getting rid of the decorator, or making HandlerClass a subclass of QObject.
The main purpose of pyqtSlot is to allow several different overloads of a slot to be defined, each with a different signature. It may also be needed sometimes when making cross-thread connections. However, these use-cases are relatively rare, and in most PyQt applications it is not necessary to use pyqtSlot at all. Signals can be connected to any python callable object, whether it is decorated as a slot or not.

PyQt auto slots: not called when decorated with a wrapper

I have a QWidget created via Qt Designer that has a QPushButton named foo, and the QWidget has a method named on_foo_clicked:
class MyWidget(QWidget):
def __init__(self):
...
#pyqtSlot()
def on_foo_clicked(self):
pass
Firstly, the on_foo_clicked is only called if it is decorated with pyqtSlot(). If I remove that decorator, I have to manually connect the self.ui.foo (of type QPushButton) to self.on_foo_clicked in MyWidget's initializer, yet I could not find any documentation about this.
Secondly, if I want to use my own decorator like this:
def auto_slot(func):
#pyqtSlot()
def wrapper(self):
func(self)
return wrapper
...
#auto_slot
def on_foo_clicked(self):
pass
it no longer works. But the following works:
def auto_slot(func):
return pyqtSlot()(func)
So the issue is not the replacement of pyqtSlot by another decorator, but rather that for some reason the wrapper function causes the auto connection mechanism to fail. Note that the above issue only affects automatic connections; if I add a line in MyWidget.__init__ to explicitely connect the self.ui.foo button to self.on_foo_clicked, then the auto_slot decorator with wrapper works as expected and the method gets called when click button.
Any ideas if there is something I can do to auto_slot with wrapper so that it will work even with automatically connected slots?
The reason I want this is so that the wrapper can trap exception raised by slot (indication of a bug) and print to console.
Just occurred to me that the problem is that wrapper function does not have the right name to be found by the auto connection system. So the following fixes the problem:
from functools import wraps
def auto_slot(func):
#wraps(func)
def wrapper(self):
func(self)
return pyqtSlot()(wrapper)

Connecting an overloaded PyQT signal using new-style syntax

I am designing a custom widget which is basically a QGroupBox holding a configurable number of QCheckBox buttons, where each one of them should control a particular bit in a bitmask represented by a QBitArray. In order to do that, I added the QCheckBox instances to a QButtonGroup, with each button given an integer ID:
def populate(self, num_bits, parent = None):
"""
Adds check boxes to the GroupBox according to the bitmask size
"""
self.bitArray.resize(num_bits)
layout = QHBoxLayout()
for i in range(num_bits):
cb = QCheckBox()
cb.setText(QString.number(i))
self.buttonGroup.addButton(cb, i)
layout.addWidget(cb)
self.setLayout(layout)
Then, each time a user would click on a checkbox contained in self.buttonGroup, I'd like self.bitArray to be notified so the corresponding bit in the array can be set/unset accordingly. For that I intended to connect QButtonGroup's buttonClicked(int) signal to QBitArray's toggleBit(int) method and, to be as pythonic as possible, I wanted to use new-style signals syntax, so I tried this:
self.buttonGroup.buttonClicked.connect(self.bitArray.toggleBit)
The problem is that buttonClicked is an overloaded signal, so there is also the buttonClicked(QAbstractButton*) signature. In fact, when the program is executing I get this error when I click a check box:
The debugged program raised the exception unhandled TypeError
"QBitArray.toggleBit(int): argument 1 has unexpected type 'QCheckBox'"
which clearly shows the toggleBit method received the buttonClicked(QAbstractButton*) signal instead of the buttonClicked(int) one.
So, the question is, how can I specify the signal connection, using new-style syntax, so that self.bitArray receives the buttonClicked(int) signal instead of the default overload - buttonClicked(QAbstractButton*)?
EDIT: The PyQT's New-style Signal and Slot Support documentation states you can use pyqtSlot decorators to specify which signal you want to connect to a given slot, but that is for a slot you are creating. What to do when the slot is from a "ready made" class? Is the only option subclassing it and then reimplementing the given slot with the right decorator?
While browsing for related questions I found this answer to be exactly what I needed. The correct new-style signal syntax for connecting only QButtonGroup's buttonClicked(int) signal to QBitArray's toggleBit(int), ignoring the other overloaded signatures, involves specifying the desired type in brackets, like this:
self.buttonGroup.buttonClicked[int].connect(self.bitArray.toggleBit)

Resources