The problem is the following - online line is created. Left mouse button - add point, right button - finish line creation. Adding points passes without errors, but when you press the right button - the application window simply closes, without any errors and warnings.

I give an example, to the maximum deleted unnecessary.

Debugging comes to line 93 (node.change_position_signal.connect (self.update_point_positions)) and everything silently closes.

Usually I manage to solve the problem myself, but here I am already in despair, I have already spent three days on this problem.

from PyQt5.QtWidgets import (QGraphicsPathItem, QGraphicsObject, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem) from PyQt5.QtCore import Qt, pyqtSignal, QMarginsF, QRectF, QPointF, pyqtSlot from PyQt5.QtGui import (QPainter, QPainterPath, QPainterPathStroker, QColor, QPen, QBrush) class MVGraphicsLinkTest(QGraphicsPathItem, QGraphicsObject): def __init__(self, scene): super().__init__() scene.addItem(self) self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.setAcceptHoverEvents(True) # lines width self._width = 10 self._color = QColor(0, 100, 0, 255) self._brush = QBrush(self._color) self.setBrush(self._brush) self.setPen(QPen(Qt.NoPen)) # stroker self._path_stroker = QPainterPathStroker() self._path_stroker.setWidth(self._width) self._path_stroker.setCapStyle(Qt.RoundCap) self._path_stroker.setJoinStyle(Qt.RoundJoin) self._path_stroker.setDashPattern(Qt.SolidLine) # init first point self.nodes = [MVGraphicsLinkNode(QPointF(10,10))] self.points = [QPointF(10,10)] # init dummy point self._dummy_mode = True self._dummy_node = MVGraphicsLinkNode(QPointF(10,10), parent=self) self._dummy_point = QPointF(10,10) self.update_path() self.grabMouse() def color(self): return self._color def update_path(self): self.prepareGeometryChange() for i, point in enumerate(self.points): if i==0: self._path = QPainterPath(point) else: self._path.lineTo(point) if self._dummy_mode: self._path.lineTo(self._dummy_point) self.setPath(self._path_stroker.createStroke(self._path)) def mousePressEvent(self, e): if e.button() == Qt.LeftButton: # builds line if self._dummy_mode: self.points.append(e.pos()) self.nodes.append(self._dummy_node) self._dummy_node = MVGraphicsLinkNode(e.pos(), parent=self) if e.button() == Qt.RightButton: # exit dummy mode and saving points if self._dummy_mode: self._dummy_mode = False self._dummy_point = None self.scene().removeItem(self._dummy_node) self._dummy_node = None self.connect_nodes() self.update_path() self.ungrabMouse() if len(self.points)<2: self.delete_self() super().mousePressEvent(e) e.accept() def mouseMoveEvent(self, e): if self._dummy_mode: self._dummy_point = e.pos() self._dummy_node.setPos(self._dummy_point) self.update_path() super().mouseMoveEvent(e) def connect_nodes(self): # connecting node signals for i, node in enumerate(self.nodes): node.set_index(i) ############################################################### # CRUSH ON THIS LINE ########################################## ############################################################### node.change_position_signal.connect(self.update_point_positions) node.delete_signal.connect(self.delete_node) def disconnect_nodes(self): # disconnecting node signals for node in self.nodes: node.change_position_signal.disconnect() node.delete_signal.disconnect() def delete_node(self, index): if len(self.points)>2: self.disconnect_nodes() self.points.pop(index) del_node = self.nodes.pop(index) self.scene().removeItem(del_node) self.connect_nodes() self.update_path() print('scene items: {0}'.format(len(self.scene().items()))) @pyqtSlot() def update_point_positions(self): for i, node in enumerate(self.nodes): self.points[i] = node.scenePos() self.update_path() class MVGraphicsLinkNode(QGraphicsObject): change_position_signal = pyqtSignal() delete_signal = pyqtSignal(int) def __init__(self, pos, movable=True, parent=None): super().__init__(parent=parent) self.setPos(pos) self._movable = movable self._index = -1 self.setFlag(QGraphicsItem.ItemIsMovable, self._movable) self.setFlag(QGraphicsItem.ItemIsSelectable, self._movable) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setAcceptHoverEvents(True) self.setEnabled(self._movable) self._hover = False self._size = 12 self._color = QColor(200, 200, 50, 200) if parent: self._color = parent.color() self._color_sel = QColor(self._color) self._color_sel.setAlpha(100) self._brush1 = QBrush(self._color_sel) self._brush2 = QBrush(self._color) self._pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine) def set_index(self, index): self._index = index self.setToolTip('index = {0}'.format(self._index)) def itemChange(self, change, value): if change == QGraphicsItem.ItemScenePositionHasChanged and self.scene(): self.change_position_signal.emit() return super().itemChange(change, value) def size(self): if self._hover: return 2*self._size else: return self._size def boundingRect(self): return QRectF(-self._size, -self._size, 2*self._size, 2*self._size) def shape(self): path = QPainterPath() path.addEllipse(QPointF(), self.size()/2, self.size()/2) return path def paint(self, painter, option, widget=None): painter.setBrush(self._brush1) painter.setPen(self._pen) if self._hover or self.isSelected(): painter.drawEllipse(QPointF(), self._size, self._size) painter.setBrush(self._brush2) painter.drawEllipse(QPointF(), self._size/2, self._size/2) def hoverEnterEvent(self, e): self._hover = self._movable super().hoverLeaveEvent(e) def hoverLeaveEvent(self, e): self._hover = False super().hoverLeaveEvent(e) def mouseDoubleClickEvent(self, e): print('delete sender: {0}'.format(self._index)) self.delete_signal.emit(self._index) super().mouseDoubleClickEvent(e) if __name__ == '__main__': import sys app = QApplication(sys.argv) view = QGraphicsView() view.setDragMode(QGraphicsView.RubberBandDrag) scene = QGraphicsScene() m = QMarginsF(30, 30, 30, 30) scene_rect = QRectF(0, 0, 600, 600) scene.setSceneRect(scene_rect.marginsAdded(m)) view.setScene(scene) view.setRenderHint(QPainter.Antialiasing) view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) MVGraphicsLinkTest(scene) view.show() sys.exit(app.exec_()) 

  • Thanks for the link, now read. I’m confused about closing without messages. Concerning the signals - node is the same object of the MVGraphicsLinkNode class, and it sends the def itemChange (self, change, value): if change == QGraphicsItem.ItemScenePositionHasChanged and self.scene (): self.change_position_signal.emit () return super () .itemChange (change, value) - hermann.minkowski
  • I do not quite understand you. Signals are declared in the MVGraphicsLinkNode class - change_position_signal = pyqtSignal () and delete_signal = pyqtSignal (int). In the same class, these signals are sent. In the MVGraphicsLinkTest class, MVGraphicsLinkNode objects are created, and the signals of this class are bound to methods. I do not understand what the error is ... slow down? - hermann.minkowski
  • I have a working option - there MVGraphicsLinkTest is not inherited from QGraphicsObject, but only from QGraphicsPathItem. And methods are not decorated with @pyqtSlot (). In this case, everything works without errors. - hermann.minkowski

1 answer 1

Try:

 from PyQt5.QtWidgets import (QGraphicsPathItem, QGraphicsObject, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem) from PyQt5.QtCore import Qt, pyqtSignal, QMarginsF, QRectF, QPointF, pyqtSlot from PyQt5.QtGui import (QPainter, QPainterPath, QPainterPathStroker, QColor, QPen, QBrush) class MVGraphicsLinkTest(QGraphicsPathItem, QGraphicsObject): def __init__(self, scene): super().__init__() scene.addItem(self) self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.setAcceptHoverEvents(True) # lines width self._width = 10 self._color = QColor(0, 100, 0, 255) self._brush = QBrush(self._color) self.setBrush(self._brush) self.setPen(QPen(Qt.NoPen)) # stroker self._path_stroker = QPainterPathStroker() self._path_stroker.setWidth(self._width) self._path_stroker.setCapStyle(Qt.RoundCap) self._path_stroker.setJoinStyle(Qt.RoundJoin) self._path_stroker.setDashPattern(Qt.SolidLine) # init first point self.nodes = [MVGraphicsLinkNode(QPointF(10,10))] self.points = [QPointF(10,10)] # init dummy point self._dummy_mode = True self._dummy_node = MVGraphicsLinkNode(QPointF(10,10), parent=self) self._dummy_point = QPointF(10,10) self.update_path() self.grabMouse() def color(self): return self._color def update_path(self): #print("...def update_path(self):") self.prepareGeometryChange() for i, point in enumerate(self.points): if i==0: self._path = QPainterPath(point) else: self._path.lineTo(point) if self._dummy_mode: self._path.lineTo(self._dummy_point) self.setPath(self._path_stroker.createStroke(self._path)) def mousePressEvent(self, e): if e.button() == Qt.LeftButton: # builds line if self._dummy_mode: self.points.append(e.pos()) self.nodes.append(self._dummy_node) self._dummy_node = MVGraphicsLinkNode(e.pos(), parent=self) if e.button() == Qt.RightButton: # exit dummy mode and saving points if self._dummy_mode: self._dummy_mode = False self._dummy_point = None self.scene().removeItem(self._dummy_node) self._dummy_node = None self.connect_nodes() self.update_path() self.ungrabMouse() if len(self.points)<2: self.delete_self() super().mousePressEvent(e) e.accept() def mouseMoveEvent(self, e): if self._dummy_mode: self._dummy_point = e.pos() self._dummy_node.setPos(self._dummy_point) self.update_path() super().mouseMoveEvent(e) def connect_nodes(self): # connecting node signals for i, node in enumerate(self.nodes): node.set_index(i) ############################################################### # CRUSH ON THIS LINE ########################################## ############################################################### # node.change_position_signal.connect(self.update_point_positions) # node.delete_signal.connect(self.delete_node) node.change_position_signal.connect(lambda : self.update_point_positions()) # <---- node.delete_signal.connect(lambda i=i: self.delete_node(i)) # <---- def disconnect_nodes(self): # disconnecting node signals for node in self.nodes: node.change_position_signal.disconnect() node.delete_signal.disconnect() def delete_node(self, index): #print("------def delete_node(self, index):") if len(self.points)>2: self.disconnect_nodes() self.points.pop(index) del_node = self.nodes.pop(index) self.scene().removeItem(del_node) self.connect_nodes() self.update_path() print('scene items: {0}'.format(len(self.scene().items()))) @pyqtSlot() def update_point_positions(self): for i, node in enumerate(self.nodes): self.points[i] = node.scenePos() #print("---def update_point_positions(self): ???", i, self.points[i]) self.update_path() class MVGraphicsLinkNode(QGraphicsObject): change_position_signal = pyqtSignal() delete_signal = pyqtSignal(int) def __init__(self, pos, movable=True, parent=None): super().__init__(parent=parent) self.setPos(pos) self._movable = movable self._index = -1 self.setFlag(QGraphicsItem.ItemIsMovable, self._movable) self.setFlag(QGraphicsItem.ItemIsSelectable, self._movable) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setAcceptHoverEvents(True) self.setEnabled(self._movable) self._hover = False self._size = 12 self._color = QColor(200, 200, 50, 200) if parent: self._color = parent.color() self._color_sel = QColor(self._color) self._color_sel.setAlpha(100) self._brush1 = QBrush(self._color_sel) self._brush2 = QBrush(self._color) self._pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine) def set_index(self, index): self._index = index self.setToolTip('index = {0}'.format(self._index)) def itemChange(self, change, value): if change == QGraphicsItem.ItemScenePositionHasChanged and self.scene(): self.change_position_signal.emit() return super().itemChange(change, value) def size(self): if self._hover: return 2*self._size else: return self._size def boundingRect(self): return QRectF(-self._size, -self._size, 2*self._size, 2*self._size) def shape(self): path = QPainterPath() path.addEllipse(QPointF(), self.size()/2, self.size()/2) return path def paint(self, painter, option, widget=None): painter.setBrush(self._brush1) painter.setPen(self._pen) if self._hover or self.isSelected(): painter.drawEllipse(QPointF(), self._size, self._size) painter.setBrush(self._brush2) painter.drawEllipse(QPointF(), self._size/2, self._size/2) def hoverEnterEvent(self, e): self._hover = self._movable super().hoverLeaveEvent(e) def hoverLeaveEvent(self, e): self._hover = False super().hoverLeaveEvent(e) def mouseDoubleClickEvent(self, e): print('delete sender: {0}'.format(self._index)) self.delete_signal.emit(self._index) super().mouseDoubleClickEvent(e) if __name__ == '__main__': import sys app = QApplication(sys.argv) view = QGraphicsView() view.setDragMode(QGraphicsView.RubberBandDrag) scene = QGraphicsScene() m = QMarginsF(30, 30, 30, 30) scene_rect = QRectF(0, 0, 600, 600) scene.setSceneRect(scene_rect.marginsAdded(m)) view.setScene(scene) view.setRenderHint(QPainter.Antialiasing) view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) MVGraphicsLinkTest(scene) view.show() sys.exit(app.exec_()) 

enter image description here


Update

 from PyQt5.QtWidgets import (QGraphicsPathItem, QGraphicsObject, QApplication, QGraphicsView, QGraphicsScene, QGraphicsItem) from PyQt5.QtCore import Qt, pyqtSignal, QMarginsF, QRectF, QPointF, pyqtSlot from PyQt5.QtGui import (QPainter, QPainterPath, QPainterPathStroker, QColor, QPen, QBrush) #class MVGraphicsLinkTest(QGraphicsPathItem, QGraphicsObject): # <=========== class MVGraphicsLinkTest(QGraphicsPathItem): # +++ def __init__(self, scene): super().__init__() scene.addItem(self) self.setFlag(QGraphicsItem.ItemIsFocusable, True) self.setAcceptHoverEvents(True) # lines width self._width = 10 self._color = QColor(0, 100, 0, 255) self._brush = QBrush(self._color) self.setBrush(self._brush) self.setPen(QPen(Qt.NoPen)) # stroker self._path_stroker = QPainterPathStroker() self._path_stroker.setWidth(self._width) self._path_stroker.setCapStyle(Qt.RoundCap) self._path_stroker.setJoinStyle(Qt.RoundJoin) self._path_stroker.setDashPattern(Qt.SolidLine) # init first point self.nodes = [MVGraphicsLinkNode(QPointF(10,10))] self.points = [QPointF(10,10)] # init dummy point self._dummy_mode = True self._dummy_node = MVGraphicsLinkNode(QPointF(10,10), parent=self) self._dummy_point = QPointF(10,10) self.update_path() self.grabMouse() def color(self): return self._color def update_path(self): #print("...def update_path(self):") self.prepareGeometryChange() for i, point in enumerate(self.points): if i==0: self._path = QPainterPath(point) else: self._path.lineTo(point) if self._dummy_mode: self._path.lineTo(self._dummy_point) self.setPath(self._path_stroker.createStroke(self._path)) def mousePressEvent(self, e): if e.button() == Qt.LeftButton: # builds line if self._dummy_mode: self.points.append(e.pos()) self.nodes.append(self._dummy_node) self._dummy_node = MVGraphicsLinkNode(e.pos(), parent=self) if e.button() == Qt.RightButton: # exit dummy mode and saving points if self._dummy_mode: self._dummy_mode = False self._dummy_point = None self.scene().removeItem(self._dummy_node) self._dummy_node = None self.connect_nodes() self.update_path() self.ungrabMouse() if len(self.points)<2: self.delete_self() super().mousePressEvent(e) e.accept() def mouseMoveEvent(self, e): if self._dummy_mode: self._dummy_point = e.pos() self._dummy_node.setPos(self._dummy_point) self.update_path() super().mouseMoveEvent(e) def connect_nodes(self): # connecting node signals for i, node in enumerate(self.nodes): node.set_index(i) ############################################################### # CRUSH ON THIS LINE ########################################## ############################################################### node.change_position_signal.connect(self.update_point_positions) # <=========== node.delete_signal.connect(self.delete_node) # <=========== def disconnect_nodes(self): # disconnecting node signals for node in self.nodes: node.change_position_signal.disconnect() node.delete_signal.disconnect() def delete_node(self, index): #print("------def delete_node(self, index):") if len(self.points)>2: self.disconnect_nodes() self.points.pop(index) del_node = self.nodes.pop(index) self.scene().removeItem(del_node) self.connect_nodes() self.update_path() print('scene items: {0}'.format(len(self.scene().items()))) # @pyqtSlot() # --- <=========== def update_point_positions(self): for i, node in enumerate(self.nodes): self.points[i] = node.scenePos() #print("---def update_point_positions(self): ???", i, self.points[i]) self.update_path() class MVGraphicsLinkNode(QGraphicsObject): change_position_signal = pyqtSignal() delete_signal = pyqtSignal(int) def __init__(self, pos, movable=True, parent=None): super().__init__(parent=parent) self.setPos(pos) self._movable = movable self._index = -1 self.setFlag(QGraphicsItem.ItemIsMovable, self._movable) self.setFlag(QGraphicsItem.ItemIsSelectable, self._movable) self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges, True) self.setAcceptHoverEvents(True) self.setEnabled(self._movable) self._hover = False self._size = 12 self._color = QColor(200, 200, 50, 200) if parent: self._color = parent.color() self._color_sel = QColor(self._color) self._color_sel.setAlpha(100) self._brush1 = QBrush(self._color_sel) self._brush2 = QBrush(self._color) self._pen = QPen(QColor(0, 0, 0), 1.0, Qt.SolidLine) def set_index(self, index): self._index = index self.setToolTip('index = {0}'.format(self._index)) def itemChange(self, change, value): if change == QGraphicsItem.ItemScenePositionHasChanged and self.scene(): self.change_position_signal.emit() return super().itemChange(change, value) def size(self): if self._hover: return 2*self._size else: return self._size def boundingRect(self): return QRectF(-self._size, -self._size, 2*self._size, 2*self._size) def shape(self): path = QPainterPath() path.addEllipse(QPointF(), self.size()/2, self.size()/2) return path def paint(self, painter, option, widget=None): painter.setBrush(self._brush1) painter.setPen(self._pen) if self._hover or self.isSelected(): painter.drawEllipse(QPointF(), self._size, self._size) painter.setBrush(self._brush2) painter.drawEllipse(QPointF(), self._size/2, self._size/2) def hoverEnterEvent(self, e): self._hover = self._movable super().hoverLeaveEvent(e) def hoverLeaveEvent(self, e): self._hover = False super().hoverLeaveEvent(e) def mouseDoubleClickEvent(self, e): print('delete sender: {0}'.format(self._index)) self.delete_signal.emit(self._index) super().mouseDoubleClickEvent(e) if __name__ == '__main__': import sys app = QApplication(sys.argv) view = QGraphicsView() view.setDragMode(QGraphicsView.RubberBandDrag) scene = QGraphicsScene() m = QMarginsF(30, 30, 30, 30) scene_rect = QRectF(0, 0, 600, 600) scene.setSceneRect(scene_rect.marginsAdded(m)) view.setScene(scene) view.setRenderHint(QPainter.Antialiasing) view.fitInView(scene.sceneRect(), Qt.KeepAspectRatio) MVGraphicsLinkTest(scene) view.show() sys.exit(app.exec_()) 

enter image description here

  • Yes, your option has earned! Thank you very much. And is using a similar design with lambda a standard approach? Or specifically in this case should it be used? And why? In the end, I decided to abandon multiple inheritance, and inherited MVGraphicsLinkTest only from QGraphicsObject. In this case, I had to implement all the QGraphicsPath functionality (implement paint (), boundingRect (), shape (), etc.), which was initially too lazy to do. In this case, there were no problems ... But for the future I would like to understand, is your option the right way out, or a crutch? - hermann.minkowski
  • Should I avoid such situations? - hermann.minkowski
  • @ hermann.minkowski Look at the updated version - S. Nick
  • Yes, I considered this option (wrote in the commentary to the first answer). But I need QGraphicsObject features, because in addition to these classes, there are a dozen others, and many of them should be able to communicate via signals. I also found another reason for the fall - the removal of objects from the scene in the signals or when the setAcceptHoverEvents (True) flag is on. In this case, you should use deleteLater () instead of removeItem (). The QGraphicsObject documentation describes everything. So this problem can be closed. Thanks for the help - hermann.minkowski