2017-08-30 29 views
4

我有一個QGraphicsScene包含一些簡單的對象(在這個簡化的示例圓),我想改變成其他對象(這裏正方形)選中時。更具體地說,我希望有父對象,它們不繪製自己,它們是由他們的子對象繪製的,並且在各種情況下,但特別是在選擇父對象時,我希望這些子對象集更改。這是我正在開發的整個應用程序的一個很好的概念框架。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_() 

所以問題:

難道我只是做了一個錯誤,可以直接修復?

或者在處理ItemSelectedHasChanged事件時不允許從場景中移除物體?

是否有一個方便的解決方法?或者什麼是一個好的替代方法?我可以用一個自定義項目代替QGraphicsRectItem,該項目可以以方形或圓形繪製,但不能方便地覆蓋我所有的用例。我可以看到我可以完成這項工作,但肯定不會那麼簡單。

編輯 - 解決方法:

有可能通過保留大約要被刪除的對象的同時,防止這種失敗。這可以通過東西就像這樣:

def updateContents(self): 
    self.prepareGeometryChange() 
    self._temp_store = self.childItems() 
    for c in self.childItems(): 
     self.scene().removeItem(c) 

    ... 

然而,這是醜陋的代碼,並增加了沒有實際的好處內存使用情況。相反,我已經轉向使用QGraphicsScene.selectionChanged信號,建議在this的答案。

+0

什麼版本?我只是試了一下,沒有問題。 – eyllanesc

+0

好問題 - Python的2.7.11- PySide 1.2.4 - 所有的Windows 8.1 – strubbly

+0

我使用python 2.7.13和1.2.4 PySide,你是什麼操作系統,你運行它通過IDE,如果是的話?它是什麼? – eyllanesc

回答

0

我調試過它。轉載於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-比比皆是 - 開源-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

作爲一種解決方案可以是您使用添加PySide和python的變量

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 
+0

您的變量版本在Windows上仍然崩潰,此外,使用print查看調用序列語句,即使在我的代碼中,只要調用了'prepareGeometryChange',就會調用'boundingRect',並且只返回新的值。舊的即將過期的值永遠不會返回 – strubbly

+0

在boundRect方法中存在這種依賴的危險。 100%的工作方式弄清楚發生了什麼有正在調試Qt的構建,並在調試器。 –

+0

運行你的代碼,我仍然得到崩潰,如果我有一個基於'QGraphicsItemGroup'取代我的實現,在這種情況下,我不要重寫'boundingRect'。 – strubbly