单击按钮后如何将小部件动态添加到布局

时间:2021-06-04 10:19:06

标签: python pyqt5

每当我点击 + 按钮时,我都想像第一行一样添加一个新行(lineEdit 旁边的一个新 + 按钮),并在相应的 lineEdit 中打印文本。

我设法这样做了,但我不明白程序如何知道我点击了哪个按钮来打印旁边的文本!

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_())

此外,当我将新按钮绑定到“self”(如“self.new_button”)时,它只会打印最新的 lineEdit 而不是较早的。

为什么会这样?有没有更好的方法来做我想做的事?

1 个答案:

答案 0 :(得分:1)

您将局部变量的范围与实例属性混淆了。

lambda 函数始终使用创建它的函数的 local 范围。
在您的示例中,new_line 是在函数 app 中创建的引用,创建时。结果是它总是引用那个行编辑。

如果你创建了一个实例属性(比如self.new_line),那么引用会在对象树上解析;换句话说:找到“self”的“new_line”属性(它是Ui_Form实例,以及任何实例方法的函数的第一个参数)。由于您总是在每次创建新行时覆盖该属性,因此它将始终引用 最新 行编辑。

我准备了一个小例子,大概可以说明这两个方面的区别。文本字段将显示点击的结果:实际点击的行(row 函数的 范围 中的 add)和该行的实例属性设置,每次创建新行时都会覆盖该属性。

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_())

一些进一步的建议:

  • 对类和实例以及它们的方法属性如何工作进行一些研究;
  • 搜索 lambdafunctool.partial 之间的区别:它们的行为相似(因为它们都返回对函数的引用),但它们对参数的评估不同;在某些情况下,您可能需要其中一种;
  • 每当动态创建对象时,为它们创建实例属性几乎毫无意义,因为这些属性将始终引用最后创建的对象;除非您真的需要知道并引用最后一个对象,否则您不应创建(或覆盖)此类属性;
  • pyuic 生成的文件不应该、永远被修改(你也不应该尝试模仿它们的行为);有很多原因被认为是不好的做法,而且由于它们不是主题,我只会给你一个“除非你真的知道你什么,否则不要这样做”重新做”(这通常会导致:“如果你知道你在做什么,你就不会编辑它们”);在关于 using Designer 的官方指南中阅读有关这些文件的正确使用的更多信息;幸运的是,最新的 PyQt 版本添加了一个更详细的警告,并且该警告永远不应被低估;
  • findChild(和 findChildren)应该只用于 Qt 内部创建的小部件(例如 QCalendarWidget 的导航按钮);使用 pyuic 文件(或 uic 模块函数)自动为所有小部件生成属性;如果您在 Designer 中有一个名为 lineEdit_4 的小部件,并且您从 .ui 文件或使用 pyuic 创建了 GUI,则您已经可以使用 self.lineEdit_4;
  • 访问该小部件