3

I want to emit a signal from a QGraphicsItem when it is doubled-clicked, in order to change a widget in the main window. The graphics-scene/-item does not provide an emit() method, but I was just wondering if there is an alternate way to do this. The code below has a function within a QGraphicsView class that will print to the terminal when an item is double-clicked. How can I make that into a slot/signal instead (if QGraphicsItem does not support signal/slots)?

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MyFrame(QGraphicsView):
    def __init__( self, parent = None ):
        super(MyFrame, self).__init__(parent)

        scene = QGraphicsScene()
        self.setScene(scene)
        self.setFixedSize(500, 500)

        pen = QPen(QColor(Qt.green))
        brush = QBrush(pen.color().darker(150))

        item = scene.addEllipse(0, 0, 45, 45, pen, brush)
        item.setPos(0,0)

    def mouseDoubleClickEvent(self, event):
        print("Circle Clicked!")
        # this double click event prints to terminal but how to setup
        # signal/slot to update the QWidget QLabel text instead?

class Example(QWidget):   
    def __init__(self):
        super(Example, self).__init__()
        self.initUI()

    def initUI(self):        
        hbox = QHBoxLayout(self)
        top = QLabel("Double Click Green Circle (Howto change this QWidget Label with signals?)")
        bottom = MyFrame()

        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(top)
        splitter.addWidget(bottom)

        hbox.addWidget(splitter)
        self.setLayout(hbox)
        self.setGeometry(0, 0, 500, 600)
        self.show()

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
cvw76
  • 99
  • 1
  • 9
  • Define a custom `itemDoubleClicked` signal on a subclass of `QGraphicsScene`, then do `self.scene().itemDoubleClicked.emit()` from the item. – ekhumoro Nov 02 '17 at 19:32
  • do you have an example? – cvw76 Nov 03 '17 at 13:59
  • `class GS(QGraphicsScene): itemDoubleClicked = QtCore.pyqtSignal()`. – ekhumoro Nov 03 '17 at 14:09
  • hi, yes, doesn't work. Errors with "does not have emit() attribute". Making a separate QGraphicsObject subclass and calling func there doesnt error but doesnt emit either. I've seen this issue of signal/slots on QGraphicsItem raised many times on searches but nobody can ever provide an example or evidence this can actually be done without totally rewriting the QGraphicsScene. That is what most folk do, so guessing its impossible otherwise to signal from QGraphis to QWidget. Shame, renders QGraphics classes pretty useless in real applications. Time to develop my own QGraphics I guess. – cvw76 Nov 06 '17 at 14:52
  • The code I posted works perfectly fine for me. Not sure what you're doing wrong. I would guess you didn't create an instance of the `QGraphicsScene` subclass and set it on the view. – ekhumoro Nov 06 '17 at 14:56
  • Is there someone who can answer this? (with an example, imagine you're talking to forest gump) I'd be soooo grateful, my last issue on my learning tutorial so far. Like I say, I've never, ever seen a working code example posted on the subject (and I've seen about 100+) of QGraphicsScene and signal/slots (that wasn't basically throwing away the QGraphicsScene framework and rewriting it. Double clicking a QGraphicsItem and emitting a signal outside QGraphics scene (to a QWidget, for example). – cvw76 Nov 06 '17 at 17:16
  • import sys from PySide import QtCore, QtGui class MyFrame(QtGui.QGraphicsView): def __init__( self, parent = None ): super(MyFrame, self).__init__(parent) scene = QtGui.QGraphicsScene() self.setScene(scene) self.setFixedSize(500, 500) pen = QtGui.QPen(QtGui.QColor(QtCore.Qt.green)) brush = QtGui.QBrush(pen.color().darker(150)) item = scene.addEllipse(0, 0, 45, 45, pen, brush) item.setPos(0,0) if ( __name__ == '__main__' ): app = QtGui.QApplication([]) f = MyFrame() f.show() app.exec_() – cvw76 Nov 06 '17 at 17:30
  • That is a circle in centre of QGraphicsScene. How to doubleclick that circle and emit event to a QWidget? – cvw76 Nov 06 '17 at 17:31
  • I have posted a simple working example. – ekhumoro Nov 06 '17 at 17:36

1 Answers1

8

Below is a simple example showing one way to emit signals from a graphics-item. This defines a custom signal on a subclass of QGraphicsScene and then uses the scene() method of graphics-items to emit it:

import sys
from PySide import QtCore, QtGui

class GraphicsScene(QtGui.QGraphicsScene):
    itemDoubleClicked = QtCore.Signal(object)

class GraphicsRectangle(QtGui.QGraphicsRectItem):
    def mouseDoubleClickEvent(self, event):
        self.scene().itemDoubleClicked.emit(self)

class Window(QtGui.QWidget):
    def __init__(self):
        super(Window, self).__init__()
        self.view = QtGui.QGraphicsView()
        self.scene = GraphicsScene(self)
        self.view.setScene(self.scene)
        layout = QtGui.QVBoxLayout(self)
        layout.addWidget(self.view)
        for i in range(1, 4):
            self.scene.addItem(GraphicsRectangle(50 * i, 50 * i, 20, 20))
        self.scene.itemDoubleClicked.connect(self.handleItemDoubleClicked)

    def handleItemDoubleClicked(self, item):
        print(item.boundingRect())

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)
    window = Window()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    sys.exit(app.exec_())

UPDATE:

Below is a an example based on the code in your question. The basic idea is the same: define a custom signal on an available QObject (the graphics-view in this case), and use that to emit the double-click notification.

import sys
from PySide.QtCore import *
from PySide.QtGui import *

class MyFrame(QGraphicsView):
    itemDoubleClicked = Signal(object)

    def __init__(self, parent=None):
        super(MyFrame, self).__init__(parent)
        scene = QGraphicsScene()
        self.setScene(scene)
        self.setFixedSize(500, 500)
        for i, color in enumerate('red blue green'.split()):
            pen = QPen(QColor(color))
            brush = QBrush(pen.color().darker(150))
            item = scene.addEllipse(i * 50, i * 50, 45, 45, pen, brush)
            item.setData(0, color.upper())

    def mouseDoubleClickEvent(self, event):
        item = self.itemAt(event.pos())
        if item is not None:
            self.itemDoubleClicked.emit(item)

class Example(QWidget):
    def __init__(self):
        super(Example, self).__init__()
        self.initUI()

    def initUI(self):
        hbox = QHBoxLayout(self)
        top = QLabel('Double Click a Circle')
        bottom = MyFrame()
        bottom.itemDoubleClicked.connect(
            lambda item, top=top:
                top.setText('Double Clicked: %s' % item.data(0)))
        splitter = QSplitter(Qt.Vertical)
        splitter.addWidget(top)
        splitter.addWidget(bottom)
        hbox.addWidget(splitter)
        self.setLayout(hbox)
        self.setGeometry(0, 0, 500, 600)
        self.show()

def main():
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Hi, thanks, answers one question but I updated the code above to give a better idea of where I am trying to go with communicating events between QGraphicsEvents and QWidgets. How can I connect signals between the QGraphicsScene here nested inside the QWindow to update the QLabel with the text that is current printed to the terminal? ie., so that clicking changes the QLabel to "Circle Clicked!"? – cvw76 Nov 07 '17 at 16:40
  • @cvw76. I have added a second example based on yours. (PS: I would be grateful if you now marked this answer as accepted - i.e. click on the tick symbol). – ekhumoro Nov 07 '17 at 18:08
  • Excellent! The lambda is pretty elegant, I was going way to complicated, this perfectly solves the issues I was trying to implement in my little app. Thanks so much!! I marked it as perfectly answered. – cvw76 Nov 07 '17 at 18:41