James Porter
James Porter

Reputation: 119

Add/Remove layout containing multiple widgets pyqt

I'm trying to make a gui that allows the addition/removal of multiple QtWidgets. The user selects a file and has the option off adding a comment in the lineedit next to it. A quick search on this site has so far given me the following code which allows me to add rows widgets.

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

I'm trying to figure out:

Any ideas would be welcome!

Upvotes: 3

Views: 1324

Answers (1)

eyllanesc
eyllanesc

Reputation: 244282

Before pointing to the solution, I'm just going to change the name of the AddRow class to RowWidget because a class should not indicate action.

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

How to delete the last row that's been added ?

Since you are using QFormLayout and assuming that you are using a version PyQt5>=5.8 you can use the removeRow() method:

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

How to assign the action to the button (this be done in the 'AddRow' class likeself.buttton.clicked.callback(self.selectfile)?

Each part of your application must be independent so the slot that you select the file must be part only of RowWidget, and RowWidget must have a method that returns that value:

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

How to collect the data from the rows (ie, after 'accept' has been clicked)?

The widgets attached to a layout are children of the widget where the layout has been added, that widget can be obtained through parentWidget(), having that parent we can obtain their children through 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)

The previous method may fail if the parentWidget() has other children that belong to RowWidget.

Another option, and that does not fail is to iterate through the 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)

Upvotes: 3

Related Questions