0

I am trying to create insert a remove tool button dynamically inside a lineedit and also associate slot to the remove tool. What complicates the issue is the slot has parameters that is, lineedit and label of rows to be removed. So as a result, I have to use lambda. The problem is the remove button only removes one row and not in a correct order. I have tried functools.partial and lambda with i=i but both are not working. Below is the code that I have.

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)

Edit: What makes this question unique is that, the parameters are also dynamic as they come from the loop.

Edit: I have added the full runable code below.

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

To reproduce the issue click on one of the remove button. It only removes P4 and don't remove any more. The same issue for the added points.

wondim
  • 697
  • 15
  • 29
  • Thank you for the find but the solution there didn't solve my problem. My parameters are created from the loop but his are manual set. So could you remove the duplicate title? – wondim Mar 11 '16 at 00:32
  • I don't believe that makes any difference. I suspect either you made a mistake implementing the default values for argument's for the lambda function or the `findChildren` method is returning widgets in an order you are not expecting. Either way, an actual runnable [mcve] will likely be required to solve the problem. – three_pineapples Mar 11 '16 at 10:15
  • Thank you for the input, I have added the full runable code in the question. – wondim Mar 11 '16 at 12:36
  • Changing the lambda to `lambda state, line_widget=line_widget, label=label: self.remove_self_row(line_widget, label)` makes your code work as expected. This is a very slight deviation from the marked duplicate because the `clicked` signal emits an argument that you have to take care of, but ignore (see [this](http://stackoverflow.com/a/35821092/1994235) answer) – three_pineapples Mar 12 '16 at 01:46
  • Thank you very much for the help. I added the code and on the runable code, it works well with the default points, however, when I add more, and more, it stops working. It can't close. On the main application, the issue is the same, except that the order of the labels removal is not correct. Say when I remove point 3, it removes label 1. Shall I keep functools.partial? – wondim Mar 12 '16 at 04:02
  • I'm not really understanding all of your issues. You can remove `functools.partial` if you wish. It will work fine. When you add new points, you don't make any signal connections to the new button that is created. Why would you expect that button to work yet? I would suggest asking a **new question** about a single specific issue as your original question has been solved by the answer in the flagged duplicate. – three_pineapples Mar 12 '16 at 06:53
  • Ok lets only talk about the ruable code. It works well with the line_edits that are created on first run. However, when I add new row using the add button, the same signal that is added inside add_row method is not working as expected. My guess is it could be caused by the parameters, as they are class property than local variables. – wondim Mar 12 '16 at 17:10
  • The issue is now fixed. It was because of the use of a class property. But I have to still ask another question to populate Labels. Thank you very much for the help three_pineapples. – wondim Mar 12 '16 at 20:08

0 Answers0