0

I am trying to create a side menu using PyQT6. I want the menu to be passed as a list like this

menu = [ ("File", [("New", newHandler), ("Open", openHandler), ("Exit", exitHandler)] ), ("Edit", [("Copy", copyHandler), ("Paste", pasteHandler)] ) ]

So this menu variable encapsulates the fact that there is going to be two broad menus: File Menu and Edit menu. Under File Menu, there is New, Open and Exit. Similarly, under Edit menu, there is Copy and Paste.

I will pass this to a SideMenu object like this which will construct the menu as specified

SideMenu(menu)

Now the SideMenu Class is like this:

class SideMenu(QWidget):

    def __init__(self, menu):
        QWidget.__init__(self)
        self.layout = QVBoxLayout(self)

        for menuItem, actions in menu:
            self.layout.addWidget(QLabel(menuItem))
            for actionName, actionHandler in actions:
                btn=QPushButton(actionName)
                btn.clicked.connect(lambda: actionHandler(btn))
                self.layout.addWidget(btn)
        self.show()


def newHandler(x):
    print("Inside newHandler")
def openHandler(x):
    print("Inside openHandler")
def exitHandler(x):
    print("Inside exitHandler")
def copyHandler(x):
    print("Inside copyHandler")
def pasteHandler(x):
    print("Inside pasteHandler")

menu = [ ("File", [("New", newHandler), ("Open", openHandler), ("Exit", exitHandler)] ), ("Edit", [("Copy", copyHandler), ("Paste", pasteHandler)] ) ]

app = QApplication(sys.argv)
screen = SideMenu(menu)
screen.show()
sys.exit(app.exec())

However, the problem is whenever any button is pressed, just the pasteHandler is called. i.e. I get to see the output Inside pasteHandler no matter which button is pressed.

However, if I modify just one line of the above program from

btn.clicked.connect(lambda: actionHandler(btn))

to

btn.clicked.connect(actionHandler)

this works perfectly !

I dont understand why it should happen. I need to pass the button to the handler function definitely. But if try to pass the button as an argument to the handler function, only the last handler function is executed on each button click.

user257330
  • 63
  • 13
  • I think it's a python 'issue' with late binding explained e.g. here: https://stackoverflow.com/questions/3431676/creating-functions-or-lambdas-in-a-loop-or-comprehension – chehrlic Nov 21 '22 at 17:42
  • @user257330 `btn.clicked.connect(lambda _, handler=actionHandler, btn=btn: handler(btn))`. Alternatively: `btn.clicked.connect(partial(actionHandler, btn))`. Note that Qt allows to get the object that emitted the signal through `self.sender()`, but it only works when implemented in a QObject subclass (in your case, by making the functions as methods of `SideMenu`), or by using a reference to an object that is within the same thread. – musicamante Nov 22 '22 at 01:21
  • @chehrlic it's not an issue, it's how Python (and many other languages) can work with namespaces and closures. Both `actionHandler` and `btn` references are updated in the loop, but lambda will evaluate them only when it's executed, meaning that the loop has reached its end: `actionHandler` will be the last function, `btn` the last button. – musicamante Nov 22 '22 at 01:29
  • @musicamante: That's why I put it in quotation marks... – chehrlic Nov 22 '22 at 15:42
  • @chehrlic The point remains, it's not an issue, not even with quotation marks. It would have been an "issue" if it was a known (possibly minor) *problem* that was considered not so relevant for common usage, even by design. But that's not the case, that's a basic aspect of the language and its semantics. On the contrary, actually: it could even be considered as a *feature*, as long as you know how it works :-) – musicamante Nov 24 '22 at 19:41
  • If you come from c++ it's an 'issue' at first sight. At least it's unexpected. – chehrlic Nov 25 '22 at 08:00

0 Answers0