How can attributes of a widget that triggered a clicked.connect function call be accessed?
-
As a novice exercise, I (the novice) set myself the task of creating horizontal lines of widgets in a QVBoxLayout. Sound simple, right? The thing is, because the number of record sets is not fixed, the number of horizontal widget sets needed is not fixed either, so my program has to create these widget sets on the fly, one for each set of data, and then access that data (the QWidget attributes) later on.
As an extremely simple example that gets to the heart of my issue, the following code creates 5 QPushButton widgets on the fly, and then attempts to display the name of whichever button a user presses, in a label using a .clicked.connect(self.update_label) call. If the called function can tell which button invoked it, the name of the button that was pressed should show up in the label, otherwise an attempt-counter and a failure message is shown on the label instead.
It seems that identifying whichever widget made a function call and accessing it's attributes should be a VERY basic capability, but I haven't been able to search up any examples related to my problem. How would a more experienced Qt Coder accomplish it?
A successful answer will provide a short, working code which accomplishes what my example program is attempting (and failing) to do. With a working example, I can redirect my studies accordingly. My apologies if mine is an already answered question, as I suspect it is. If so, chalk it up to my ignorance of Qt which prevented me from finding a previously asked question.
Thank you, and here is my code:
import sys from PyQt6.QtWidgets import ( QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, ) attempt_count = 0 # GLOBAL attempt counter. class my_window(QMainWindow): def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Name of Button") layout.addWidget(self.label) for cnt in range(1,6): txt = str(cnt)+"_Button" self.wid = QPushButton(txt) self.wid.setCheckable(True) self.wid.setObjectName(txt) self.wid.clicked.connect(self.update_labels) layout.addWidget(self.wid) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def update_labels(self,state): global attempt_count attempt_count += 1 try: # Attempt to show the name of the triggering button in label self.label.setText(parent.text()) #<-- What code will display the triggering button name? # (Note: self.label.setText(self.wid.text()) shows '5_Button', the name of the last button created.) # Apparently each new widget created overwrites the one before. I thought of using a master-list, # but, again, how can the function tell which button was clicked?) except:# If the above code fails, give proof that label is connected and working. self.label.setText(str(attempt_count)+") Show button name failed.") app = QApplication(sys.argv) window = my_window() window.show() app.exec()
-
@SGaist
Oh, OK! :) Well I started to type this in anyway, so...."How are attributes of a widget that triggered a clicked.connect function call accessed?"
If this boils down to your question. You just need to know which widget emitted the clicked signal, then you can of course access its attributes. There are two ways:
-
The "older" way is
QObject::sender()
. This is simple but has drawbacks, noted in the docs. -
The "newer" way is to take advantage of lambdas, available in both Python and C++. You can use them as the connected slot and pass a parameter of the object which emitted the signal. In Python:
self.wid.clicked.connect(lambda state, btn=self.wid : self.update_labels(btn)) def update_labels(self, btn): print(btn.objectName())
[Hope I got the above syntax right for Python!] This is discussed in more detail in e.g. https://stackoverflow.com/questions/35819538/using-lambda-expression-to-connect-slots-in-pyqt
-
-
Hi and welcome to devnet
QObject::sender comes to mind but watch for the drawbacks.
-
@SGaist
Oh, OK! :) Well I started to type this in anyway, so...."How are attributes of a widget that triggered a clicked.connect function call accessed?"
If this boils down to your question. You just need to know which widget emitted the clicked signal, then you can of course access its attributes. There are two ways:
-
The "older" way is
QObject::sender()
. This is simple but has drawbacks, noted in the docs. -
The "newer" way is to take advantage of lambdas, available in both Python and C++. You can use them as the connected slot and pass a parameter of the object which emitted the signal. In Python:
self.wid.clicked.connect(lambda state, btn=self.wid : self.update_labels(btn)) def update_labels(self, btn): print(btn.objectName())
[Hope I got the above syntax right for Python!] This is discussed in more detail in e.g. https://stackoverflow.com/questions/35819538/using-lambda-expression-to-connect-slots-in-pyqt
-
-
Between @SGaist and @JonB, this question has been answered and resolved. My thanks to both of you, but especially to JonB who gave the key coding I needed to finish my challenge.
For completeness, here is the final corrected and working python code that creates five buttons on the fly and then displays in the label, the object name of any button pressed.
import sys from PyQt6.QtWidgets import ( QApplication, QMainWindow, QPushButton, QLabel, QVBoxLayout, QWidget, ) class my_window(QMainWindow): def __init__(self): super().__init__() layout = QVBoxLayout() self.label = QLabel("Name of Button") layout.addWidget(self.label) for cnt in range(1,6): txt = str(cnt)+"_Button" wid = QPushButton(txt) wid.setCheckable(True) wid.setObjectName(txt) wid.clicked.connect(lambda state, btn=wid : self.update_labels(btn)) layout.addWidget(wid) container = QWidget() container.setLayout(layout) self.setCentralWidget(container) def update_labels(self, btn): self.label.setText(btn.objectName()) app = QApplication(sys.argv) window = my_window() window.show() app.exec()
-
It has been mentioned that .sender() has it's drawbacks, yet in reading over https://stackoverflow.com/questions/35819538/using-lambda-expression-to-connect-slots-in-pyqt I see that this lambda method has a potential drawback of its own; a potential garbage-collection issue leading to a memory leak, if you connect your signal to a lambda slot with a reference to self. I bring this warning to your attention because it seems important, even though it may be considered outside the topic of this thread. Read about it on the stackoverflow thread, found at the link, if you think it is a concern. There are ways to juggle the garbage collection issue. Of course if your lambda use widget is never deleted during the run of your program, the issue never arises.