pyqt dynamic generate QMenu action and connect
Asked Answered
V

3

6

Still learning how pyqt works. I want to dynamically generate a customContextMenu and connect with a function. So far I got the following but the connect part not working ?

import sys
from PyQt4 import QtGui, QtCore

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

    # create button
    self.button = QtGui.QPushButton("test button", self)       
    self.button.resize(100, 30)

    # set button context menu policy
    self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    self.connect(self.button, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
    self.popMenu = QtGui.QMenu(self)

    def on_context_menu(self, point):
        self.popMenu.clear()

        #some test list for test
        testItems = ['itemA', 'itemB', 'itemC']
        for item in testItems:
            action = self.btn_selectPyFilterPopMenu.addAction("Selected %s" % item)
            self.connect(action,QtCore.SIGNAL("triggered()"),self,QtCore.SLOT("printItem('%s')" % item))    
        self.popMenu.exec_(self.button.mapToGlobal(point))

    @pyqtSlot(str)
    def printItem(self, item):
        print item

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
Vineyard answered 5/12, 2013 at 2:35 Comment(1)
'exec_' blocks main loop. I don't recommend it.Egwin
D
18

Your code is almost right. You just need to connect the signals to a lambda with a default argument, like this:

    for item in testItems:
        action = self.popMenu.addAction('Selected %s' % item)
        action.triggered.connect(
            lambda chk, item=item: self.printItem(item))

The default argument ensures that each lambda gets a copy of the current loop variable. Also note that an initial chk argument is also required. This is because the triggered signal sends its current checked-state (true or false) by default, which would clobber the item argument of the lambda.

Finally, I would urge to use the new-style syntax when connecting signals - the old style can be very error-prone, and is far less pythonic.

Dolph answered 5/12, 2013 at 6:10 Comment(2)
Thank you ekhumoro... there are other solutions posted on SO, but they don't work. You have repeatedly solved many of my problems and I wish I knew you personally.Tymothy
@panofish. Glad I was able to help :)Dolph
T
2

I tryed and correct the example given in the first post. Here is a working version. Right click on the button, select an item and it will be printed inn your terminal :

import sys
from PyQt4 import QtGui, QtCore
class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

        # create button
        self.button = QtGui.QPushButton("test button",self)       
        self.button.resize(100, 30)

        # set button context menu policy
        self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.connect(self.button, QtCore.SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
        self.popMenu = QtGui.QMenu(self)

    def on_context_menu(self, point):
        self.popMenu.clear()

        #some test list for test
        testItems = ['itemA', 'itemB', 'itemC']
        for item in testItems:
        action = self.popMenu.addAction('Selected %s' % item)
        action.triggered[()].connect(
            lambda item=item: self.printItem(item))
        self.popMenu.exec_(self.button.mapToGlobal(point))

    @QtCore.pyqtSlot(str)
    def printItem(self, item):
        print item

def main():
    app = QtGui.QApplication(sys.argv)
    form = MainForm()
    form.show()
    app.exec_()

if __name__ == '__main__':
    main()
Tutt answered 21/12, 2014 at 16:8 Comment(0)
C
1

If I understand you right:

import sys
from PyQt4 import QtGui, QtCore

class MainForm(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(MainForm, self).__init__(parent)

    # create button
    self.button = QtGui.QPushButton("test button", self)       
    self.button.resize(100, 30)

    # set button context menu policy
    self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    self.customContextMenuRequested.connect (self.on_context_menu)

    def on_context_menu(self, point):

        popMenu = QtGui.QMenu(self)

        #some test list for test
        testItems = ['itemA', 'itemB', 'itemC']

        #
        for item in testItems:
            action = QtGui.Action(item)
            action.triggered.connect(lambda x: print item)

        popMenu.exec_(self.button.mapToGlobal(point))
Cardinalate answered 5/12, 2013 at 2:41 Comment(3)
how do I pass the value to a function? I have a test function printItem. I need to be able to know what item I've clicked in a dynamically generated menu. Thanks!Vineyard
If you want to create your own signal, you must subclass QAction. See this: https://mcmap.net/q/216494/-passing-an-argument-to-a-slotCardinalate
consider using a QSignalMapperTelium

© 2022 - 2024 — McMap. All rights reserved.