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

QGraphicsItem子项仅父项选中时可见的路径编辑交互实现问题

类似Photoshop的路径编辑交互实现问题

交互需求

  • 选中路径后,其控制点变为可见
  • 用户可点击拖动路径的任意控制点调整路径
  • 直接拖动路径可移动整个路径
  • 取消选中路径后,控制点隐藏

当前问题

  • 需实现路径取消选中时隐藏控制点,但编辑选中路径的控制点时不隐藏

效果对比

  • 当前实现效果:当前效果
  • 目标效果参考:目标效果

解决方案

核心逻辑是:只要路径本身或其任意控制点处于选中状态,就保持控制点可见;只有当两者都未被选中时,才隐藏控制点。以下是具体修改方案:

1. 修改控制点类,同步选中状态到父路径

在AnnotationPointItem的itemChange方法中添加选中变化通知,让父路径实时更新控制点显示状态:

def itemChange(self, change, value):
    if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
        parent = self.parentItem()
        if parent:
            if self.positionFlag == 0:
                parent.setPoints(start=self.pos())
            elif self.positionFlag == 1:
                parent.setPoints(end=self.pos())
    elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
        # 通知父路径更新控制点显示
        parent = self.parentItem()
        if parent:
            parent.updateHandleVisibility()
    return super(AnnotationPointItem, self).itemChange(change, value)

2. 完善路径类的显示控制逻辑

在AnnotationPathItem中替换原有的selectionChanged方法,新增updateHandleVisibility方法统一处理显示逻辑:

def itemChange(self, change, value):
    if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
        self.updateHandleVisibility()
    return super(AnnotationPathItem, self).itemChange(change, value)

def updateHandleVisibility(self):
    # 判断是否需要显示控制点:路径选中 或 任一控制点选中
    shouldShow = self.isSelected() or self.startPointItem.isSelected() or self.endPointItem.isSelected()
    self.startPointItem.setVisible(shouldShow)
    self.endPointItem.setVisible(shouldShow)

修改后的完整代码

import sys
import math
import random

from PySide2 import QtWidgets, QtGui, QtCore

# SETTINGS
handle_size = 16
handle_color = QtGui.QColor(40,130,230)
handle_radius = 8


class AnnotationPointItem(QtWidgets.QGraphicsEllipseItem):
    def __init__(self, positionFlag=0, pos=QtCore.QPointF(), parent=None):
        super(AnnotationPointItem, self).__init__(-handle_radius, -handle_radius, 2*handle_radius, 2*handle_radius, parent)
        
        self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable | QtWidgets.QGraphicsItem.ItemSendsScenePositionChanges)    
        self.setPen(QtGui.QPen(handle_color, 4, QtCore.Qt.SolidLine))
        self.setBrush(QtGui.QBrush(QtGui.QColor('white')))
        self.positionFlag = positionFlag 

    def paint(self, painter, option, widget=None):
        # 移除选中轮廓
        if self.isSelected():
            option.state &= ~QtWidgets.QStyle.State_Selected
        super(AnnotationPointItem, self).paint(painter, option, widget)

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
            parent = self.parentItem()
            if parent:
                if self.positionFlag == 0:
                    parent.setPoints(start=self.pos())
                elif self.positionFlag == 1:
                    parent.setPoints(end=self.pos())
        elif change == QtWidgets.QGraphicsItem.ItemSelectedChange:
            # 通知父路径更新控制点显示
            parent = self.parentItem()
            if parent:
                parent.updateHandleVisibility()
        return super(AnnotationPointItem, self).itemChange(change, value)


class AnnotationPathItem(QtWidgets.QGraphicsLineItem):
    def __init__(self, 
        start=QtCore.QPointF(), 
        end=QtCore.QPointF(), 
        color=QtCore.Qt.green,
        thickness=10,
        parent=None):
        super(AnnotationPathItem, self).__init__(start.x(), start.y(), end.x(), end.y(), parent)

        self._color = color
        self._thickness = thickness

        self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable | QtWidgets.QGraphicsItem.ItemIsSelectable)
        self.setPen(QtGui.QPen(self._color, self._thickness, QtCore.Qt.SolidLine))

        # 子控制点
        self.startPointItem = AnnotationPointItem(positionFlag=0, parent=self)
        self.startPointItem.hide()
        self.startPointItem.setPos(self.line().p1())

        self.endPointItem = AnnotationPointItem(positionFlag=1, parent=self)
        self.endPointItem.hide()
        self.endPointItem.setPos(self.line().p2())

    def itemChange(self, change, value):
        if change == QtWidgets.QGraphicsItem.ItemSelectedChange:
            self.updateHandleVisibility()
        return super(AnnotationPathItem, self).itemChange(change, value)

    def updateHandleVisibility(self):
        # 只要路径或任一控制点选中,就显示控制点
        shouldShow = self.isSelected() or self.startPointItem.isSelected() or self.endPointItem.isSelected()
        self.startPointItem.setVisible(shouldShow)
        self.endPointItem.setVisible(shouldShow)

    def paint(self, painter, option, widget=None):
        # 移除路径选中轮廓
        if self.isSelected():
            option.state &= ~QtWidgets.QStyle.State_Selected
        super(AnnotationPathItem, self).paint(painter, option, widget)

    def setPoints(self, start=None, end=None):
        currentLine = self.line()
        if start is not None:
            currentLine.setP1(start)
        if end is not None:
            currentLine.setP2(end)
        self.setLine(currentLine)


class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.resize(1200,1200)

        self.scene = QtWidgets.QGraphicsScene(self)
        self.scene.setBackgroundBrush(QtGui.QColor(40,40,40))

        self.view = QtWidgets.QGraphicsView(self)
        self.view.setSceneRect(-4000, -4000, 8000, 8000)
        self.view.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
        self.view.setMouseTracking(True)
        self.view.setScene(self.scene)

        self.addButton = QtWidgets.QPushButton("Add Annotation", self)
        self.addButton.clicked.connect(self.add_annotation)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.view)
        layout.addWidget(self.addButton)
        self.setLayout(layout)

        # 示例路径
        item = AnnotationPathItem(QtCore.QPointF(-70, -150), QtCore.QPointF(150, -350))
        self.scene.addItem(item)

    def add_annotation(self):
        r = random.randint(0,255)
        g = random.randint(0,255)
        b = random.randint(0,255)
        color = QtGui.QColor(r,g,b)
        startPos = QtCore.QPointF(random.randint(-200,200), random.randint(-200,200))
        endPos = QtCore.QPointF(random.randint(-200,200), random.randint(-200,200))
        item = AnnotationPathItem(startPos, endPos, color)
        self.scene.addItem(item)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec_()

逻辑说明

  • 路径选中时自动显示控制点;路径取消选中时,先检查是否有控制点处于选中状态,若有则保持显示,否则隐藏。
  • 控制点的选中状态变化会实时通知父路径,触发显示状态更新,避免了点击控制点时路径失活导致控制点隐藏的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:07:33