使用QSignalMapper在PySide中的QGraphicsItems之间进行通信

时间:2019-04-10 13:47:22

标签: python pyside signals-slots qgraphicsitem qsignalmapper

下面的代码显示了我尝试获取多个可移动的VerticalLineSegment对象(源自QGraphicsLineItemQObject),以在它们移动时向另一个信号对象(使用QSignalMapper)发出信号。感谢您提供有关为何未触发VerticalLineSegment插槽updateX的帮助。

(前进的目标是将VerticalLineSegment放在不同的QGraphicsScene中,但我认为最好暂时保持简单。)

from PySide import QtGui, QtCore
import sys


class VerticalLineSegment( QtCore.QObject , QtGui.QGraphicsLineItem ):
    onXMove = QtCore.Signal()

    def __init__(self, x , y0 , y1 , parent=None):
        QtCore.QObject.__init__(self)
        QtGui.QGraphicsLineItem.__init__( self , x , y0 , x , y1 , parent)

        self.setFlag(QtGui.QGraphicsLineItem.ItemIsMovable)
        self.setFlag(QtGui.QGraphicsLineItem.ItemSendsGeometryChanges)
        self.setCursor(QtCore.Qt.SizeAllCursor)

    def itemChange( self , change , value ):
        if change is QtGui.QGraphicsItem.ItemPositionChange:
            self.onXMove.emit()
            value.setY(0)  # Restrict movements along horizontal direction
            return value
        return QtGui.QGraphicsLineItem.itemChange(self, change , value )

    def shape(self):
        path = super(VerticalLineSegment, self).shape()
        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(5)
        return stroker.createStroke(path)

    def boundingRect(self):
        return self.shape().boundingRect()

    # slot
    def updateX(self , object ):
        print "slot"        


class CustomScene(QtGui.QGraphicsScene):
    def __init__(self , parent=None):
        super(CustomScene, self).__init__(parent)
        self.signalMapper = QtCore.QSignalMapper()

    def addItem( self , item ):
        self.signalMapper.setMapping( item , item )
        item.onXMove.connect(self.signalMapper.map )
        self.signalMapper.mapped.connect(item.updateX)
        return QtGui.QGraphicsScene.addItem(self,item)


class Editor(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Editor, self).__init__(parent)

        scene = CustomScene()

        line0 = VerticalLineSegment( 10 , 210 , 300 )
        line1 = VerticalLineSegment( 10 , 110 , 200 )
        line2 = VerticalLineSegment( 10 ,  10 , 100 )

        scene.addItem( line0 )
        scene.addItem( line1 )
        scene.addItem( line2 )

        view = QtGui.QGraphicsView()
        view.setScene( scene )

        self.setGeometry( 250 , 250 , 600 , 600 )
        self.setCentralWidget(view)
        self.show()


if __name__=="__main__":
    app=QtGui.QApplication(sys.argv)
    myapp = Editor()
    sys.exit(app.exec_())

2 个答案:

答案 0 :(得分:1)

在PySide(以及PySide2,PyQt4和PyQt5)中,无法从QGraphicsItem和QObject继承(在特殊情况下仅允许双重继承)

因此,一种可能的解决方案是使用合成,即将QObject作为属性,并使用信号:

import sys
import uuid
from PySide import QtGui, QtCore


class Signaller(QtCore.QObject):
    onXMove = QtCore.Signal()


class VerticalLineSegment(QtGui.QGraphicsLineItem):
    def __init__(self, _id, x, y0, y1, parent=None):
        super(VerticalLineSegment, self).__init__(x, y0, x, y1, parent)
        self._id = _id
        self.signaller = Signaller()
        self.setFlag(QtGui.QGraphicsLineItem.ItemIsMovable)
        self.setFlag(QtGui.QGraphicsLineItem.ItemSendsGeometryChanges)
        self.setCursor(QtCore.Qt.SizeAllCursor)

    def itemChange(self, change, value):
        if change is QtGui.QGraphicsItem.ItemPositionChange:
            self.signaller.onXMove.emit()
            value.setY(0)  # Restrict movements along horizontal direction
            return value
        return QtGui.QGraphicsLineItem.itemChange(self, change, value)

    def shape(self):
        path = super(VerticalLineSegment, self).shape()
        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(5)
        return stroker.createStroke(path)

    def boundingRect(self):
        return self.shape().boundingRect()

    def updateX(self, _id):
        print("slot", _id)


class CustomScene(QtGui.QGraphicsScene):
    def __init__(self, parent=None):
        super(CustomScene, self).__init__(parent)
        self.signalMapper = QtCore.QSignalMapper(self)

    def addItem(self, item):
        if hasattr(item, "_id"):
            item.signaller.onXMove.connect(self.signalMapper.map)
            self.signalMapper.setMapping(item.signaller, item._id)
            self.signalMapper.mapped[str].connect(item.updateX)
        super(CustomScene, self).addItem(item)


class Editor(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Editor, self).__init__(parent)

        scene = CustomScene()

        line0 = VerticalLineSegment(str(uuid.uuid4()), 10.0, 210.0, 300.0)
        line1 = VerticalLineSegment(str(uuid.uuid4()), 10.0, 110.0, 200.0)
        line2 = VerticalLineSegment(str(uuid.uuid4()), 10.0, 10.0, 100.0)

        scene.addItem(line0)
        scene.addItem(line1)
        scene.addItem(line2)

        view = QtGui.QGraphicsView()
        view.setScene(scene)

        self.setGeometry(250, 250, 600, 600)
        self.setCentralWidget(view)
        self.show()

或使用QGraphicsObject:

import sys
from PySide import QtCore, QtGui

class VerticalLineSegment(QtGui.QGraphicsObject):
    onXMove = QtCore.Signal()

    def __init__(self, x, y0, y1, parent=None):
        super(VerticalLineSegment, self).__init__(parent)
        self._line = QtCore.QLineF(x, y0, x, y1)
        self.setFlag(QtGui.QGraphicsLineItem.ItemIsMovable)
        self.setFlag(QtGui.QGraphicsLineItem.ItemSendsGeometryChanges)
        self.setCursor(QtCore.Qt.SizeAllCursor)

    def paint(self, painter, option, widget=None):
        painter.drawLine(self._line)

    def shape(self):
        path = QtGui.QPainterPath()
        path.moveTo(self._line.p1())
        path.lineTo(self._line.p2())
        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(5)
        return stroker.createStroke(path)

    def boundingRect(self):
        return self.shape().boundingRect()

    def itemChange(self, change, value):
        if change is QtGui.QGraphicsItem.ItemPositionChange:
            self.onXMove.emit()
            value.setY(0)  # Restrict movements along horizontal direction
            return value
        return QtGui.QGraphicsLineItem.itemChange(self, change, value)

    def updateX(self , obj):
        print("slot", obj) 

class CustomScene(QtGui.QGraphicsScene):
    def __init__(self, parent=None):
        super(CustomScene, self).__init__(parent)
        self.signalMapper = QtCore.QSignalMapper(self)

    def addItem(self, item):
        if isinstance(item, QtCore.QObject):
            item.onXMove.connect(self.signalMapper.map)
            self.signalMapper.setMapping(item, item)
            self.signalMapper.mapped[QtCore.QObject].connect(item.updateX)
        super(CustomScene, self).addItem(item)


class Editor(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Editor, self).__init__(parent)

        scene = CustomScene()

        line0 = VerticalLineSegment(10.0, 210.0, 300.0)
        line1 = VerticalLineSegment(10.0, 110.0, 200.0)
        line2 = VerticalLineSegment(10.0, 10.0, 100.0)

        scene.addItem(line0)
        scene.addItem(line1)
        scene.addItem(line2)

        view = QtGui.QGraphicsView()
        view.setScene(scene)

        self.setGeometry(250, 250, 600, 600)
        self.setCentralWidget(view)
        self.show()

答案 1 :(得分:0)

这是我想出的解决方案。就像@eyllanesc的第一个解决方案一样,它使用一个我称为广播器的信号器,而不是现在为obsolete / deprecatedQSignalMapper。以下是相关更改:

class VerticalLineSegment( QtCore.QObject , QtGui.QGraphicsLineItem ):
    onXMove = QtCore.Signal( int , int )

    def __init__(self, x , y0 , y1 , parent=None):
        ...
        self.index = -1
        ...

    def updateX( self , id , x ):
        if id is not self.index:
            # Disconnect and reconnect to avoid a signal cycle
            self.onXMove.disconnect()
            self.setX( x )
            self.onXMove.connect( self.sender().onXMove )


# Alternative to signal mapper
class Broadcaster( QtCore.QObject ):
    onXMove = QtCore.Signal( int , int )    


class CustomScene(QtGui.QGraphicsScene):
    def __init__(self , parent=None):
        super(CustomScene, self).__init__(parent)
        self.broadcaster = Broadcaster()
        self.count = 0

    def addItem( self , item ):
        item.index = self.count
        self.count = self.count + 1
        item.onXMove.connect( self.broadcaster.onXMove )
        self.broadcaster.onXMove.connect( item.updateX )
        return QtGui.QGraphicsScene.addItem(self,item)              

这是完整的程序

from PySide import QtGui, QtCore
import sys

class VerticalLineSegment( QtCore.QObject , QtGui.QGraphicsLineItem ):
    onXMove = QtCore.Signal( int , int )

    def __init__(self, x , y0 , y1 , parent=None):
        QtCore.QObject.__init__(self)
        QtGui.QGraphicsLineItem.__init__( self , x , y0 , x , y1 , parent)
        self.index = -1

        self.setFlag(QtGui.QGraphicsLineItem.ItemIsMovable)
        self.setFlag(QtGui.QGraphicsLineItem.ItemSendsGeometryChanges)
        self.setCursor(QtCore.Qt.SizeAllCursor)

    def itemChange( self , change , value ):
        if change is QtGui.QGraphicsItem.ItemPositionChange:
            self.onXMove.emit( self.index , value.x() )
            value.setY(0)  # Restrict movements along horizontal direction
            return value
        return QtGui.QGraphicsLineItem.itemChange(self, change , value )

    def shape(self):
        path = super(VerticalLineSegment, self).shape()
        stroker = QtGui.QPainterPathStroker()
        stroker.setWidth(5)
        return stroker.createStroke(path)

    def boundingRect(self):
        return self.shape().boundingRect()

    def updateX( self , id , x ):
        if id is not self.index:
            self.onXMove.disconnect()
            self.setX( x )
            self.onXMove.connect( self.sender().onXMove )


class Broadcaster( QtCore.QObject ):
    onXMove = QtCore.Signal( int , int )


class CustomScene(QtGui.QGraphicsScene):
    def __init__(self , parent=None):
        super(CustomScene, self).__init__(parent)
        self.broadcaster = Broadcaster()
        self.count = 0

    def addItem( self , item ):
        item.index = self.count
        self.count = self.count + 1
        item.onXMove.connect( self.broadcaster.onXMove )
        self.broadcaster.onXMove.connect( item.updateX )
        return QtGui.QGraphicsScene.addItem(self,item)


class Editor(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(Editor, self).__init__(parent)

        scene = CustomScene()

        line0 = VerticalLineSegment( 10 , 210 , 300 )
        line1 = VerticalLineSegment( 10 , 110 , 200 )
        line2 = VerticalLineSegment( 10 ,  10 , 100 )

        scene.addItem( line0 )
        scene.addItem( line1 )
        scene.addItem( line2 )

        view = QtGui.QGraphicsView()
        view.setScene( scene )

        self.setGeometry( 250 , 250 , 600 , 600 )
        self.setCentralWidget(view)
        self.show()

if __name__=="__main__":
    app=QtGui.QApplication(sys.argv)
    myapp = Editor()
    sys.exit(app.exec_())