Jaime02
Jaime02

Reputation: 347

QMenu does not execute methods properly

I am working with a custom QMenu which executes some methods. The menu has three options: a delete row option, a toggle variable option and a debug option, which prints the value of the toggleing variable. The code is not properly executed. Sometimes the debug button doesnt work and it suddely gets executed many times. The toggle option needs to be clicked twice to work, i dont know why. This is my MRE:

# -*- coding: utf-8 -*-

from PyQt5.QtCore import Qt, QRect, pyqtSlot
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import QWidget, QPushButton, QHBoxLayout, QMainWindow, QLabel, QMenu, \
    QApplication, QVBoxLayout, QListWidgetItem, QListWidget, QAction


class Punto(QWidget):
    def __init__(self, parent, internal_id, name):
        QWidget.__init__(self)

        # Toggle variable
        self.render = True

        self.customContextMenuRequested.connect(self.context_menu)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.menu = QMenu()
        self.borrar = QAction("Delete")
        self.ver = QAction("Toggle")
        self.debug = QAction("Debug")
        self.ver.setCheckable(True)
        self.ver.setChecked(True)

        self.parent = parent
        self.id = internal_id

        label = QLabel(name)
        hbox = QHBoxLayout()
        hbox.addWidget(label)
        hbox.addStretch(1)
        self.setLayout(hbox)

    def context_menu(self):
        self.menu.addAction(self.borrar)
        self.borrar.triggered.connect(self.delete)

        self.menu.addAction(self.ver)
        self.ver.triggered.connect(self.change)

        self.menu.addAction(self.debug)
        self.debug.triggered.connect(self.debugg)

        self.menu.exec(QCursor.pos())

    @pyqtSlot()
    def debugg(self):
        print(f"Render: {self.render}")

    @pyqtSlot()
    def change(self):
        if self.ver.isChecked():
            self.ver.setChecked(False)
            self.render = False
        else:
            self.ver.setChecked(True)
            self.render = True

    @property
    def itemid(self):
        return self.id

    @pyqtSlot()
    def delete(self):
        self.parent.delete_point(self.id)


class Ventana(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setFixedSize(200, 200)

        widget_central = QWidget(self)

        boton_punto = QPushButton(widget_central)
        boton_punto.setGeometry(QRect(0, 0, 200, 20))
        boton_punto.clicked.connect(self.crear_punto)

        boton_punto.setText("Create")

        widget_punto = QWidget(widget_central)
        widget_punto.setGeometry(QRect(0, 20, 200, 200))
        vertical_punto = QVBoxLayout(widget_punto)
        vertical_punto.setContentsMargins(0, 0, 0, 0)
        self.lista_puntos = QListWidget(widget_punto)

        vertical_punto.addWidget(self.lista_puntos)

        self.id_punto = 0
        self.setCentralWidget(widget_central)

    def crear_punto(self):
        # Add placeholder item to List
        item = QListWidgetItem()
        self.lista_puntos.addItem(item)
        # Create Custom Widget
        punto = Punto(self, self.id_punto, "A")
        self.id_punto += 1
        item.setSizeHint(punto.minimumSizeHint())
        # Set the punto widget to be displayed within the placeholder item
        self.lista_puntos.setItemWidget(item, punto)

    def delete_point(self, idd):
        for indx in range(self.lista_puntos.count()):
            item = self.lista_puntos.item(indx)
            widget = self.lista_puntos.itemWidget(item)
            if widget.id == idd:
                self.lista_puntos.takeItem(self.lista_puntos.row(item))
                break


if __name__ == "__main__":
    MainEvent = QApplication([])
    main_app = Ventana()
    main_app.show()
    MainEvent.exec()

Upvotes: 1

Views: 602

Answers (1)

eyllanesc
eyllanesc

Reputation: 243887

You have 2 errors:

  • By default a QAction already makes the change of state so it is not necessary that you implement it, but you are doing it, that is, by default the QAction changes from on to off (or vice versa) but you by code change it from off a on (or vice versa) that when done in ms the change is not observed. So instead of connecting the triggered signal, use the toggled signal and just change the render.

  • When you connect a signal to the same slot "n" times the slot is invoked "n" times, and in your case you are connecting it every time the context_menu method is invoked, there are at least 2 solutions: make the connection only once or use the type of connection Qt::UniqueConnection, in my solution I will use the first one.

Considering the above, the solution is:

class Punto(QWidget):
    def __init__(self, parent, internal_id, name):
        QWidget.__init__(self)

        # Toggle variable
        self.render = True

        self.customContextMenuRequested.connect(self.context_menu)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.menu = QMenu()
        self.borrar = QAction("Delete")
        self.ver = QAction("Toggle")
        self.debug = QAction("Debug")
        self.ver.setCheckable(True)
        self.ver.setChecked(True)

        self.parent = parent
        self.id = internal_id

        label = QLabel(name)
        hbox = QHBoxLayout(self)
        hbox.addWidget(label)
        hbox.addStretch(1)

        self.borrar.triggered.connect(self.delete)
        self.ver.toggled.connect(self.change)
        self.debug.triggered.connect(self.debugg)
        self.menu.addAction(self.borrar)
        self.menu.addAction(self.ver)
        self.menu.addAction(self.debug)

    def context_menu(self):
        self.menu.exec(QCursor.pos())

    @pyqtSlot()
    def debugg(self):
        print(f"Render: {self.render}")

    @pyqtSlot(bool)
    def change(self, state):
        self.render = self.ver.isChecked()

    @property
    def itemid(self):
        return self.id

    @pyqtSlot()
    def delete(self):
        self.parent.delete_point(self.id)

Upvotes: 2

Related Questions