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
相关产品推荐
相关产品推荐

