jmizrahi
jmizrahi

Reputation: 63

Keep menu open after clicking on the button it is launched with

I have a QToolButton with a menu. When the QToolButton is clicked, the menu appears. The default behavior is that when an action is clicked from the menu, the menu disappears. How can I make it so that the menu stays open until the user clicks elsewhere?

Here is minimal code that shows the behavior:

from PyQt4 import QtGui, QtCore
import sys, os

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    toolButton = QtGui.QToolButton()
    toolButton.setText('Select')
    toolMenu = QtGui.QMenu()
    for i in range(3):
        action = toolMenu.addAction(str(i))
        action.setCheckable(True)
    toolButton.setMenu(toolMenu)
    toolButton.setPopupMode(QtGui.QToolButton.InstantPopup)
    toolButton.show()
    sys.exit(app.exec_())

Upvotes: 2

Views: 3179

Answers (4)

Jason
Jason

Reputation: 3296

I made a PyQt5 version based on @three_pineapples's answer and solved what @Space Hornet tried to solve--get the states of checkboxes.

According to the doc of QWidgetAction:

Note that it is up to the widget to activate the action, for example by reimplementing mouse event handlers and calling QAction::trigger().

So I think one needs to connect the checkbox's stateChanged signal to the action's trigger method.

I also added the a text to the action therefore action.text() gives the same text label as the checkbox. May not be necessary though.

Complete code below:

import sys
from PyQt5 import QtWidgets
from PyQt5.QtCore import pyqtSlot

@pyqtSlot(QtWidgets.QAction)
def menuTriggered(action):
    print('state change=',action.text())
    return

@pyqtSlot(QtWidgets.QMenu)
def buttonTriggered(menu):
    actions=menu.findChildren(QtWidgets.QWidgetAction)
    for actii in actions:
        wii=actii.defaultWidget()
        stateii=wii.isChecked()
        print('action', actii.text(), 'is checked:',stateii)
    return

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    toolButton = QtWidgets.QToolButton()
    toolButton.setText('Select')
    toolMenu = QtWidgets.QMenu()

    for i in range(3):
        checkBox = QtWidgets.QCheckBox(str(i), toolMenu)
        checkableAction = QtWidgets.QWidgetAction(toolMenu)
        checkableAction.setDefaultWidget(checkBox)

        # Add a text to action, for easier handling in slot
        checkableAction.setText(str(i))

        # Connect the checkbox's stateChanged to QAction.trigger
        checkBox.stateChanged.connect(checkableAction.trigger)
        toolMenu.addAction(checkableAction)

    toolMenu.triggered.connect(menuTriggered)
    toolButton.setMenu(toolMenu)
    toolButton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)

    # NOTE that toolButton.clicked work, toolButton.triggered not
    toolButton.clicked.connect(lambda: buttonTriggered(toolMenu))
    toolButton.show()
    sys.exit(app.exec_())

Upvotes: 1

Space Hornet
Space Hornet

Reputation: 1

I was looking for the exact same thing and used the code from three_pineapples, but I had trouble connecting it the way I wanted. I thought I'd share my solution in case anyone else finds it useful. The button function is very similar but my code includes my solution for connecting the checkboxes to a function. Also, since they are stored in a list one can connect them individually or in a loop if that's easier.

from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys, os

##### main window class #####
class main_window(QMainWindow):
    def __init__(self):
        super(main_window, self).__init__()
        self.resize(300, 200)

        wdgMain = QWidget()
        self.setCentralWidget(wdgMain)
        layMain = QGridLayout(wdgMain)
        wdgMain.setLayout(layMain)

        ## checkable tool button ##
        tlbToolButton1 = QToolButtonChx("Check Me Out!")
        layMain.addWidget(tlbToolButton1, 0, 0)
        tlbToolButton1.addItems(["Item" + str(n) for n in range(8)])

        ## connect tool button checkboxes ##
        for i in range(tlbToolButton1.length()):
            tlbToolButton1.index(i).stateChanged.connect(self.checkbox_tester)

    def checkbox_tester(self, choice):
        objSender = self.sender()
        strObjectName = objSender.objectName()
        print "Action Checker::", strObjectName, ":", choice
##### end of main window class #####



##### checkable tool button class #####
class QToolButtonChx(QToolButton):
    def __init__(self, strText=""):
        super(QToolButtonChx, self).__init__()

        self.setText(strText)
        tlbMenu = QMenu(self)
        self.setMenu(tlbMenu)
        self.setPopupMode(QToolButton.MenuButtonPopup)
        self.lstchxItems = []

    def addItem(self, strItem):
        self.lstchxItems.append(QCheckBox(strItem, self.menu()))
        actCheckItem = QWidgetAction(self.menu())
        actCheckItem.setDefaultWidget(self.lstchxItems[-1])
        self.lstchxItems[-1].setObjectName('chx' + strItem)
        self.menu().addAction(actCheckItem)

    def addItems(self, lstItems):
        for strItem in lstItems:
            self.lstchxItems.append(QCheckBox(strItem, self.menu()))
            actCheckItem = QWidgetAction(self.menu())
            actCheckItem.setDefaultWidget(self.lstchxItems[-1])
            self.lstchxItems[-1].setObjectName('chx' + strItem)
            self.menu().addAction(actCheckItem)

    def index(self, intIndex):
        return self.lstchxItems[intIndex]

    def length(self):
        return len(self.lstchxItems)
##### end of checkable tool button class #####


if __name__ == '__main__':
    app = QApplication(sys.argv)
    winMain = QMainWindow()
    gui = main_window()
    gui.show()
    sys.exit(app.exec_())

Upvotes: 0

three_pineapples
three_pineapples

Reputation: 11869

Shamelessly porting this code from this c++ answer:

from PyQt4 import QtGui, QtCore
import sys, os

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    toolButton = QtGui.QToolButton()
    toolButton.setText('Select')
    toolMenu = QtGui.QMenu()
    for i in range(3):    
        checkBox = QtGui.QCheckBox(str(i), toolMenu)
        checkableAction = QtGui.QWidgetAction(toolMenu)
        checkableAction.setDefaultWidget(checkBox)
        toolMenu.addAction(checkableAction)
    toolButton.setMenu(toolMenu)
    toolButton.setPopupMode(QtGui.QToolButton.InstantPopup)
    toolButton.show()
    sys.exit(app.exec_())

Upvotes: 1

Alexander Lutsenko
Alexander Lutsenko

Reputation: 2160

The easiest solution I've managed to find is to make an addition to actionEvent:

class myMenu(QtGui.QMenu):
    def actionEvent(self, event):
        super().actionEvent(event)
        self.show()

Upvotes: 0

Related Questions