Whenever i click the + button i want to add a new row just like the first one ( a new + button next to a lineEdit) and also print the text inside the respective lineEdit.
i managed to do it this way, but i dont understand how the program knows which button i’m clicking on to print the text next to it!
from PyQt5 import QtCore, QtGui, QtWidgets count = 1 class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form") Form.resize(569, 176) self.formLayout = QtWidgets.QFormLayout(Form) self.formLayout.setObjectName("formLayout") self.lineEdit_1 = QtWidgets.QLineEdit(Form) self.lineEdit_1.setObjectName("lineEdit_1") self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_1) self.pushButton_1 = QtWidgets.QPushButton(Form) self.pushButton_1.setObjectName("pushButton_1") self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.pushButton_1) self.pushButton_print = QtWidgets.QPushButton(Form) self.pushButton_print.setObjectName("pushButton_print") self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.pushButton_print) self.pushButton_1.clicked.connect(self.add) self.pushButton_print.clicked.connect(self.appear) self.retranslateUi(Form) QtCore.QMetaObject.connectSlotsByName(Form) def retranslateUi(self, Form): _translate = QtCore.QCoreApplication.translate Form.setWindowTitle(_translate("Form", "Form")) self.pushButton_1.setText(_translate("Form", "+")) self.pushButton_print.setText(_translate("Form", "print 4th")) def add(self): global count count+=1 new_btn = QtWidgets.QPushButton("+", Form) new_line = QtWidgets.QLineEdit(Form) #new_btn.setObjectName(f"pushButton_{count}") #new_line.setObjectName(f"lineEdit_{count}") self.formLayout.setWidget(count, QtWidgets.QFormLayout.LabelRole, new_btn) self.formLayout.setWidget(count, QtWidgets.QFormLayout.FieldRole, new_line) new_btn.clicked.connect(lambda: print(new_line.text())) new_btn.clicked.connect(self.add) def appear(self): lineEdit = Form.findChild(QtWidgets.QLineEdit, f"lineEdit_4") print(lineEdit.text()) if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) Form = QtWidgets.QWidget() ui = Ui_Form() ui.setupUi(Form) Form.show() sys.exit(app.exec_())
also when i bind the new buttons to “self” like “self.new_button”, it will only print the latest lineEdit and not the earlier ones.
Why is this happening? is there a better way of doing what i want to do?
Advertisement
Answer
You’re confusing the scope of local variables with instance attributes.
A lambda function always uses the local scope of the function in which it was created.
In your example, new_line
is a reference created within the function app
, when it was created. The result is that it will always refer to that line edit.
If you create an instance attribute (like self.new_line
) then the reference is resolved on the object tree; in other words: find the “new_line” attribute of “self
” (which is the Ui_Form
instance, and the first argument of the function of any instance method). Since you’re always overwriting that attribute everytime a new row is created, it will always refer to the latest line edit.
I’ve prepared a small example that could probably show the difference between the two aspects. The text field will show the result of the click: the actual clicked row (which is row
in the scope of the add
function) and the instance attribute set for that row, which is overwritten everytime a new row is created.
from PyQt5 import QtWidgets class ScopeTest(QtWidgets.QWidget): def __init__(self): super().__init__() self.row = 0 layout = QtWidgets.QVBoxLayout(self) self.logView = QtWidgets.QPlainTextEdit(readOnly=True, minimumHeight=200) layout.addWidget(self.logView) self.addButton = QtWidgets.QPushButton('Add row') layout.addWidget(self.addButton) layout.addWidget(QtWidgets.QFrame(frameShape=QtWidgets.QFrame.HLine)) self.addButton.clicked.connect(self.add) def add(self): self.row += 1 # note that the following line is important! # "row" is a *local* variable! "self.row" is an *instance attribute*! row = self.row button = QtWidgets.QPushButton('Row {}'.format(self.row)) self.layout().addWidget(button) button.clicked.connect(lambda: self.log(row, self.row)) def log(self, clickedRow, scopeRow): self.logView.appendPlainText('Clicked: {}, Scope: {}'.format( clickedRow, scopeRow)) self.logView.verticalScrollBar().setValue( self.logView.verticalScrollBar().maximum()) if __name__ == '__main__': import sys app = QtWidgets.QApplication(sys.argv) test = ScopeTest() test.show() sys.exit(app.exec_())
Some further suggestions:
- do some research on classes and instances, and how their methods and attributes work;
- search about the difference between
lambda
andfunctool.partial
: they behave similarly (since they both return a reference to a function) but they evaluate their arguments differently; there are situations for which you might need one or the other; - whenever objects are dynamically created, creating instance attributes for them is almost pointless since those attributes will always refer to the last created objects; unless you really need to know and refer to that last object, you should not create (nor overwrite) such attributes;
- files generated by
pyuic
should never, NEVER be modified (nor you should try to mimic their behavior); there are lots of reasons for which that is considered a bad practice, and since they’re not on topic I’ll just leave you with a “don’t do that unless you really know what you’re doing” (which usually leads to: “if you know what you’re doing, you don’t edit them”); read more about the correct uses of those files in the official guidelines about using Designer; luckily, the latest PyQt versions added a more verbose warning about that, and that warning should never be underestimated; findChild
(andfindChildren
) should be used only for widgets internally created by Qt (such as the navigation buttons of a QCalendarWidget); using pyuic files (oruic
module functions) automatically generates attributes for all widgets; if you have a widget namedlineEdit_4
in Designer and you created the GUI from the.ui
file or using pyuic, you can already access that widget usingself.lineEdit_4
;