PySide6 解决QWidget嵌入QGraphicsView时移动问题,提供QGraphicsProxyWidget和QGraphicsWidget两种思路。

项目应用:

当我们需要让QWidget在QGraphicsView中移动


问题描述

由于我们需要在视图中使用QWidget,所以我们需要将它转换为QGraphicsProxyWidget,并将其设置为相应的Flag,可当我们运行后会发现:它并没有像我们预期的那样移动


原因分析:

对于没有父类的Item,可能是QGrahicsProxyWidget本身就无法移动,而有QGraphicsItem或QGraphicsWidget的原因为:鼠标事件被QGraphicsProxyWidget捕获并没有向上传递给父类Item


解决方案:

于此我们有两种解决方案:

  1. 给QGraphicsProxyWidget本身实现移动方法:(此处已QLineEdit为例子)
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QMouseEvent, QFocusEvent
from PySide6.QtWidgets import (QApplication, QLineEdit,QGraphicsProxyWidget,
                               QGraphicsScene,QGraphicsView,QGraphicsSceneMouseEvent)

class lineEdit(QLineEdit):
    def __init__(self):
        super().__init__()
        self.setPlaceholderText('请输入:')
        self.setFont(QFont('sumHei,8'))
        self.setReadOnly(True)  # 设置只读
        self.resize(100, 50)

        self.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)  # 关闭右键上下菜单

    def mousePressEvent(self, arg__1: QMouseEvent) -> None:  # 单击
        if arg__1.button() == Qt.MouseButton.RightButton:  # 右键点击
            self.setReadOnly(True)
            self.deselect()
            self.graphicsProxyWidget().setCursor(Qt.CursorShape.ArrowCursor)#设置鼠标样式,自身没有效果,需要父类proxyWidget的

        super().mousePressEvent(arg__1)
    def mouseDoubleClickEvent(self, arg__1: QMouseEvent) -> None:  # 双击
        if arg__1.button() == Qt.MouseButton.LeftButton:  # 左键双击
            self.setReadOnly(False)
            self.graphicsProxyWidget().setCursor(Qt.CursorShape.IBeamCursor)

        super().mouseDoubleClickEvent(arg__1)

    def focusOutEvent(self, arg__1: QFocusEvent) -> None:  # 失去焦点
        self.setReadOnly(True)
        self.graphicsProxyWidget().setCursor(Qt.CursorShape.ArrowCursor)

        super().focusOutEvent(arg__1)
    
class lineEditProxy(QGraphicsProxyWidget):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.edit = lineEdit()

        self.setWidget(self.edit)
        self.setPos(200,225)

    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if event.buttons() == Qt.MouseButton.LeftButton and self.edit.isReadOnly():
            self.setCursor(Qt.CursorShape.ArrowCursor)#还原鼠标该有移动样式
            self.edit.deselect()#取消edit的选中状态

            self.moveBy(event.pos().x(),event.pos().y())#移动ProxyWidget
            
        super().mouseMoveEvent(event)


if __name__ == '__main__':
    app = QApplication([])
    scene = QGraphicsScene()
    scene.setSceneRect(0,0,500,500)#设置场景大小和坐标,注意要设置
    view = QGraphicsView(scene)
    scene.addItem(lineEditProxy())
    view.show()
    app.exec()

PySide6 解决QWidget嵌入QGraphicsView时移动问题,提供QGraphicsProxyWidget和QGraphicsWidget两种思路。_第1张图片

其中关键为:

def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if event.buttons() == Qt.MouseButton.LeftButton and self.edit.isReadOnly():
            self.setCursor(Qt.CursorShape.ArrowCursor)#还原鼠标该有移动样式
            self.edit.deselect()#取消edit的选中状态

            self.moveBy(event.pos().x(),event.pos().y())#移动ProxyWidget
            
        super().mouseMoveEvent(event)

本例中我们通过右键拖动LineEidt,但它本身右键会触发编辑此时我们就需要重写进入编辑的方式(详见PySide6 QLineEdit 自定义进入和退出编辑)

QGraphicsProxyWidget.moveBy(dx,dy),顾名思义:移动ProxyWidget的效果,注意这里传入的不是场景(scene)的坐标而是QGraphicsProxyWidget本身的坐标!!!

PS:由于我们将QLineEidt转换为QGraphicsProxyWidget,此时如果重写方法总会有些各种奇怪的bug,所以作者建议QWidget和QGraphicsProxyWidget分开写,这样有利于发现并更方便改正这些奇怪的bug(例如鼠标样式经常设置无效,需要调用ProxyWidget的才行或其他)

  1. 通过设置父类Item,使用父类Item本身的移动方法(QGraphicsItem或QGraphicsWidget)
from PySide6.QtCore import Qt, QRectF
from PySide6.QtGui import QFont, QMouseEvent, QFocusEvent, QPainter
from PySide6.QtWidgets import (QApplication, QLineEdit,QGraphicsItem,QGraphicsWidget,QGraphicsProxyWidget,
                               QGraphicsScene,QGraphicsView,QGraphicsSceneMouseEvent)

class lineProxy(QGraphicsProxyWidget):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.inputEdit = QLineEdit()
        self.inputEdit.setPlaceholderText('请输入文本:')
        self.inputEdit.setFont(QFont('sumHei,8'))
        self.inputEdit.setFixedSize(100, 50)
        self.inputEdit.setReadOnly(True)

        self.inputEdit.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
        self.setWidget(self.inputEdit)

    def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if event.button() == Qt.MouseButton.RightButton:
            self.inputEdit.setReadOnly(True)
            self.setCursor(Qt.CursorShape.ArrowCursor)

        if self.inputEdit.isReadOnly():
            self.parentItem().mousePressEvent(event) #关键:传递事件给QGraphicsWiget

        super().mousePressEvent(event)
    def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self.inputEdit.isReadOnly():
            self.parentItem().mouseMoveEvent(event)#关键:传递事件给QGraphicsWiget

        if self.inputEdit.isReadOnly() is False:
            super().mouseMoveEvent(event)

    def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if event.button() == Qt.MouseButton.LeftButton:
            self.inputEdit.setReadOnly(False)
            self.setCursor(Qt.CursorShape.IBeamCursor)

    def mouseReleaseEvent(self, event: QGraphicsSceneMouseEvent) -> None:
        if self.inputEdit.isReadOnly():
            self.parentItem().mouseReleaseEvent(event)

        super().mouseMoveEvent(event)

    def focusOutEvent(self, event: QFocusEvent) -> None:
        self.inputEdit.setReadOnly(True)
        self.setCursor(Qt.CursorShape.ArrowCursor)

        super().focusOutEvent(event)

class lineEditWidget(QGraphicsWidget):# /QGraphicsItem
    def __init__(self,parent=None):
        super().__init__(parent)

        lineProxy(self)#实例化Proxy并将其设置为self的子类

        #设置可移动和选中的flag
        self.setFlag(self.GraphicsItemFlag.ItemIsSelectable | self.GraphicsItemFlag.ItemIsMovable,True)
        self.setPos(200,225)


    # QGraphicsItem时需要重写
    # def boundingRect(self) -> QRectF:
    #     return QRectF(0,0,100,50)
    #
    # def paint(self, painter: QPainter, option, widget=None) -> None:
    #     pass

if __name__ == '__main__':
    app = QApplication([])
    scene = QGraphicsScene()
    scene.setSceneRect(0,0,500,500)
    view = QGraphicsView(scene)
    scene.addItem(lineEditWidget())
    view.show()
    app.exec()

上段代码运行效果可见上面第一种方法的运行结果他们是一样的

第二种核心代码是self.parentItem().mouseMoveEvent(event) 可以说它是整段代码的灵魂。

为什么要调用它就可以执行移动:因为QGraphicsItem和QGraphicsWidget本身只要通过设置相应的flag就可以实现移动(你可以试试),但加上ProxyWidget会导致QGraphicsItem / Widget 因事件(QEvent)无法传递移动 所需要的事件上(主要为鼠标事件),事件会被ProxyWidget的事件阻塞,而无法正常移动。此时显而易见我们只需像这样把需要的相关事件手动传递给它的父类Item(parentItem)的相关函数就行。

QEvent 传递导致:
QWidget → QGraphicsProxyWidget → QGraphicsItem / Wiget → QGraphicsScene

你可能感兴趣的:(python,pyqt,用户界面)