求助:基于wxPython Phoenix实现类似Unreal的拖拽节点连线应用
实现wxPython节点连线与连接功能的核心方案
1. 基础数据结构定义
先通过简单类存储节点和连接的核心信息,方便后续管理:
class Node: def __init__(self, x, y, width=100, height=60): self.x = x self.y = y self.width = width self.height = height # 定义节点的输入/输出引脚坐标 self.input_pin = (x, y + height//2) self.output_pin = (x + width, y + height//2) class Connection: def __init__(self, start_node, end_node): self.start_node = start_node self.end_node = end_node self.start = start_node.output_pin self.end = end_node.input_pin
2. 自定义节点编辑器面板
继承wx.Panel,重写事件处理函数实现拖拽、连线绘制等核心交互:
import wx class NodeEditorPanel(wx.Panel): def __init__(self, parent): super().__init__(parent) self.nodes = [] self.connections = [] self.temp_connection = None # 记录拖拽中的临时连线 self.selected_node = None # 记录正在拖拽的节点 self.drag_offset = (0, 0) # 绑定事件 self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) self.Bind(wx.EVT_MOTION, self.OnMouseMotion) # 添加测试节点 self.nodes.append(Node(100, 100)) self.nodes.append(Node(300, 200)) def OnPaint(self, event): dc = wx.PaintDC(self) dc.SetBackground(wx.Brush(wx.WHITE)) dc.Clear() # 先绘制连线(避免被节点遮挡) dc.SetPen(wx.Pen(wx.BLUE, 2)) for conn in self.connections: dc.DrawLine(conn.start[0], conn.start[1], conn.end[0], conn.end[1]) # 绘制拖拽中的临时虚线连线 if self.temp_connection: dc.SetPen(wx.Pen(wx.RED, 2, wx.PENSTYLE_DASH)) dc.DrawLine(*self.temp_connection) # 绘制节点和引脚 dc.SetPen(wx.Pen(wx.BLACK, 1)) dc.SetBrush(wx.Brush(wx.LIGHT_GREY)) for node in self.nodes: dc.DrawRectangle(node.x, node.y, node.width, node.height) # 绘制输入引脚(绿色) dc.SetBrush(wx.Brush(wx.GREEN)) dc.DrawCircle(node.input_pin[0], node.input_pin[1], 5) # 绘制输出引脚(红色) dc.SetBrush(wx.Brush(wx.RED)) dc.DrawCircle(node.output_pin[0], node.output_pin[1], 5) def OnLeftDown(self, event): pos = event.GetPosition() # 检查是否点击输出引脚,启动连线拖拽 for node in self.nodes: if abs(pos.x - node.output_pin[0]) < 5 and abs(pos.y - node.output_pin[1]) < 5: self.temp_connection = (node.output_pin[0], node.output_pin[1], pos.x, pos.y) return # 检查是否点击节点,启动节点拖拽 for node in reversed(self.nodes): if node.x <= pos.x <= node.x + node.width and node.y <= pos.y <= node.y + node.height: self.selected_node = node self.drag_offset = (pos.x - node.x, pos.y - node.y) return def OnLeftUp(self, event): pos = event.GetPosition() # 完成连线创建:检查是否连接到输入引脚 if self.temp_connection: start_pin = (self.temp_connection[0], self.temp_connection[1]) start_node = next((n for n in self.nodes if n.output_pin == start_pin), None) for node in self.nodes: if abs(pos.x - node.input_pin[0]) < 5 and abs(pos.y - node.input_pin[1]) < 5 and start_node != node: self.connections.append(Connection(start_node, node)) self.Refresh() self.temp_connection = None self.selected_node = None def OnMouseMotion(self, event): if event.Dragging() and event.LeftIsDown(): pos = event.GetPosition() if self.temp_connection: # 更新临时连线的终点坐标 self.temp_connection = (self.temp_connection[0], self.temp_connection[1], pos.x, pos.y) self.Refresh() elif self.selected_node: # 拖拽节点并更新关联连线的坐标 self.selected_node.x = pos.x - self.drag_offset[0] self.selected_node.y = pos.y - self.drag_offset[1] self.selected_node.input_pin = (self.selected_node.x, self.selected_node.y + self.selected_node.height//2) self.selected_node.output_pin = (self.selected_node.x + self.selected_node.width, self.selected_node.y + self.selected_node.height//2) # 更新所有与该节点相关的连线 for conn in self.connections: if conn.start_node == self.selected_node: conn.start = self.selected_node.output_pin if conn.end_node == self.selected_node: conn.end = self.selected_node.input_pin self.Refresh() # 主窗口 class MainFrame(wx.Frame): def __init__(self): super().__init__(None, title="Unreal-Style Node Editor", size=(800, 600)) NodeEditorPanel(self) self.Show() if __name__ == "__main__": app = wx.App() MainFrame() app.MainLoop()
3. 核心功能说明
- 节点拖拽:点击节点区域可拖动节点,关联连线会自动跟随更新坐标
- 连线创建:点击红色输出引脚,拖拽到另一节点的绿色输入引脚,松开鼠标即可完成连接
- 连线预览:拖拽过程中会显示红色虚线预览最终连线效果
4. 扩展方向
- 给节点添加多组输入/输出引脚,为每个引脚添加唯一标识
- 实现右键删除连线、节点的功能
- 区分不同类型节点的样式(颜色、尺寸、引脚数量)
- 添加菜单/工具栏实现节点的添加、删除操作
内容的提问来源于stack exchange,提问作者Swatcat
相关产品推荐
相关产品推荐

