QGraphicsItem在移动后没有留在原地

Posted

技术标签:

【中文标题】QGraphicsItem在移动后没有留在原地【英文标题】:QGraphicsItem not staying in place after being moved 【发布时间】:2019-01-26 14:39:11 【问题描述】:

我目前正在创建一个使用 QGraphicsView 并允许用户移动 QGraphicsItems 的应用程序,因此他们可以创建类似图表的结构。

我需要项目在单击时更改颜色,但在释放鼠标按钮时更改回原来的颜色。但是,当我定义“mouseReleaseEvent()”方法时,当我在移动后单击视口上的任意位置时,该项目只会恢复到原始位置。

第一次移动后如何让项目保持原位?

为了对item定位有更多的控制,我尝试对场景使用“setSceneRect()”,但并没有解决问题。

我也无法使用 CustomItem 的“setPos()”方法解决问题。当同一场景中有多个项目时,坐标系似乎会发生变化。

另一个问题是,当鼠标悬停在某些其他项目上时,它们应该会改变颜色。我尝试覆盖 CustomItem 类中的“hoverEnterEvent()”方法,但它不起作用。

这是重现我面临的问题的最小代码。

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(QtWidgets.QMainWindow):

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.setupUi(self)

        self.main_menu = self.menuBar().addMenu("&Menu") 
        self.addItem = QtWidgets.QAction("&Add Rectangle", self, triggered = self.addRectangle)
        self.delItem = QtWidgets.QAction("&Delete Selected Rectangle(s)", self, triggered = self.delRectangle) 
        self.main_menu.addAction(self.addItem) 
        self.main_menu.addAction(self.delItem) 


        self.scene = CustomScene(self.main_menu) 
        self.graphicsView.setScene(self.scene)

    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.graphicsView = QtWidgets.QGraphicsView(self.centralwidget)
        self.graphicsView.setObjectName("graphicsView")
        self.verticalLayout.addWidget(self.graphicsView)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))

    def addRectangle(self):
        self.item = CustomItem()
        self.scene.addItem(self.item)
        self.scene.update()

    def delRectangle(self):
        for item in self.scene.selectedItems():
            self.scene.removeItem(item)

class CustomScene (QtWidgets.QGraphicsScene):

    def __init__(self, scene_menu, parent=None):
        super(CustomScene, self).__init__(parent)

        self.setSceneRect(0,0,750,500)
        self.sceneMenu = scene_menu

    def contextMenuEvent (self, event):
        self.sceneMenu.exec_(event.screenPos()) 

class CustomItem (QtWidgets.QGraphicsRectItem):

    def __init__(self, parent = None, scene = None):
        super(CustomItem, self).__init__()

        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True) 
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) 

        self.setRect(200,200,120,25) #Creates and show the rectangle

    def contextMenuEvent(self, event): #Defines the menu shown on mouse right-click
        self.scene().clearSelection()
        self.setSelected(True)
        self.RCMenu.exec_(event.screenPos()) 

    def mousePressEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.cyan))

    def hoverEnterEvent(self, event): #Not Working as intended, but it should change the rectangle's color to lightGray when I hover the mouse over it
        self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))

    def mouseReleaseEvent(self, event): # HERE IS THE REAL PROBLEM. WHENEVER I CLICK ON THE RECTANGLE AFTER IT'S RELEASE, IT GOES TO A SEEMINGLY RANDOM LOCATION
        self.setBrush(QtGui.QBrush(QtCore.Qt.white))

if __name__ == "__main__":
    import sys

    if not QtWidgets.QApplication.instance():
        app = QtWidgets.QApplication(sys.argv)

    else:
        app = QtWidgets.QApplication.instance()

    window = Ui_MainWindow()
    window.show()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

QGraphicsItem已经有移动到新位置的行为,但是当你覆盖这些行为时,因为你正在删除父类的实现,如果你想保留这个行为你必须调用父类的方法通过 super 类。

另一方面,您必须使用setAcceptHoverEvents(True) 来启用悬停类型的事件。

以上解决办法是:

class CustomItem (QtWidgets.QGraphicsRectItem):
    def __init__(self, parent = None, scene = None):
        super(CustomItem, self).__init__()
        self.setAcceptHoverEvents(True)
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True) 
        self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True) 
        self.setRect(200,200,120,25) #Creates and show the rectangle

    def contextMenuEvent(self, event): #Defines the menu shown on mouse right-click
        self.scene().clearSelection()
        self.setSelected(True)
        self.RCMenu.exec_(event.screenPos()) 

    def mousePressEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.cyan))
        super(CustomItem, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.white))
        super(CustomItem, self).mouseReleaseEvent(event)

    def hoverEnterEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.lightGray))
        super(CustomItem, self).hoverEnterEvent(event)

    def hoverLeaveEvent(self, event):
        self.setBrush(QtGui.QBrush(QtCore.Qt.white))
        super(CustomItem, self).hoverLeaveEvent(event)

另一方面,用于删除项目的代码存在问题,但现在它不可见,因为直到现在您只是逐个元素地删除,但是当您想要消除一组元素时,您会看到问题,当您从列表中删除项目时,您必须从头开始,因为如果您在访问未分配的内存时不会遇到问题,那么在您的情况下,解决方案是:

def delRectangle(self):
    for item in reversed(self.scene.selectedItems()):
        self.scene.removeItem(item)

【讨论】:

以上是关于QGraphicsItem在移动后没有留在原地的主要内容,如果未能解决你的问题,请参考以下文章

熊猫留在原地加入

QGraphicsItem绘制问题

QGraphicsItem:如何在 QGraphicsScene 调整大小时自动移动它?

如何知道当前正在移动哪个 QGraphicsItem?

HDRi 环境和移动摄像机

使用 mouseMoveEvent 约束 QGraphicsItem 移动