1

I have a context menu on the QGraphicsView. I cannot activate the context menu of a QTextGraphicsItem. I read that I need to send a scene event to the item, but I cannot find the event needed to make the sendEvent method work.

def graphicsview_menu(self, position):
    item = self.ui.graphicsView.itemAt(position)
    if item is not None:
        self.scene.sendEvent(item, event)
        return
    # Menu for blank graphics view area
    menu = QtWidgets.QMenu()
    ...
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
Colin Curtain
  • 217
  • 1
  • 10

2 Answers2

1

As the contextMenuPolicy documentation explains:

The default value of this property is Qt::DefaultContextMenu, which means the contextMenuEvent() handler is called. [...] With Qt::CustomContextMenu, the signal customContextMenuRequested() is emitted.

This means that if you set the custom policy, the graphics view will not call contextMenuEvent(), which is what actually allows it to eventually send a new graphics context menu event to the item, if it supports it.

The solution is to install an event filter on the viewport (which is what actually receives the event in the first place), call the base implementation and return the result of the event.isAccepted(), which tells if the event was handled (an item probably showing a menu) or not.

If the event wasn't handled, then the filter will return False, which means that the default implementation would be called: the filter was installed on the viewport, False will make it ignore the default call and forward it to the event of the parent (the view itself), and since the custom policy tells to emit the custom context menu signal, your graphicsview_menu function will be actually called:

        # somewhere in the __init__
        self.ui.graphicsView.viewport().installEventFilter(self)

    # ...

    def eventFilter(self, obj, event):
        if (obj == self.ui.graphicsView.viewport() and 
            event.type() == event.Type.ContextMenu):
                self.ui.graphicsView.contextMenuEvent(event)
                return event.isAccepted()
        return super().eventFilter(obj, event)

Note: the above obviously assumes that the main class inherits from QWidget (or at least QObject).

musicamante
  • 41,230
  • 6
  • 33
  • 58
0

Thank you. your suggestion works perfectly. Its a bit hard to understand it fully, but I am getting there. Have posted my code sections below in pyqt6 in case anyone else has similar issue.

class ViewGraph(QDialog):
    ...
    def __init__(self):
        # Set the scene
        self.scene = GraphicsScene()  # QGraphicsScene class
        self.ui.graphicsView.setScene(self.scene)
        self.ui.graphicsView.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
        self.ui.graphicsView.customContextMenuRequested.connect(self.graphicsview_menu)
        self.ui.graphicsView.viewport().installEventFilter(self)
        # Load items into scene
        ...

def eventFilter(self, obj, event):
    """ This is required to forward context menu event to graphics view items """

    if obj == self.ui.graphicsView.viewport() and event.type() == event.Type.ContextMenu:
        self.ui.graphicsView.contextMenuEvent(event)
        return event.isAccepted()
    return super().eventFilter(obj, event)

def graphicsview_menu(self, position):
    item = self.ui.graphicsView.itemAt(position)
    if item is not None:
        # Forward contextmenu event to QTextGraphicsItem, etc
        self.scene.sendEvent(item)
        return
    # Menu for blank graphics view area
    menu = QtWidgets.QMenu()
    ...
Colin Curtain
  • 217
  • 1
  • 10
  • Please don't create new answers if they just add minimal changes to the provided ones, instead ask for modifications in the comments: I forgot about the Enum name issue of PyQt6 and the `ui` attribute, but those are the only actual differences with this answer. – musicamante Apr 26 '22 at 22:07
  • OK. Im still learning the correct way to do things here. – Colin Curtain Apr 26 '22 at 22:26