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

PyQt5 QGraphicsScene节点编号、位置更新及唯一ID存储咨询

解决方案

1. 优化节点坐标更新效率

原来通过遍历全局列表查找节点再更新坐标的方案时间复杂度为O(n),节点数量大时性能损耗明显,可通过以下方式优化为O(1)效率:

  • 直接将节点ID、坐标作为Node类的实例属性,不需要依赖全局列表存储,拖动节点时直接修改当前触发事件的Node实例自身属性即可,完全省去遍历查找过程。
  • 废弃全局列表的存储方式,改用字典存储节点映射:键为唯一ID,值为Node实例,需要批量查询时直接通过ID取对象即可。

2. 唯一ID分配与快速检索实现

唯一ID生成

给Node类加类级别的自增ID计数器,每次实例化Node时自动分配唯一ID,不需要手动维护全局编号变量,不会出现ID重复问题。

ID存储

两种存储方式可以同时使用,适配不同检索场景:

  1. 作为Node的实例属性self.node_id存储,直接通过实例访问。
  2. 利用QGraphicsItem内置的setData()方法将ID存入Item的自定义数据域,后续从QGraphicsScene.items()返回的任意元素中都可以通过data()方法快速判断是否为Node节点、获取对应ID。

快速检索

在Scene类中维护两个映射字典:

  • self.node_map:键为node_id,值为Node实例,查询指定ID的节点时直接取值即可,无需遍历所有scene元素。
  • self.group_map:键为连通组ID,值为该组所有节点ID的列表,批量删除连通组时直接遍历对应组的ID列表操作即可。

核心修改代码示例

修改后的Node类

class Node(QGraphicsEllipseItem):
    # 类级别的自增ID计数器
    _next_id = 0
    def __init__(self, rect=QRectF(-20, -20, 20, 20), parent=None):
        super().__init__(rect, parent)
        # 分配唯一ID
        self.node_id = Node._next_id
        Node._next_id += 1
        # 将ID存入Item内置数据域,自定义键从Qt.UserRole开始
        self.setData(Qt.UserRole, self.node_id)
        self.edges = []
        # 原有初始化逻辑保持不变
        self.setZValue(1)
        self.setBrush(Qt.darkGray)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)

    def mouseMoveEvent(self, event):
        origCursorPos = event.lastScenePos()
        actualCursorPos = event.scenePos()
        origPos = self.scenePos()
        new_x = actualCursorPos.x() - origCursorPos.x() + origPos.x()
        new_y = actualCursorPos.y() - origCursorPos.y() + origPos.y()
        self.setPos(QPointF(new_x, new_y))
        # 直接读取当前节点的ID和坐标,无需遍历全局列表
        print(f"节点ID {self.node_id} 新坐标: {new_x:.1f}, {new_y:.1f}")

修改后的Scene类

class SceneClass(QGraphicsScene):
    prepStartEndNode = 0
    def __init__(self, parent=None):
        super().__init__(QRectF(0, 0, 2000, 2000), parent)
        self.node_start = None
        self.node_end = None
        # 节点ID到实例的映射
        self.node_map = {}
        # 连通组ID到节点ID列表的映射
        self.group_map = {}
        self._current_group_id = 0

    # 新增节点时存入映射的逻辑示例
    def mouseDoubleClickEvent(self, event):
        if event.button() == Qt.LeftButton and self.node_start is None:
            SceneClass.prepStartEndNode = 1
            node = Node()
            # 新增节点存入映射
            self.node_map[node.node_id] = node
            # 新建连通组
            self.group_map[self._current_group_id] = [node.node_id]
            self.addItem(node)
            node.setPos(event.scenePos() + QPointF(10, 10))
            self.node_start = node
        else:
            self.node_start = None
            SceneClass.prepStartEndNode = 0
            # 当前连通组创建完成,组ID自增
            self._current_group_id += 1
            self.saveNodeToGlobalList()

检索与删除示例

# 检索指定ID的节点
target_node = self.node_map.get(3)
if target_node:
    print(f"找到节点ID3,坐标:{target_node.pos().x()}, {target_node.pos().y()}")

# 批量删除指定连通组
def delete_group(self, group_id):
    if group_id not in self.group_map:
        return
    for node_id in self.group_map[group_id]:
        node = self.node_map.pop(node_id)
        # 先删除关联边
        for edge in node.edges:
            self.removeItem(edge)
        # 再删除节点
        self.removeItem(node)
    del self.group_map[group_id]

# 从scene.items()中遍历筛选所有节点
for item in self.items():
    node_id = item.data(Qt.UserRole)
    if node_id is not None:
        print(f"遍历到节点ID:{node_id}")

内容的提问来源于stack exchange,提问作者Pr.Syn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 02:00:04