Jaime02
Jaime02

Reputation: 347

Delete dynamically create buttons in PyQt5

I have created a button who creates buttons in a vertical layout, called "elementos". In each row, there is a label and a X button who should delete the row. There is a list with the text of the label called "puntos" MRE:

from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QOpenGLWidget
import sys
from sys import argv, exit


class Renderizador(QOpenGLWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.puntos = []
        self.num_elementos = 0

    def borrar_punto(self):
        self.puntos.pop()
        ui.elementos.removeWidget(self.name)
        ui.elementos.removeWidget(self.borrar)
        self.name.deleteLater()
        self.borrar.deleteLater()
        self.num_elementos -= 1

    def crear_punto(self):
        do = ui.valor_do.value()
        cota = ui.valor_cota.value()
        alejamiento = ui.valor_alej.value()
        self.puntos.append((ui.nombre.toPlainText(), do, cota, alejamiento))
        self.name = QtWidgets.QLabel()
        self.name.setText(ui.nombre.toPlainText() + "({}, {}, {})".format(do, cota, alejamiento))
        self.borrar = QtWidgets.QPushButton()
        self.borrar.setText("X")
        self.borrar.clicked.connect(lambda: self.borrar_punto())

        ui.elementos.addWidget(self.name, self.num_elementos, 0, 1, 1)
        ui.elementos.addWidget(self.borrar, self.num_elementos, 1, 1, 1)
        self.num_elementos += 1
        self.update()


class UiVentana:
    def __init__(self):
        ventana.resize(1500, 1000)
        ventana.setLayoutDirection(QtCore.Qt.LeftToRight)
        ventana.setFixedSize(ventana.size())

        self.widget_central = QtWidgets.QWidget(ventana)
        self.gridLayoutWidget = QtWidgets.QWidget(self.widget_central)
        self.gridLayoutWidget.setGeometry(QtCore.QRect(1000, 60, 280, 50))
        self.Vistas = QtWidgets.QGridLayout(self.gridLayoutWidget)

        self.Visor = Renderizador(self.widget_central)
        self.Visor.setGeometry(QtCore.QRect(0, 0, 1000, 1000))

        self.gridLayoutWidget_2 = QtWidgets.QWidget(self.widget_central)
        self.gridLayoutWidget_2.setGeometry(QtCore.QRect(1004, 105, 300, 400))
        self.Punto = QtWidgets.QGridLayout(self.gridLayoutWidget_2)
        self.Punto.setContentsMargins(5, 5, 5, 5)
        self.Punto.setVerticalSpacing(4)
        self.texto_nombre = QtWidgets.QLabel(self.gridLayoutWidget_2)
        self.texto_nombre.setText("Nombre:")
        self.Punto.addWidget(self.texto_nombre, 3, 0, 1, 1)
        self.crear_punto = QtWidgets.QPushButton(self.gridLayoutWidget_2)
        self.crear_punto.setText("Crear")
        self.crear_punto.clicked.connect(self.Visor.crear_punto)

        self.Punto.addWidget(self.crear_punto, 3, 2, 1, 1)
        self.texto_cota = QtWidgets.QLabel(self.gridLayoutWidget_2)

        self.nombre = QtWidgets.QTextEdit(self.gridLayoutWidget_2)
        self.nombre.setMaximumSize(QtCore.QSize(100, 24))
        self.Punto.addWidget(self.nombre, 3, 1, 1, 1)

        self.scroll = QtWidgets.QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.scroll_widget = QtWidgets.QWidget()
        self.scroll_widget.resize(200, 300)
        self.elementos_widget = QtWidgets.QWidget()
        self.vbox = QtWidgets.QVBoxLayout(self.scroll_widget)
        self.vbox.setContentsMargins(0, 0, 0, 0)
        self.vbox.addWidget(self.elementos_widget)
        self.vbox.addStretch()
        self.elementos = QtWidgets.QGridLayout()
        self.elementos_widget.setLayout(self.elementos)
        self.scroll.setWidget(self.scroll_widget)
        self.scroll_widget.setLayout(self.elementos)
        self.scroll.setWidget(self.scroll_widget)
        self.Punto.addWidget(self.scroll, 4, 0, 1, 3)

        self.valor_do = QtWidgets.QSpinBox(self.gridLayoutWidget_2)
        self.Punto.addWidget(self.valor_do, 2, 0, 1, 1)
        self.valor_alej = QtWidgets.QSpinBox(self.gridLayoutWidget_2)
        self.Punto.addWidget(self.valor_alej, 2, 1, 1, 1)
        self.valor_cota = QtWidgets.QSpinBox(self.gridLayoutWidget_2)
        self.Punto.addWidget(self.valor_cota, 2, 2, 1, 1)

        ventana.setCentralWidget(self.widget_central)
        ventana.show()


def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)


if __name__ == "__main__":
    app = QtWidgets.QApplication(argv)
    ventana = QtWidgets.QMainWindow()
    ui = UiVentana()
    sys.excepthook = except_hook
    exit(app.exec_())

The pop statement should delete from the list the corresponding info about the label. I get an out of index range error, but i get a "RuntimeError: wrapped C/C++ object of type QLabel has been deleted" error if I pop the last statement. Full code: https://github.com/Jaime02/Proyecto-de-investigacion-2019-Dibujo-tecnico/blob/experimental/error (Lines 120-140)

Edit: The delete button only works once if two or more rows are created

Upvotes: 1

Views: 687

Answers (1)

eyllanesc
eyllanesc

Reputation: 243955

Your code has at least the following errors:

  • If you are going to create an object within a loop, do not make it a member, for example in your case self.name and self.borrar are variables that will always point to the last QLabel and QPushButton, so when you delete only the last row will be deleted in a single occasion.

  • Your code is very messy since they are modifying variables in places where they are not due as they could cause problems such as tracking errors, for example the variable window and ui.

Considering the above I have rewritten your code implementing the logic of passing the widgets and deleting the widgets directly.

import sys
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets


class UiVentana(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(UiVentana, self).__init__(parent)

        self.puntos = []

        self.visor = QtWidgets.QOpenGLWidget()

        self.valor_do = QtWidgets.QSpinBox()
        self.valor_cota = QtWidgets.QSpinBox()
        self.valor_alej = QtWidgets.QSpinBox()

        self.texto_nombre = QtWidgets.QLabel("Nombre")
        self.nombre = QtWidgets.QLineEdit()
        self.crear_punto = QtWidgets.QPushButton("Crear", clicked=self.crear_punto)

        elementos_widget = QtWidgets.QWidget()

        scroll_widget = QtWidgets.QWidget()
        scroll = QtWidgets.QScrollArea(widgetResizable=True)
        scroll.setWidget(scroll_widget)

        vbox = QtWidgets.QVBoxLayout(scroll_widget)
        vbox.addWidget(elementos_widget)
        vbox.addStretch()

        self.elementos = QtWidgets.QGridLayout(elementos_widget)

        grid_layout = QtWidgets.QGridLayout()
        grid_layout.addWidget(self.valor_do, 0, 0)
        grid_layout.addWidget(self.valor_cota, 0, 1)
        grid_layout.addWidget(self.valor_alej, 0, 2)
        grid_layout.addWidget(self.texto_nombre, 1, 0)
        grid_layout.addWidget(self.nombre, 1, 1)
        grid_layout.addWidget(self.crear_punto, 1, 2)
        grid_layout.addWidget(scroll, 2, 0, 1, 3)

        for i in range(3):
            grid_layout.setColumnStretch(i, 1)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QHBoxLayout(central_widget)
        lay.addWidget(self.visor, stretch=1)
        lay.addLayout(grid_layout)

        self.resize(1280, 960)


    def crear_punto(self):
        do = self.valor_do.value()
        cota = self.valor_cota.value()
        alejamiento = self.valor_alej.value()
        name = QtWidgets.QLabel(
            "{}({}, {}, {})".format(self.nombre.text(), do, cota, alejamiento)
        )

        punto = (self.nombre.text(), do, cota, alejamiento)

        borrar = QtWidgets.QPushButton("X")

        wrapper = partial(self.borrar_punto, (name, borrar), punto)
        borrar.clicked.connect(wrapper)

        row = self.elementos.rowCount()
        self.elementos.addWidget(name, row, 0)
        self.elementos.addWidget(borrar, row, 1)

        self.puntos.append(punto)

    def borrar_punto(self, widgets, punto):
        if self.puntos:
            name, borrar = widgets
            name.deleteLater()
            borrar.deleteLater()
            self.puntos.remove(punto)
            print(self.puntos)


def except_hook(cls, exception, traceback):
    sys.__excepthook__(cls, exception, traceback)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    sys.excepthook = except_hook
    w = UiVentana()
    w.show()
    exit(app.exec_())

Upvotes: 2

Related Questions