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