1

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

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Jaime02
  • 299
  • 7
  • 21
  • What is the view option? – eyllanesc Nov 01 '19 at 11:43
  • @eyllanesc the toggle one, my bad – Jaime02 Nov 01 '19 at 11:45
  • It works fine to my setup python3.6, Centos 7, the toggle button must be pressed on checkbox area to be toggled – chatzich Nov 01 '19 at 11:46
  • @chatzich im using python 3.7 windows 10 pyqt5 5.13.1 – Jaime02 Nov 01 '19 at 11:49
  • @PepeElMago33 Can you run it with a lower version of pyqt5? I am using 5.8 – chatzich Nov 01 '19 at 11:52
  • @PepeElMago33 I do not also like the MainEvent.exec() all pyqt tutorials are using .exec_() – chatzich Nov 01 '19 at 11:56
  • @chatzich "That is because until Python 3, exec was a reserved keyword, so the PyQt devs added underscore to it. From Python 3 onwards, exec is no longer a reserved keyword (because it is a builtin function; same situation as print), so it made sense in PyQt5 to provide a version without an underscore to be consistent with C++ docs, but to keep a version with the underscore for backwards compatibility. So for PyQt5 with Python 3, the two exec functions are the same. For older PyQt, only exec_() is available." – Jaime02 Nov 01 '19 at 11:57
  • @chatzich PyQt5 5.8 is not avaiable, says pip – Jaime02 Nov 01 '19 at 11:58
  • @PepeElMago33 try other lower versions pyqt5.10 – chatzich Nov 01 '19 at 11:59
  • 1
    @PepeElMago33 Your mistake is correct, if in an earlier version it does not reproduce it is probably due to a bug. It is not necessary to use an old version, try my solution. I have tested it with Qt 5.13.1 on Linux. – eyllanesc Nov 01 '19 at 12:00
  • 2
    @chatzich In python3 exec_() and exec () are valid. – eyllanesc Nov 01 '19 at 12:01

1 Answers1

2

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)
eyllanesc
  • 235,170
  • 19
  • 170
  • 241