我有一个QGraphicsScene包含一些简单对象(在此简化示例中为圆圈)的对象,我想在选择时将其更改为其他对象(此处为正方形)。更具体地说,我希望拥有不自己绘制的父对象,它们由其子对象绘制,并且在各种情况下,但特别是在选择父对象时,我希望子对象集改变。这是我正在开发的整个应用程序的一个很好的概念框架。
所以我已经实现了这个PySide,我认为它工作正常:当你点击它们时,圆圈会很好地变成正方形。
直到我RubberBandDrag在视图中使用选择。当橡皮筋选择到达父对象并且选择更改时,这会导致即时段错误。据推测,这是因为 QT 中的橡皮筋选择以某种方式保持指向子项的指针,该子项在橡皮筋选择操作完成之前消失。
下面的简化代码 - 通过首先单击对象(它变化很好)然后拖动对象来测试它 - 段错误:
from PySide import QtCore,QtGui
class SceneObject(QtGui.QGraphicsItem):
def __init__(self, scene):
QtGui.QGraphicsItem.__init__(self, scene = scene)
self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True)
self.setFlag(QtGui.QGraphicsItem.ItemHasNoContents, True)
self.updateContents()
def updateContents(self):
self.prepareGeometryChange()
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
def itemChange(self, change, value):
if self.scene() != None:
if change == QtGui.QGraphicsItem.ItemSelectedHasChanged:
self.updateContents()
return
return super(SceneObject,self).itemChange(change, value)
def boundingRect(self):
return self.childrenBoundingRect()
class Visualiser(QtGui.QMainWindow):
def __init__(self):
super(Visualiser,self).__init__()
self.viewer = QtGui.QGraphicsView(self)
self.viewer.setDragMode(QtGui.QGraphicsView.RubberBandDrag)
self.setCentralWidget(self.viewer)
self.viewer.setScene(QtGui.QGraphicsScene())
parent_item = SceneObject(self.viewer.scene())
parent_item.setPos(50,50)
app = QtGui.QApplication([])
mainwindow = Visualiser()
mainwindow.show()
app.exec_()
Run Code Online (Sandbox Code Playgroud)
所以问题:
我是不是犯了一个可以直接修复的错误?
或者在处理ItemSelectedHasChanged事件时不允许从场景中移除对象?
有方便的解决方法吗?或者有什么好的替代方法?我可以QGraphicsRectItem用一个自定义项目替换它,该项目可以绘制为正方形或圆形,但这并不能方便地涵盖我的所有用例。我可以看到我可以完成这项工作,但肯定不会那么简单。
编辑- 解决方法:
可以通过将即将被删除的对象保留一段时间来防止这种失败。这可以通过这样的事情来完成:
def updateContents(self):
self.prepareGeometryChange()
self._temp_store = self.childItems()
for c in self.childItems():
self.scene().removeItem(c)
...
Run Code Online (Sandbox Code Playgroud)
然而,这是丑陋的代码并且增加了内存使用并没有真正的好处。相反,我已经开始使用这个答案中QGraphicsScene.selectionChanged建议的信号。
我已经调试过了。转载于 Lunix
1 qFatal(const char *, ...) *plt 0x7f05d4e81c40
2 qt_assert qglobal.cpp 2054 0x7f05d4ea197e
3 QScopedPointer<QGraphicsItemPrivate, QScopedPointerDeleter<QGraphicsItemPrivate>>::operator-> qscopedpointer.h 112 0x7f05d2c767ec
4 QGraphicsItem::flags qgraphicsitem.cpp 1799 0x7f05d2c573b8
5 QGraphicsScene::setSelectionArea qgraphicsscene.cpp 2381 0x7f05d2c94893
6 QGraphicsView::mouseMoveEvent qgraphicsview.cpp 3257 0x7f05d2cca553
7 QGraphicsViewWrapper::mouseMoveEvent qgraphicsview_wrapper.cpp 1023 0x7f05d362be83
8 QWidget::event qwidget.cpp 8374 0x7f05d2570371
Run Code Online (Sandbox Code Playgroud)
qt-everywhere-opensource-src-4.8.6/src/gui/graphicsview/qgraphicsscene.cpp:2381
void QGraphicsScene::setSelectionArea(const QPainterPath &path, Qt::ItemSelectionMode mode,
const QTransform &deviceTransform)
{
...
// Set all items in path to selected.
foreach (QGraphicsItem *item, items(path, mode, Qt::DescendingOrder, deviceTransform)) {
if (item->flags() & QGraphicsItem::ItemIsSelectable) { // item is invalid here
if (!item->isSelected())
changed = true;
unselectItems.remove(item);
item->setSelected(true);
}
}
Run Code Online (Sandbox Code Playgroud)
他们使用items()函数来查找橡皮筋选择下的项目列表。但是,如果处理时的一项删除了某些内容,则项目指针就会变得无效。下一次调用会item->flags()导致崩溃。
作为替代方案,您可以使用QGraphicsScene::selectionChanged信号。每次选择更改时仅发出一次。
看起来 Qt 预计不会有一些重大变化itemChange
这背后是您在调用时常犯的错误prepareGeometryChange()。
它被设计为在更改之前调用boundingRect。边界矩形应该是调用时的旧矩形prepareGeometryChange和调用后的新矩形。
所以这可能会发生:
在updateContents:
self.prepareGeometryChange(); # calls boundingRect. old value returned
...
shape_item.setParentItem(self); # could call the boundingRect. but still old value returned!
Run Code Online (Sandbox Code Playgroud)
添加子项后,它boundingRect再次调用,但值意外不同。
作为解决方案,您可以添加一个变量
def updateContents(self):
for c in self.childItems():
self.scene().removeItem(c)
if self.isSelected():
shape_item = QtGui.QGraphicsRectItem()
else:
shape_item = QtGui.QGraphicsEllipseItem()
shape_item.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, False)
shape_item.setFlag(QtGui.QGraphicsItem.ItemStacksBehindParent,True)
shape_item.setPen(QtGui.QPen("green"))
shape_item.setRect(QtCore.QRectF(0,0,10,10))
shape_item.setParentItem(self)
self.prepareGeometryChange();
self._childRect = self.childrenBoundingRect()
def boundingRect(self):
return self._childRect
Run Code Online (Sandbox Code Playgroud)