添加/删除包含多个小部件pyqt的布局

Posted

技术标签:

【中文标题】添加/删除包含多个小部件pyqt的布局【英文标题】:Add/Remove layout containing multiple widgets pyqt 【发布时间】:2018-09-04 20:58:30 【问题描述】:

我正在尝试制作一个允许添加/删除多个 QtWidget 的 gui。用户选择一个文件,并可以选择在它旁边的 lineedit 中添加注释。到目前为止,在这个网站上的快速搜索已经给了我以下代码,它允许我添加行小部件。

import os
import sys

from PyQt5 import QtGui, QtCore, QtWidgets


class Additional(QtWidgets.QMainWindow):

    def __init__(self):
        super(Additional, self).__init__()

        self.addButton     = QtWidgets.QPushButton('  +   ')
        self.delButton     = QtWidgets.QPushButton('  -   ')
        self.acceptButton  = QtWidgets.QPushButton('Accept')
        self.cancelButton  = QtWidgets.QPushButton('Cancel')

        self.addButton.clicked.connect(self.addWidget)
        self.delButton.clicked.connect(self.delWidget)
        self.acceptButton.clicked.connect(self.acceptValues)
        self.cancelButton.clicked.connect(self.cancel)

        self.scrollLayout  = QtWidgets.QFormLayout()

        self.scrollWidget  = QtWidgets.QWidget()
        self.scrollWidget.setLayout(self.scrollLayout)

        self.scrollArea    = QtWidgets.QScrollArea()
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setWidget(self.scrollWidget)

        self.button_layout = QtWidgets.QVBoxLayout()
        self.mainLayout    = QtWidgets.QHBoxLayout()

        self.button_layout.addWidget(self.addButton)
        self.button_layout.addWidget(self.delButton)
        self.button_layout.addWidget(self.acceptButton)
        self.button_layout.addWidget(self.cancelButton)

        self.mainLayout.addLayout(self.button_layout)
        self.mainLayout.addWidget(self.scrollArea)

        self.centralWidget = QtWidgets.QWidget()
        self.centralWidget.setLayout(self.mainLayout)

        self.setCentralWidget(self.centralWidget)

        self.resize(800, 200)
        self.setFixedSize(self.size())
        self.show()

    def addWidget(self):
        self.scrollLayout.addRow(AddRow())

    def delWidget(self):
        # Would this call another class to remove the row? If so, how?
        pass

    def acceptValues(self):
        # Would I count the widgets in the 'scroll' area and get the data from that?
        pass

    def cancel(self):
        QtCore.QCoreApplication.instance().quit()
        # BTW, is this the right way to close the window/instance?



class AddRow(QtWidgets.QWidget):
    def __init__( self, parent=None):
        super(AddRow, self).__init__(parent)
        self.button   = QtWidgets.QPushButton('Select file')
        self.label    = QtWidgets.QLabel('Selection will go here')
        self.lineedit = QtWidgets.QLineEdit()
        self.lineedit.setPlaceholderText("Rename (optional)...")
        # Would I add the button callback here?


        layout = QtWidgets.QHBoxLayout()
        layout.addWidget(self.button)
        layout.addWidget(self.label)
        layout.addWidget(self.lineedit)

        self.setLayout(layout)


app = QtWidgets.QApplication(sys.argv)
myApp = Additional()
app.exec_()

我想弄清楚:

如何删除添加的最后一行。 如何将操作分配给按钮(这在“AddRow”中完成 类如self.buttton.clicked.callback(self.selectfile) 如何从行中收集数据(即,在“接受”之后 点击)

欢迎任何想法!

【问题讨论】:

【参考方案1】:

在指出解决方案之前,我只是要将 AddRow 类的名称更改为 RowWidget,因为类不应指示操作。

class RowWidget(QtWidgets.QWidget):
    def __init__( self, parent=None):
        super(RowWidget, self).__init__(parent)
        ...

如何删除最后添加的行?

由于您使用的是 QFormLayout 并假设您使用的是 PyQt5>=5.8 版本,您可以使用 removeRow() 方法:

@QtCore.pyqtSlot()
def delWidget(self):
    if self.scrollLayout.rowCount() > 0:
        self.scrollLayout.removeRow(self.scrollLayout.rowCount()-1)

如何将动作分配给按钮(这可以在'AddRow'类中完成,例如self.button.clicked.callback(self.selectfile)?

应用程序的每个部分都必须是独立的,因此您选择文件的槽必须只是 RowWidget 的一部分,并且 RowWidget 必须有一个返回该值的方法:

class RowWidget(QtWidgets.QWidget):
    def __init__( self, parent=None):
        super(RowWidget, self).__init__(parent)
        self.button   = QtWidgets.QPushButton('Select file')
        self.label    = QtWidgets.QLabel('Selection will go here')
        self.lineedit = QtWidgets.QLineEdit()
        self.lineedit.setPlaceholderText("Rename (optional)...")

        self.button.clicked.connect(self.on_select_file)

        layout = QtWidgets.QHBoxLayout(self)
        layout.addWidget(self.button)
        layout.addWidget(self.label)
        layout.addWidget(self.lineedit)

    @QtCore.pyqtSlot()
    def on_select_file(self):
        filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Open File")
        if filename:
            self.lineedit.setText(filename)

    def get_filename(self):
        return self.lineedit.text()

如何从行中收集数据(即点击“接受”后)?

附加到布局的小部件是添加布局的小部件的子级,可以通过parentWidget()获取该小部件,有了该父级我们可以通过findChildren()获取其子级:

@QtCore.pyqtSlot()
def acceptValues(self):
    l_values = []
    for w in self.scrollLayout.parentWidget().findChildren(RowWidget):
        l_values.append(w.get_filename())
    print(l_values)

如果parentWidget() 有其他属于RowWidget 的子代,前面的方法可能会失败。

另一个不会失败的选择是遍历QLayoutItems:

@QtCore.pyqtSlot()
def acceptValues(self):
    l_values = []
    for i in range(self.scrollLayout.rowCount()):
        layout_item = self.scrollLayout.itemAt(i)
        if isinstance(layout_item.widget(), RowWidget):
            l_values.append(layout_item.widget().get_filename())
    print(l_values)

【讨论】:

@JamesPorter 如果我的回答对您有帮助,请不要忘记将其标记为正确并给予支持,如果您不知道该怎么做,请查看tour,这是最好的感谢的方式。 我忘了问,如果 removeRow() 不可用,是否有替代方案? @JamesPorter 你有什么版本的 PyQt5? @eyllanesc,根据 Maya 内置的内容,运行 print PySide2.__version__ 返回“2.0.0~alpha0” @JamesPorter 虽然你认为 PyQt5 和 PySide2 很相似,但事实是它们在某些方面是不同的,所以如果你在 pyside2 中需要帮助,请展示一个 pyside2 的示例。正如我所说,当我有时间时,我会尝试为您的 pyside2 版本展示一个替代方案。

以上是关于添加/删除包含多个小部件pyqt的布局的主要内容,如果未能解决你的问题,请参考以下文章

PyQt5 - 动态添加小部件到布局

尽管更新了小部件,但 Pyqt5 更新的小部件未添加到布局中

清除pyqt布局中的所有小部件

PyQt4 - 自定义小部件类结构?

在滚动区域小部件的 PyQt5 中添加 n 列

在 PyQt5/Pyside2 中动态添加小部件到流布局