#python #pyside #qgraphicsitem #qsignalmapper
#питон #pyside #qgraphicsitem #qsignalmapper #python
Вопрос:
Приведенный ниже код показывает мою попытку заставить несколько подвижных VerticalLineSegment
объектов (производных от QGraphicsLineItem
и QObject
) сигнализировать одному (используя а QSignalMapper
) другому, когда они перемещаются. Я был бы признателен за помощь относительно того, почему VerticalLineSegment
слот updateX
не запускается.
(В дальнейшем цель будет состоять в том VerticalLineSegment
, чтобы использовать s в разных QGraphicsScene
s, но я подумал, что пока лучше сделать это простым.)
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_())
Ответ №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. @eyllansec Строка
self.signalMapper.mapped[str].connect(print)
в первой версии генерирует ошибкуSyntaxError: invalid syntax
. Есть ли где-нибудь опечатка?2. @Olumide Какую версию python и pyside вы используете?
3. @eyllansec Моя версия Python 2.7.15rc1, PySide 1.2.2, версия QtCore 4.8.7. Кстати, я придумал несколько похожее решение. Я опубликую это через мгновение. Пожалуйста, дайте мне знать, что вы думаете.
4. @Olumide Я изменил первый код, чтобы он был совместим с python2 и python3. Проблема в том, что в python2 print не является вызываемым, с другой стороны, в python3 это так. Попробуйте использовать мой новый код
5. Я отмечаю это как ответ, потому что, хотя мой ответ проще, поскольку он позволяет избежать использования QSignalMapper , в этом ответе рассматривается вопрос, который я задал, т.Е. Как использовать QSignalMapper для ответа на вопрос.
Ответ №2:
Вот решение, которое я придумал. Как и в первом решении @eyllanesc, в нем используется сигнализатор, который я называю вещателем, вместо QSignalMapper
которого он теперь устарел / устарел. Вот соответствующие изменения:
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_())
Комментарии:
1. В вашем объяснении содержится ошибка, в Qt5> = 5.10 оно устарело, но вы используете PySide, который основан на Qt4, поэтому для этого класса он не устарел.
2. Ах, верно. … Но его будущее доказательство 😉
3. До сих пор мне не приходилось анализировать время, потому что оно устарело, хотя для этих случаев я использую не QSignalMapper, а модель, которая позволяет обрабатывать простейшие вещи. 🙂
4. Раньше я думал, что signal mapper был «крутым», но теперь я думаю, что это ограничение из-за узкого диапазона типов данных, которые он может повторно излучать. (Кстати, я пытаюсь заставить ваши версии обновлять позиции всех сегментов линии, подобных моему ответу, чтобы я мог объективно сравнить их.)
5. Я добавлю решение с использованием моделей, но позже, возможно, это вам поможет. То, что, по вашему мнению, вы хотите сделать, это синхронизировать элементы нескольких QGraphicsScene. Но у меня вопрос, почему у вас есть несколько QGraphicsScene? QGraphicScene может принадлежать нескольким QGraphicsView