Search code examples
pythonpyqtpyqt5qgraphicsitemqt-signals

Connecting signal to slot between classes in PyQt


Aim is to connect a signal of the top class TicTacToe with the QMainWindow class.

It throws an error: TicTacToe cannot be converted to PyQt5.QtCore.QObject in this context

#!/usr/bin/env python


from PyQt5.QtCore import (QLineF, QPointF, QRectF, pyqtSignal)
from PyQt5.QtGui import (QIcon, QBrush, QColor, QPainter, QPixmap)
from PyQt5.QtWidgets import (QAction, QMainWindow, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem,
                             QGridLayout, QVBoxLayout, QHBoxLayout,
                             QLabel, QLineEdit, QPushButton)

class TicTacToe(QGraphicsItem):
    def __init__(self):
        super(TicTacToe, self).__init__()

    def paintEvent(self, painter, option, widget):
        painter.setPen(Qt.black)
        painter.drawLine(0,100,300,100)


    def boundingRect(self):
        return QRectF(0,0,300,300)

    def mousePressEvent(self, event):
        pos = event.pos()
        self.select(int(pos.x()/100), int(pos.y()/100))
        self.update()
        super(TicTacToe, self).mousePressEvent(event)

    messageSignal = pyqtSignal(int)


class MyGraphicsView(QGraphicsView):
    def __init__(self):
        super(MyGraphicsView, self).__init__()
        scene = QGraphicsScene(self)
        self.tic_tac_toe = TicTacToe()
        scene.addItem(self.tic_tac_toe)

        scene.addPixmap(QPixmap("exit.png"))

        self.setScene(scene)

    def keyPressEvent(self, event):
        key = event.key()
        if key == Qt.Key_R:
            self.tic_tac_toe.reset()
        super(MyGraphicsView, self).keyPressEvent(event)

class Example(QMainWindow):    
    def __init__(self):
        super(Example, self).__init__()

        self.y = MyGraphicsView()
        self.setCentralWidget(self.y)

        self.y.tic_tac_toe.messageSignal.connect (self.messageSlot)

        self.initUI()

    def messageSlot(self, val):
        self.statusBar().showMessage(val)


    def initUI(self):               
        self.toolbar = self.addToolBar('Tools')


        self.setGeometry(30, 30, 30, 20)
        self.setWindowTitle('Menubar')    
        self.show()        


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    mainWindow = Example()

    mainWindow.showFullScreen()
    sys.exit(app.exec_())

Solution

  • Only classes that inherit from QObject have the ability to create signals, for example QWidget, QMainWIndow, QGraphicsView inherit from QObject so they can have signals. But QGraphicsItem does not inherit from QObject because of efficiency issues so they do not have the ability to create signals. If you want an item that is a QObject you must use QGraphicsObject. In addition, the items have the paint() method, not paintEvent().

    class TicTacToe(QGraphicsObject):
        def paint(self, painter, option, widget):
            painter.setPen(Qt.black)
            painter.drawLine(0,100,300,100)
    
        def boundingRect(self):
            return QRectF(0,0,300,300)
    
        def mousePressEvent(self, event):
            pos = event.pos()
            # self.select(int(pos.x()/100), int(pos.y()/100))
            self.update()
            super(TicTacToe, self).mousePressEvent(event)
    
        messageSignal = pyqtSignal(int)
    

    If you still want to use QGraphicsItem, a possible work around is to create a class that is in charge of the communication and that inherits from QObject:

    class Helper(QObject):
        messageSignal = pyqtSignal(int)
    
    class TicTacToe(QGraphicsObject):
        def __init__(self, helper):
            super(TicTacToe, self).__init__()
            self.helper = helper
    
        def paint(self, painter, option, widget):
            painter.setPen(Qt.black)
            painter.drawLine(0,100,300,100)
    
        def boundingRect(self):
            return QRectF(0,0,300,300)
    
        def mousePressEvent(self, event):
            pos = event.pos()
            self.helper.emit(10)
            # self.select(int(pos.x()/100), int(pos.y()/100))
            self.update()
            super(TicTacToe, self).mousePressEvent(event)
    
    
    class MyGraphicsView(QGraphicsView):
        def __init__(self):
            super(MyGraphicsView, self).__init__()
            scene = QGraphicsScene(self)
            self.helper = Helper(self)
            self.tic_tac_toe = TicTacToe(self.helper)
            scene.addItem(self.tic_tac_toe)
            scene.addPixmap(QPixmap("exit.png"))
            self.setScene(scene)
    
        def keyPressEvent(self, event):
            key = event.key()
            if key == Qt.Key_R:
                self.tic_tac_toe.reset()
            super(MyGraphicsView, self).keyPressEvent(event)
    
    class Example(QMainWindow):    
        def __init__(self):
            super(Example, self).__init__()
    
            self.y = MyGraphicsView()
            self.setCentralWidget(self.y)
            self.helper.messageSignal.connect(self.messageSlot)
            self.initUI()
    
        def messageSlot(self, val):
            self.statusBar().showMessage(val)
    
    
        def initUI(self):               
            self.toolbar = self.addToolBar('Tools')
            self.setGeometry(30, 30, 30, 20)
            self.setWindowTitle('Menubar')    
            self.show()