Pregunta

I'm currently trying to create quite a lot of contextMenu for lots of different buttons in a maya UI I'm working on.

The issue is that I've a long list of buttons and I need the same contextMenu for all of them. What I'm doing now is not really elegant, that's why I'm asking if you could help me.

Because with the following process, my script will be long, but I can't figure out how to clean that properly.

Here's an example of my code :

from PyQt4 import QtCore,QtGui,uic
from functools import partial
import os


class MyWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        uiFilePath = os.path.join(os.path.dirname(__file__),"myfile.ui") 
        self.ui = uic.loadUi(uiFilePath, self)

        '''
        Right Click Menu
        '''
        self.ui.buttonA.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.ui.buttonA.customContextMenuRequested.connect(self.buttonAMenu)

        self.ui.buttonB.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.ui.buttonB.customContextMenuRequested.connect(self.buttonBMenu)


    '''
    BUTTON A
    '''
    @QtCore.pyqtSlot()
    def on_buttonA_released(self):
        print ('Doing Stuff when clicking on Button A')

    def buttonAMenu(self, pos):
        menu = QtGui.QMenu()
        menu.addAction('First Action', lambda:self.FirstActionButtonA(objects))
        menu.addAction('Second Action', lambda:self.SecondActionButtonA(objects))
        menu.exec_(QtGui.QCursor.pos())

    def FirstActionButtonA(self, objects):
        print ('First Action working on :')
        print (objects)

    def SecondActionButtonA(self):
        print ('Second Action working on :')
        print (objects)

    '''
    BUTTON B
    '''
    @QtCore.pyqtSlot()
    def on_buttonB_released(self):
        print ('Doing Stuff when clicking on Button B')

    def buttonBMenu(self, pos):
        menu = QtGui.QMenu()
        menu.addAction('First Action', lambda:self.FirstActionButtonB(objects))
        menu.addAction('Second Action', lambda:self.SecondActionButtonB(objects))
        menu.exec_(QtGui.QCursor.pos())

    def FirstActionButtonB(self, objects):
        print ('First Action working on :')
        print (objects)

    def SecondActionButtonB(self):
        print ('Second Action working on :')
        print (objects)
¿Fue útil?

Solución

So what you want to do is create a class which contains your code. You can create an instance of this class for each button or instead of each button. The two options are:

  • Create a class which you pass in a reference to a button when you instantiate it. This is the simplest, but technically less clean approach.
  • Subclass QPushButton and promote your buttons in Qt Designer to your new class

Note that the preferred solution is to subclass QPushButton and then promote your widgets in Qt Designer to your new class. However, this can be a bit fiddly to work out. But, if you want to go down this route, you can, and you'll want to read this: Promote PyQt Widget

The class will look similar either way. You'll want something like this (for option 1)

class MyButton(object):
    def __init__(self, button):
        self.button = button
        self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.button.customContextMenuRequested.connect(self.buttonMenu)
        self.button.clicked.connect(self.on_button_released)

    @QtCore.pyqtSlot()
    def on_button_released(self):
        print ('Doing Stuff when clicking on Button A')

    def buttonMenu(self, pos):
        menu = QtGui.QMenu()
        menu.addAction('First Action', lambda:self.FirstActionButton(objects))
        menu.addAction('Second Action', lambda:self.SecondActionButton(objects))
        menu.exec_(QtGui.QCursor.pos())

    def FirstActionButton(self, objects):
        print ('First Action working on :')
        print (objects)

    def SecondActionButton(self):
        print ('Second Action working on :')
        print (objects)

Your MyWidget class would then look like:

class MyWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        uiFilePath = os.path.join(os.path.dirname(__file__),"myfile.ui") 
        self.ui = uic.loadUi(uiFilePath, self)

        list_of_buttons = [self.ui.buttonA, self.ui.buttonB,...]
        self.adaptors = []
        for button in list_of_buttons:
            self.adapters.append(MyButton(button))

For option 2, you'll want to change your class to be like this:

class MyButton(QtGui.QPushButton):
    def __init__(self, *args, **kwargs):
        QPushButton.__init__(self,*args,**kwargs)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.buttonMenu)
        self.clicked.connect(self.on_button_released)

    # same code follows as in above class example

If widget promotion is done correctly, you won't need to do anything special in the __init__ method of the MyWidget class as your custom class will be instantiated automatically when you call uic.loadUI.

Otros consejos

If your button functions are all the same except for, say, the button name, you could send all 25 button signals to the same slot, and in that slot use self.sender().objectName (or similar). Although some developers don't like to use self.sender(), this is a well-established technique described in the PyQt book (Rapid GUI Programming with Python and Qt).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top