You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PySide QGraphicsPathItem鼠标悬停事件异常问题求助

解决QGraphicsPathItem悬停事件仅部分生效的问题

我来帮你搞定这个悬停异常的问题!你的代码里核心问题出在手动硬编码的boundingRect范围太小,导致曲线的右下部分根本不在事件检测的范围内——哪怕你设置了QPainterPathStroker也没用,因为Qt的图形项事件触发有个前提:鼠标必须先落在boundingRect定义的区域内,才会进一步检查shape的范围。

问题分析

你当前的boundingRect返回的是QRectF(-100,-100,120,120),这个矩形的右下角坐标是(-100+120, -100+120) = (20,20),但你的曲线终点是(120,120),远远超出了这个范围。所以鼠标在右下部分时,连第一步的boundingRect检测都过不了,自然触发不了悬停事件。

解决方案

只需要修改boundingRect方法,让它自动适配你的shape范围(也就是你用QPainterPathStroker生成的宽笔触区域),而不是手动写死数值。

修改后的关键代码片段:

def boundingRect(self):
    # 基于shape自动计算包围盒,确保覆盖所有需要检测事件的区域
    return self.shape().boundingRect()

完整修改后的代码

from PySide import QtCore, QtGui
import math  # 补上原代码缺失的math模块导入

class Edge(QtGui.QGraphicsPathItem):
    def __init__(self):
        QtGui.QGraphicsPathItem.__init__(self)
        self.setAcceptsHoverEvents(True)
        path = QtGui.QPainterPath()
        x1 = -100
        x2 = 120
        y1 = -100
        y2 = 120
        dx = abs(x1-x2)/2
        dy = abs(y1-y2)/2
        a = QtCore.QPointF(x1, y1)
        b = QtCore.QPointF(x1+dx, y1)
        c = QtCore.QPointF(x2-dy, y2)
        d = QtCore.QPointF(x2, y2)
        path.moveTo(a)
        path.cubicTo(b,c,d)
        self.setPath(path)
        self.hover = False

    def hoverEnterEvent(self, event):
        self.hover = True
        QtGui.QGraphicsPathItem.hoverEnterEvent(self, event)

    def hoverMoveEvent(self, event):
        QtGui.QGraphicsPathItem.hoverMoveEvent(self, event)

    def hoverLeaveEvent(self, event):
        self.hover = False
        QtGui.QGraphicsPathItem.hoverLeaveEvent(self, event)

    def boundingRect(self):
        # 修复:基于shape自动计算包围盒,覆盖所有可交互区域
        return self.shape().boundingRect()

    def paint(self, painter, option, widget):
        if self.hover:
            c = QtCore.Qt.red
        else:
            c = QtCore.Qt.black
        painter.setPen(QtGui.QPen(c, 10, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
        painter.drawPath(self.path())
        painter.setPen(QtGui.QPen(QtCore.Qt.blue, 1, QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
        painter.drawPath(self.shape())

    def shape(self):
        s = QtGui.QPainterPathStroker()
        s.setWidth(30)
        s.setCapStyle(QtCore.Qt.RoundCap)
        path = s.createStroke(self.path())
        return path

class GraphWidget(QtGui.QGraphicsView):
    def __init__(self):
        QtGui.QGraphicsView.__init__(self)
        scene = QtGui.QGraphicsScene(self)
        scene.setItemIndexMethod(QtGui.QGraphicsScene.NoIndex)
        scene.setSceneRect(-200, -200, 400, 400)
        self.setScene(scene)
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.setTransformationAnchor(QtGui.QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter)
        self.edge = Edge()
        scene.addItem(self.edge)
        self.scale(0.8, 0.8)
        self.setMinimumSize(400, 400)
        self.setWindowTitle(self.tr("Elastic Nodes"))

    def wheelEvent(self, event):
        self.scaleView(math.pow(2.0, -event.delta() / 240.0))

    def drawBackground(self, painter, rect):
        sceneRect = self.sceneRect()
        rightShadow = QtCore.QRectF(sceneRect.right(), sceneRect.top() + 5, 5, sceneRect.height())
        bottomShadow = QtCore.QRectF(sceneRect.left() + 5, sceneRect.bottom(), sceneRect.width(), 5)
        if rightShadow.intersects(rect) or rightShadow.contains(rect):
            painter.fillRect(rightShadow, QtCore.Qt.darkGray)
        if bottomShadow.intersects(rect) or bottomShadow.contains(rect):
            painter.fillRect(bottomShadow, QtCore.Qt.darkGray)
        gradient = QtGui.QLinearGradient(sceneRect.topLeft(), sceneRect.bottomRight())
        gradient.setColorAt(0, QtCore.Qt.white)
        gradient.setColorAt(1, QtCore.Qt.lightGray)
        painter.fillRect(rect.intersect(sceneRect), QtGui.QBrush(gradient))
        painter.setBrush(QtCore.Qt.NoBrush)
        painter.drawRect(sceneRect)

    def scaleView(self, scaleFactor):
        factor = self.matrix().scale(scaleFactor, scaleFactor).mapRect(QtCore.QRectF(0, 0, 1, 1)).width()
        if factor < 0.07 or factor > 100:
            return
        self.scale(scaleFactor, scaleFactor)

    def mouseMoveEvent(self, event):
        self.edge.update()
        QtGui.QGraphicsView.mouseMoveEvent(self, event)

widget = GraphWidget()
widget.show()

额外说明

  1. 我补上了原代码缺失的import math——因为wheelEvent里用到了math.pow,不导入会直接报错。
  2. 现在boundingRect会自动跟随shape的范围,不管你怎么调整曲线路径或者stroke宽度,都能保证所有可交互区域被覆盖,悬停事件就能在整个30px宽的笔触范围内正常触发了。

内容的提问来源于stack exchange,提问作者chengxudude

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 10:07:27