Search code examples
pythonpyqtpysidesignals-slotsqgraphicsitem

PySide/PyQT5: How to emit signals from a QGraphicsItem?


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()

Solution

  • 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()