QGraphicsScene 选中时改变对象
QGraphicsScene changing objects when selected
我有一个 QGraphicsScene
包含一些简单的对象(在这个简化的示例圆圈中),我想在选择时更改为其他对象(这里是正方形)。 More specifically I'd like to have parent objects which don't draw themselves, they are drawn by their child objects, and under various circumstances, but in particular when the parent objects are selected, I'd like the set of child objects to改变。这是我正在开发的整个应用程序的一个很好的概念框架。
所以我在 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_()
所以问题:
我刚刚犯了一个可以直接修复的错误吗?
或者在处理 ItemSelectedHasChanged
事件时不允许从场景中移除对象?
有没有方便的解决方法?或者什么是好的替代方法?我可以将 QGraphicsRectItem
替换为自定义项目,该项目可以绘制为正方形或圆形,但不能方便地涵盖我的所有用例。我知道我可以完成这项工作,但肯定不会那么简单。
编辑 - 解决方法:
可以通过将即将删除的对象保留一段时间来防止这种失败。这可以通过这样的事情来完成:
def updateContents(self):
self.prepareGeometryChange()
self._temp_store = self.childItems()
for c in self.childItems():
self.scene().removeItem(c)
...
但是,这是丑陋的代码,增加了内存使用量并没有实际好处。相反,我已按照 答案中的建议使用 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
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);
}
}
他们正在使用 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!
添加子项后,它再次调用 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
我有一个 QGraphicsScene
包含一些简单的对象(在这个简化的示例圆圈中),我想在选择时更改为其他对象(这里是正方形)。 More specifically I'd like to have parent objects which don't draw themselves, they are drawn by their child objects, and under various circumstances, but in particular when the parent objects are selected, I'd like the set of child objects to改变。这是我正在开发的整个应用程序的一个很好的概念框架。
所以我在 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_()
所以问题:
我刚刚犯了一个可以直接修复的错误吗?
或者在处理 ItemSelectedHasChanged
事件时不允许从场景中移除对象?
有没有方便的解决方法?或者什么是好的替代方法?我可以将 QGraphicsRectItem
替换为自定义项目,该项目可以绘制为正方形或圆形,但不能方便地涵盖我的所有用例。我知道我可以完成这项工作,但肯定不会那么简单。
编辑 - 解决方法:
可以通过将即将删除的对象保留一段时间来防止这种失败。这可以通过这样的事情来完成:
def updateContents(self):
self.prepareGeometryChange()
self._temp_store = self.childItems()
for c in self.childItems():
self.scene().removeItem(c)
...
但是,这是丑陋的代码,增加了内存使用量并没有实际好处。相反,我已按照 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
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);
}
}
他们正在使用 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!
添加子项后,它再次调用 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