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

Python 如何在单击按钮后将小部件动态添加到布局中,python,pyqt5,Python,Pyqt5,无论何时单击+按钮,我都希望添加一个与第一行相同的新行(lineEdit旁边的一个新+按钮),并在相应的lineEdit中打印文本 我设法这样做,但我不明白程序如何知道我点击哪个按钮来打印旁边的文本 from PyQt5 import QtCore, QtGui, QtWidgets count = 1 class Ui_Form(object): def setupUi(self, Form): Form.setObjectName("Form")

无论何时单击+按钮,我都希望添加一个与第一行相同的新行(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_按钮”,它将只打印最新的lineEdit,而不打印较早的lineEdit

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

你把局部变量的范围和实例属性混淆了

lambda函数始终使用创建它的函数的本地作用域。
在您的示例中,
new\u line
是在创建时在函数
app
中创建的引用。结果是它将始终引用该行编辑

如果创建实例属性(如
self.new\u line
),则在对象树上解析引用;换句话说:查找“
self
”的“new_line”属性(这是
Ui_形式
实例,是任何实例方法的函数的第一个参数)。由于每次创建新行时总是覆盖该属性,因此它将始终引用最新的行编辑

我准备了一个小例子,可能会说明这两个方面的区别。文本字段将显示单击的结果:实际单击的行(在
添加
功能范围内为
)和为该行设置的实例属性,该属性在每次创建新行时被覆盖

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_())
一些进一步的建议:

  • 研究类和实例,以及它们的方法和属性是如何工作的
  • 搜索
    lambda
    functool.partial
    之间的差异:它们的行为相似(因为它们都返回对函数的引用),但对参数的求值不同;有些情况下,你可能需要一个或另一个
  • 无论何时动态创建对象,为它们创建实例属性几乎毫无意义,因为这些属性将始终引用上次创建的对象;除非您确实需要知道并引用最后一个对象,否则不应创建(或覆盖)此类属性
  • pyuic
    生成的文件不应被修改(也不应试图模仿它们的行为);这被认为是一种不好的做法有很多原因,因为它们不在主题中,所以我给你留下一个“除非你真的知道你在做什么,否则不要那样做”(这通常会导致:“如果你知道你在做什么,就不要编辑它们”);阅读更多关于这些文件正确使用的官方指南;幸运的是,最新的PyQt版本增加了一个更详细的警告,这个警告永远不应该被低估
  • findChild
    (和
    findChildren
    )应仅用于Qt内部创建的小部件(例如QCalendarWidget的导航按钮);使用pyuic文件(或
    uic
    module函数)自动为所有小部件生成属性;如果您在Designer中有一个名为
    lineEdit_4
    的小部件,并且您是从
    .ui
    文件或使用pyuic创建GUI的,那么您已经可以使用
    self.lineEdit_4
    访问该小部件了
你的问题很令人困惑。你能告诉我你到底想要什么吗?您还可以告诉我是否希望按钮显示所有lineedit文本,或者是否希望在lineedit旁边有一个按钮显示相应的lineedit文本?让我这样说。运行我的代码时,您将看到,当我单击+按钮时,会添加一个新行。每次创建一个新的lineEdit和一个新的按钮时,我都不会选择单击哪个+按钮。现在,除了创建新行外,单击+还会打印它旁边的lineEdit中的文本。我不明白的是,程序如何知道我正在单击哪个按钮以及要打印哪个行(因为它们的变量名都是相同的:new_btn和new_line)。是的按钮应该打印相应的lineEdit我的问题是程序如何知道应该打印哪个lineEdit文本?它们都有相同的变量名@Heike但是创建新的线条编辑时,如何记住以前的线条编辑?因为变量名现在指向最新的一个,谢谢你的回答。我知道instrance属性
self.row
每次都被覆盖。您说过“在您的示例中,new_line是在函数应用程序中创建时创建的引用。结果是它将始终引用该行编辑。”。我们创建了一个新的lineEdit,并将其命名为variable
new\u line
,这让我很困惑,尽管lineEdit都有相同的变量名,但程序如何区分它们。另外,假设我们有一个按钮,如果第四个按钮存在,单击时会显示结果。我们如何引用第四个?@Sherafati 1。每次调用
app
,都会创建一个新的
new\u line
变量,该变量仅对该调用有效,并引用该“范围”中编辑的行。事实上,你总是使用相同的名字是有影响的;我建议你做一些关于“可变范围”的研究。2.我不明白你的意思,你能澄清一下吗?@Sherafati然后在
\uuuu init\uuuuu
中创建一个空列表,并在每次创建小部件时将其添加到该列表中,然后如果你想访问第四个,只需使用该索引,例如:
第四个按钮=self.widgetList[3]
@Sherafati如前所述,findChild只能在pyqt在内部创建对象时使用,使用它是非常不鼓励的,也是毫无意义的:只需创建一个