Skip to content
Advertisement

How to dynamically add widgets to a layout after a button is clicked [closed]

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 and functool.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 (and findChildren) should be used only for widgets internally created by Qt (such as the navigation buttons of a QCalendarWidget); using pyuic files (or uic module functions) automatically generates attributes for all widgets; if you have a widget named lineEdit_4 in Designer and you created the GUI from the .ui file or using pyuic, you can already access that widget using self.lineEdit_4;
Advertisement