2

What is the best method/practice for emitting a signal upon entering either a QGraphicsWidget or a QGraphicsItem ?

In my MWE I would like to trigger a call to MainWindow.update, from Square.hoverEnterEvent, whenever the user mouse(s) over an item in a QGraphicsScene. The trouble is that QGraphicsItem/Widget is not really designed to emit signals. Instead these classes are setup to handle events passed down to them from QGraphicsScene. QGraphicsScene handles the case that the user has selected an item but does not appear to handle mouse entry events, At least there is no mechanism for entryEvent to percolate up to the parent widget/window.

import sys
from PyQt5.QtWidgets import QWidget, QApplication, qApp, QMainWindow, QGraphicsScene, QGraphicsView, QStatusBar, QGraphicsWidget, QStyle
from PyQt5.QtCore    import Qt, QSizeF

class Square(QGraphicsWidget) :
 """
 doc string
 """
 def __init__(self,*args, name = None, **kvps) :
  super().__init__(*args, **kvps)
  self.radius = 5
  self.name = name
  self.setAcceptHoverEvents(True)

 def sizeHint(self, hint, size):
  size = super().sizeHint(hint, size)
  print(size)
  return QSizeF(50,50)

 def paint(self, painter, options, widget):
  self.initStyleOption(options)
  ink = options.palette.highLight() if options.state == QStyle.State_Selected else options.palette.button()
  painter.setBrush(ink) # ink
  painter.drawRoundedRect(self.rect(), self.radius, self.radius)

 def hoverEnterEvent(self, event) :
  print("Enter Event")
  super().hoverEnterEvent(event)

class MainWindow(QMainWindow):

 def __init__(self, *args, **kvps) : 
  super().__init__(*args, **kvps)
  # Status bar
  self.stat = QStatusBar(self)
  self.setStatusBar(self.stat)
  self.stat.showMessage("Started")
  # Widget(s)
  self.data = QGraphicsScene(self)
  self.view = QGraphicsView(self.data, self)
  item = self.data.addItem(Square())
  self.view.ensureVisible(self.data.sceneRect())
  self.setCentralWidget(self.view)
  # Visibility
  self.showMaximized()

 def update(self, widget) : 
  self.stat.showMessage(str(widget.name))

if __name__ == "__main__" :
 # Application
 app = QApplication(sys.argv)
 # Scene Tests
 main = MainWindow()
 main.show()
 # Loop
 sys.exit(app.exec_())

The docs state that QGraphicsItem is not designed to emit signals, instead it is meant to respond to the events sent to it by QGraphicsScene. In contrast it seems that QGraphicsWidget is designed to do so but I'm not entirely sure where the entry point ought to be. Personally I feel QGraphicsScene should really be emitting these signals, from what I understand of the design, but am not sure where the entry point ought to be in this case either.

Currently I see the following possible solutions, with #3 being the preferred method. I was wondering if anyone else had a better strategy :

  1. Create a QGraphicsScene subclass, let's call it Scene, to each QGraphicsItem/QGraphicsWidget and call a custom trigger/signal upon the Scene from each widget. Here I would have to subclass any item I intend on including within the scene.
  2. Set Mainwindow up as the event filter for each item in the scene or upon the scene itself and calling MainWindow.update.
  3. Set Mainwindow.data to be a subclass of QGraphicsScene, let's call it Scene, and let it filter it's own events emitting a hoverEntry signal. hoverEntry is then connected to MainWindow.update as necessary.
Carel
  • 3,289
  • 2
  • 27
  • 44

1 Answers1

2

As Murphy's Law would have it Ekhumoro already provides an answer.

It seems one should subclass QGraphicsScene and add the necessary signal. this is then triggered from the QGraphicsItem/Widget. This requires that all items within a scene be sub-classed to ensure that they call the corresponding emit function but it seems must do this do this anyhow when working with the graphics scene stuff.

I'll not mark this as answered for a bit in case some one has a better suggestion.

import sys
from PyQt5.QtWidgets import QWidget, QApplication, qApp, QMainWindow, QGraphicsScene, QGraphicsView, QStatusBar, QGraphicsWidget, QStyle, QGraphicsItem
from PyQt5.QtCore    import Qt, QSizeF, pyqtSignal

class Square(QGraphicsWidget) :
 """
 doc string
 """
 def __init__(self,*args, name = None, **kvps) :
  super().__init__(*args, **kvps)
  self.radius = 5
  self.name = name
  self.setAcceptHoverEvents(True)
  self.setFlag(self.ItemIsSelectable)
  self.setFlag(self.ItemIsFocusable)

 def sizeHint(self, hint, size):
  size = super().sizeHint(hint, size)
  print(size)
  return QSizeF(50,50)

 def paint(self, painter, options, widget):
  self.initStyleOption(options)
  ink = options.palette.highLight() if options.state == QStyle.State_Selected else options.palette.button()
  painter.setBrush(ink) # ink
  painter.drawRoundedRect(self.rect(), self.radius, self.radius)

 def hoverEnterEvent(self, event) :
  super().hoverEnterEvent(event)
  self.scene().entered.emit(self)
  self.update()

class GraphicsScene(QGraphicsScene) :
 entered = pyqtSignal([QGraphicsItem],[QGraphicsWidget])


class MainWindow(QMainWindow):

 def __init__(self, *args, **kvps) : 
  super().__init__(*args, **kvps)
  # Status bar
  self.stat = QStatusBar(self)
  self.setStatusBar(self.stat)
  self.stat.showMessage("Started")
  # Widget(s)
  self.data = GraphicsScene(self)
  self.data.entered.connect(self.itemInfo)
  self.data.focusItemChanged.connect(self.update)
  self.view = QGraphicsView(self.data, self)
  item = Square(name = "A")
  item.setPos( 50,0)
  self.data.addItem(item)
  item = Square(name = "B")
  item.setPos(-50,0)
  self.data.addItem(item)
  self.view.ensureVisible(self.data.sceneRect())
  self.setCentralWidget(self.view)
  # Visibility
  self.showMaximized()

 def itemInfo(self, item):
  print("Here it is -> ", item)

if __name__ == "__main__" :
 # Application
 app = QApplication(sys.argv)
 # Scene Tests
 main = MainWindow()
 main.show()
 # Loop
 sys.exit(app.exec_())

The magic lines of interest are then the QGrahicsScene subclass.

class GraphicsScene(QGraphicsScene) :
 entered = pyqtSignal([QGraphicsItem],[QGraphicsWidget])

The QGraphicsWidget.hoverEnterEvent triggers the entered signal. (This is where I got stuck)

 def hoverEnterEvent(self, event) :
  ...
  self.scene().entered.emit(self)
  ...

And the switcheroo from self.data = QGraphicsScene(...) to self.data = GraphicsScene in the MainWindow's init function.

Carel
  • 3,289
  • 2
  • 27
  • 44