Как использовать слот PyQt и сигнал с параметром в цикле?

Я пытаюсь создать динамическую вставку кнопки удаления инструмента внутри строки редактирования, а также связать слот с инструментом удаления. Что усложняет проблему, так это то, что у слота есть параметры, то есть lineedit и метка строк, которые нужно удалить. В результате я должен использовать lambda. Проблема в том, что кнопка удаления удаляет только одну строку и не в правильном порядке. Я пробовал functools.partial и лямбда с i=i, но оба не работают. Ниже приведен код, который у меня есть.

from functools import partial
    def __init__(self, iface, parent=None):
        remove_icon = ':/plugins/stdm/images/icons/remove.png'

        for i, (line_widget, label) in enumerate(
                zip(self.coordFormContainer.findChildren(QLineEdit), 
                    self.coordFormContainer.findChildren(QLabel)
                )
        ):
            self.remove_button = QToolButton(line_widget)
            self.remove_button.setCursor(Qt.PointingHandCursor)

            self.remove_button.setFocusPolicy(Qt.NoFocus)
            self.remove_button.setIcon(QIcon(remove_icon))

            self.remove_button.setStyleSheet(
                'background: transparent; '
                'border: none;'
            )
            self.remove_button.setToolTip(
                QApplication.translate(
                    'Window',
                    'Remove this point'
                )
            )
            self.remove_button.setStyleSheet(
                'QToolTip { '
                    'color: #222; '
                    'background-color: #ddd; '
                    'border: 1px solid #eee; '
                '}'
            )
            layout = QHBoxLayout(line_widget)
            layout.addWidget(self.remove_button, 0, Qt.AlignRight)

            layout.setSpacing(0)
            layout.setMargin(0)
            self.remove_button.setObjectName(
                'remove_button_'+str(i)
            )
            # Signal for removing rows using remove_button
            self.remove_button.clicked.connect(
                partial(lambda: self.remove_self_row(line_widget, label))
            )

    def remove_self_row(self, line_widget, label):
        line_widget.setParent(None)
        label.setParent(None)

Изменить. Уникальность этого вопроса заключается в том, что параметры также являются динамическими, поскольку они поступают из цикла.

Изменить: ниже я добавил полный исполняемый код.

from functools import partial
from PyQt4.QtGui import *
from PyQt4.QtCore import *

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_Points(object):
    def setupUi(self, Points):
        Points.setObjectName(_fromUtf8("Points"))
        Points.resize(458, 418)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(Points.sizePolicy().hasHeightForWidth())
        Points.setSizePolicy(sizePolicy)
        self.verticalLayout_3 = QtGui.QVBoxLayout(Points)
        self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
        self.coordFormContainer = QtGui.QWidget(Points)
        self.coordFormContainer.setObjectName(_fromUtf8("coordFormContainer"))
        self.verticalLayout_5 = QtGui.QVBoxLayout(self.coordFormContainer)
        self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5"))
        self.scrollArea = QtGui.QScrollArea(self.coordFormContainer)
        self.scrollArea.setFrameShape(QtGui.QFrame.NoFrame)
        self.scrollArea.setLineWidth(0)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName(_fromUtf8("scrollArea"))
        self.scrollAreaWidgetContents = QtGui.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 414, 374))
        self.scrollAreaWidgetContents.setObjectName(_fromUtf8("scrollAreaWidgetContents"))
        self.verticalLayout_2 = QtGui.QVBoxLayout(self.scrollAreaWidgetContents)
        self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
        self.horizontalLayout_7 = QtGui.QHBoxLayout()
        self.horizontalLayout_7.setObjectName(_fromUtf8("horizontalLayout_7"))
        self.AddPointButton = QtGui.QPushButton(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.AddPointButton.sizePolicy().hasHeightForWidth())
        self.AddPointButton.setSizePolicy(sizePolicy)
        self.AddPointButton.setObjectName(_fromUtf8("AddPointButton"))
        self.horizontalLayout_7.addWidget(self.AddPointButton)
        self.RemovePoint = QtGui.QPushButton(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.RemovePoint.sizePolicy().hasHeightForWidth())
        self.RemovePoint.setSizePolicy(sizePolicy)
        self.RemovePoint.setObjectName(_fromUtf8("RemovePoint"))
        self.horizontalLayout_7.addWidget(self.RemovePoint)
        self.verticalLayout_2.addLayout(self.horizontalLayout_7)
        self.formLayout_2 = QtGui.QFormLayout()
        self.formLayout_2.setFieldGrowthPolicy(QtGui.QFormLayout.ExpandingFieldsGrow)
        self.formLayout_2.setObjectName(_fromUtf8("formLayout_2"))
        self.p1Label = QtGui.QLabel(self.scrollAreaWidgetContents)
        self.p1Label.setObjectName(_fromUtf8("p1Label"))
        self.formLayout_2.setWidget(1, QtGui.QFormLayout.LabelRole, self.p1Label)
        self.p1LineEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.p1LineEdit.sizePolicy().hasHeightForWidth())
        self.p1LineEdit.setSizePolicy(sizePolicy)
        self.p1LineEdit.setInputMethodHints(QtCore.Qt.ImhNone)
        self.p1LineEdit.setObjectName(_fromUtf8("p1LineEdit"))
        self.formLayout_2.setWidget(1, QtGui.QFormLayout.FieldRole, self.p1LineEdit)
        self.p2Label = QtGui.QLabel(self.scrollAreaWidgetContents)
        self.p2Label.setObjectName(_fromUtf8("p2Label"))
        self.formLayout_2.setWidget(2, QtGui.QFormLayout.LabelRole, self.p2Label)
        self.p2LineEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.p2LineEdit.sizePolicy().hasHeightForWidth())
        self.p2LineEdit.setSizePolicy(sizePolicy)
        self.p2LineEdit.setObjectName(_fromUtf8("p2LineEdit"))
        self.formLayout_2.setWidget(2, QtGui.QFormLayout.FieldRole, self.p2LineEdit)
        self.p3Label = QtGui.QLabel(self.scrollAreaWidgetContents)
        self.p3Label.setObjectName(_fromUtf8("p3Label"))
        self.formLayout_2.setWidget(3, QtGui.QFormLayout.LabelRole, self.p3Label)
        self.p3LineEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.p3LineEdit.sizePolicy().hasHeightForWidth())
        self.p3LineEdit.setSizePolicy(sizePolicy)
        self.p3LineEdit.setObjectName(_fromUtf8("p3LineEdit"))
        self.formLayout_2.setWidget(3, QtGui.QFormLayout.FieldRole, self.p3LineEdit)
        self.p4Label = QtGui.QLabel(self.scrollAreaWidgetContents)
        self.p4Label.setObjectName(_fromUtf8("p4Label"))
        self.formLayout_2.setWidget(4, QtGui.QFormLayout.LabelRole, self.p4Label)
        self.p4LineEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents)
        sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.p4LineEdit.sizePolicy().hasHeightForWidth())
        self.p4LineEdit.setSizePolicy(sizePolicy)
        self.p4LineEdit.setObjectName(_fromUtf8("p4LineEdit"))
        self.formLayout_2.setWidget(4, QtGui.QFormLayout.FieldRole, self.p4LineEdit)
        self.verticalLayout_2.addLayout(self.formLayout_2)
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.verticalLayout_5.addWidget(self.scrollArea)
        self.verticalLayout_3.addWidget(self.coordFormContainer)

        self.retranslateUi(Points)
        QtCore.QMetaObject.connectSlotsByName(Points)

    def retranslateUi(self, Points):
        Points.setWindowTitle(_translate("Points", "Window", None))
        self.AddPointButton.setToolTip(_translate("Points", "<html><head/><body><p>Test Tool</p></body></html>", None))
        self.AddPointButton.setText(_translate("Points", "Add point", None))
        self.RemovePoint.setText(_translate("Points", "Remove Point", None))
        self.p1Label.setText(_translate("Points", "P1", None))
        self.p1LineEdit.setPlaceholderText(_translate("Points", "788524.328102, 341547.783899", None))
        self.p2Label.setText(_translate("Points", "P2", None))
        self.p3Label.setText(_translate("Points", "P3", None))
        self.p4Label.setText(_translate("Points", "P4", None))



class Window(QDialog, Ui_Points):
    def __init__(self):
        QDialog.__init__(self)
        self.setupUi(self)
        self.points = 4
        self.coordFormContainer.setFixedHeight(300)
        self.coordFormContainer.setFixedWidth(400)
        self.AddPointButton.clicked.connect(
            self.add_row
        )
        self.RemovePoint.clicked.connect(
            self.remove_row
        )
        for i, (line_widget, label) in enumerate(
            zip(self.coordFormContainer.findChildren(QLineEdit),
                self.coordFormContainer.findChildren(QLabel)
            )
        ):

            self.remove_button = QToolButton(line_widget)
            self.remove_button.setText('x')
            self.remove_button.setCursor(Qt.PointingHandCursor)

            self.remove_button.setFocusPolicy(Qt.NoFocus)

            self.remove_button.setStyleSheet(
                'background: red; '
                'color: red;'
                'border: none;'
            )
            self.remove_button.setToolTip(
                QApplication.translate(
                    'Window',
                    'Remove this point'
                )
            )
            self.remove_button.setStyleSheet(
                'QToolTip { '
                    'color: #555; '
                    'background-color: #ddd; '
                    'border: 1px solid #eee; '
                '}'
            )
            layout = QHBoxLayout(line_widget)
            layout.addWidget(self.remove_button, 0, Qt.AlignRight)

            layout.setSpacing(0)
            layout.setMargin(0)
            self.remove_button.setObjectName(
                'remove_button_'+str(i)
            )
            # Signal for removing rows using remove_button
            self.remove_button.clicked.connect(
                partial(lambda: self.remove_self_row(line_widget, label))
            )


    def remove_self_row(self, line_widget, label):
        """
        A slot raised to remove parent lineedit of a close button.
        :param line_widget: The lineedit to be removed
        :param label: The lable to be removed
        :return:
        """
        line_widget.setParent(None)
        label.setParent(None)

    def add_row (self):
        """
        A slot raised to add new point row.
        :return: None
        """
        self.points = self.points + 1
        self.label = QLabel()
        self.label.setObjectName(
            'p'+str(self.points)+'Label'
        )
        self.label.setText(
            "P"+str(self.points)
        )
        self.formLayout_2.setWidget(
            self.points,
            QFormLayout.LabelRole,
            self.label
        )
        self.line_edit = QLineEdit()
        self.line_edit.setObjectName(
            'p'+str(self.points)+'LineEdit'
        )
        self.formLayout_2.setWidget(
            self.points,
            QFormLayout.FieldRole,
            self.line_edit
        )
        # add remove button
        self.add_remove_button(self.line_edit)

    def add_remove_button(self, line_widget):
        """
        Adds the remove button on the lineedit specified
        :param line_widget: The parent lineedit of the remove button
        :return:
        """
        self.remove_button = QToolButton(line_widget)
        self.remove_button.setCursor(Qt.PointingHandCursor)
        self.remove_button.setFocusPolicy(Qt.NoFocus)
        self.remove_button.setText('x')
        self.remove_button.setStyleSheet(
            'background: red; '
            'color: red;'
            'border: none;'
        )
        self.remove_button.setToolTip(
            QApplication.translate(
                'Point',
                'Remove this point'
            )
        )
        self.remove_button.setStyleSheet(
            'QToolTip { '
                'color: #555; '
                'background-color: #ddd; '
                'border: 1px solid #eee; '
            '}'
        )
        layout = QHBoxLayout(line_widget)
        layout.addWidget(self.remove_button, 0, Qt.AlignRight)
        layout.setSpacing(0)
        layout.setMargin(0)


    def remove_row(self):
        """
        A slot raised to remove point row.
        :return: None
        """
        try:
            # Remove label and lineedit
            self.formLayout_2.itemAt(
                self.points, QFormLayout.FieldRole
            ).widget().setParent(None)
            self.formLayout_2.itemAt(
                self.points, QFormLayout.LabelRole
            ).widget().setParent(None)
            self.points = self.points - 1
        except AttributeError:
            return

if __name__ == '__main__':

    import sys
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    window.resize(320, 240)
    sys.exit(app.exec_())

Чтобы воспроизвести проблему, нажмите одну из кнопок удаления. Он удаляет только P4 и больше не удаляет. Та же проблема с добавленными точками.


person wondim    schedule 10.03.2016    source источник
comment
Спасибо за находку, но решение не решило мою проблему. Мои параметры создаются из цикла, но его задаются вручную. Не могли бы вы удалить повторяющийся заголовок?   -  person wondim    schedule 11.03.2016
comment
Я не верю, что это имеет какое-то значение. Я подозреваю, что либо вы допустили ошибку, реализуя значения по умолчанию для аргументов лямбда-функции, либо метод findChildren возвращает виджеты в порядке, в котором вы не ожидаете. В любом случае, для решения проблемы, скорее всего, потребуется реальный работоспособный минимально воспроизводимый пример.   -  person three_pineapples    schedule 11.03.2016
comment
Спасибо за вклад, я добавил полный исполняемый код в вопрос.   -  person wondim    schedule 11.03.2016
comment
Изменение лямбда на lambda state, line_widget=line_widget, label=label: self.remove_self_row(line_widget, label) заставляет ваш код работать должным образом. Это очень небольшое отклонение от отмеченного дубликата, потому что сигнал clicked выдает аргумент, о котором вы должны позаботиться, но игнорируете (см. this ответ)   -  person three_pineapples    schedule 12.03.2016
comment
Огромное спасибо за помощь. Я добавил код и работающий код, он хорошо работает с точками по умолчанию, однако, когда я добавляю больше и больше, он перестает работать. Он не может закрыться. В основном приложении проблема та же, за исключением того, что порядок удаления меток не правильный. Скажем, когда я удаляю пункт 3, он удаляет метку 1. Должен ли я сохранить functools.partial?   -  person wondim    schedule 12.03.2016
comment
Я не очень понимаю все ваши проблемы. Вы можете удалить functools.partial, если хотите. Это будет работать нормально. Когда вы добавляете новые точки, вы не устанавливаете никаких сигнальных соединений с новой созданной кнопкой. Почему вы ожидаете, что эта кнопка все еще работает? Я бы предложил задать новый вопрос об одной конкретной проблеме, поскольку ваш первоначальный вопрос был решен с помощью ответа в помеченном дубликате.   -  person three_pineapples    schedule 12.03.2016
comment
Хорошо, давайте поговорим только о рублевом коде. Он хорошо работает с line_edits, которые создаются при первом запуске. Однако, когда я добавляю новую строку с помощью кнопки «Добавить», тот же сигнал, который добавляется внутри метода add_row, не работает должным образом. Я предполагаю, что это может быть вызвано параметрами, поскольку они являются свойством класса, а не локальными переменными.   -  person wondim    schedule 12.03.2016
comment
Теперь проблема исправлена. Это было из-за использования свойства класса. Но я все еще должен задать еще один вопрос, чтобы заполнить метки. Большое спасибо за помощь three_pineapples.   -  person wondim    schedule 12.03.2016