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()

运行结果

其中关键为:

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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用 PySide6 中的 QGraphicsView 类可以实现鼠标滚轮放大缩小和移动功能。具体实现步骤如下: 1. 继承 QGraphicsView 类创建自定义视图类,例如 MyGraphicsView。 ```python from PySide6.QtWidgets import QGraphicsView class MyGraphicsView(QGraphicsView): pass ``` 2. 在自定义视图类中重写 wheelEvent() 函数,实现鼠标滚轮缩放功能。可以通过调整视图的缩放比例和滚轮事件的 delta() 值来实现缩放效果。 ```python from PySide6.QtCore import Qt class MyGraphicsView(QGraphicsView): def wheelEvent(self, event): # 改变视图缩放比例 zoomInFactor = 1.25 zoomOutFactor = 1 / zoomInFactor if event.angleDelta().y() > 0: zoomFactor = zoomInFactor else: zoomFactor = zoomOutFactor self.scale(zoomFactor, zoomFactor) ``` 3. 在自定义视图类中添加 mousePressEvent() 和 mouseMoveEvent() 函数,实现鼠标移动功能。通过记录鼠标按下的位置和移动后的位置,计算出偏移量并调整视图的位置实现移动效果。 ```python class MyGraphicsView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) # 设置拖拽模式为滚动条拖拽 def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._lastPos = event.pos() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: delta = event.pos() - self._lastPos self._lastPos = event.pos() self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x()) self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y()) ``` 最终实现的 MyGraphicsView 类可以同实现鼠标滚轮放大缩小和移动功能。 完整代码如下: ```python from PySide6.QtWidgets import QGraphicsView from PySide6.QtCore import Qt class MyGraphicsView(QGraphicsView): def __init__(self, parent=None): super().__init__(parent) self.setDragMode(QGraphicsView.ScrollHandDrag) def wheelEvent(self, event): zoomInFactor = 1.25 zoomOutFactor = 1 / zoomInFactor if event.angleDelta().y() > 0: zoomFactor = zoomInFactor else: zoomFactor = zoomOutFactor self.scale(zoomFactor, zoomFactor) def mousePressEvent(self, event): if event.button() == Qt.LeftButton: self._lastPos = event.pos() def mouseMoveEvent(self, event): if event.buttons() == Qt.LeftButton: delta = event.pos() - self._lastPos self._lastPos = event.pos() self.horizontalScrollBar().setValue(self.horizontalScrollBar().value() - delta.x()) self.verticalScrollBar().setValue(self.verticalScrollBar().value() - delta.y()) ``` 可以在 PySide6 应用程序中使用 MyGraphicsView 类来创建带有鼠标滚轮放大缩小和移动功能的视图。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值