如何在C# WPF中实现支持节点拖拽与边创建的交互式Dijkstra图?
嘿,我帮你梳理下在WPF里实现这个功能的关键步骤,拆解开来其实没那么复杂~
一、实现可拖拽的椭圆节点
首先得做一个能被拖拽的自定义节点控件,核心是处理鼠标事件:
- 先定义一个
Node类(可以直接继承Ellipse,或者封装成UserControl),给它加Id、Position这类属性,方便后续识别和绑定位置,还要加一个PositionChanged事件,用来通知边更新位置。 - 处理三个关键鼠标事件:
MouseDown:记录拖拽起始点,开启拖拽状态并捕获鼠标MouseMove:根据鼠标位移更新节点在Canvas上的位置MouseUp:结束拖拽并释放鼠标捕获
代码示例:
public class Node : Ellipse { public event EventHandler PositionChanged; private Point _dragStartPoint; private bool _isDragging; public Node() { // 给节点设置默认样式,比如椭圆大小、颜色 Width = 40; Height = 40; Fill = Brushes.LightBlue; Stroke = Brushes.Black; // 绑定事件 MouseDown += Node_MouseDown; MouseMove += Node_MouseMove; MouseUp += Node_MouseUp; } private void Node_MouseDown(object sender, MouseButtonEventArgs e) { if (e.LeftButton == MouseButtonState.Pressed) { _dragStartPoint = e.GetPosition(this); _isDragging = true; CaptureMouse(); } } private void Node_MouseMove(object sender, MouseEventArgs e) { if (_isDragging && e.LeftButton == MouseButtonState.Pressed) { var canvas = Parent as Canvas; if (canvas == null) return; Point currentPoint = e.GetPosition(canvas); double offsetX = currentPoint.X - _dragStartPoint.X; double offsetY = currentPoint.Y - _dragStartPoint.Y; // 更新节点在Canvas上的位置 Canvas.SetLeft(this, Canvas.GetLeft(this) + offsetX); Canvas.SetTop(this, Canvas.GetTop(this) + offsetY); _dragStartPoint = e.GetPosition(this); // 触发位置变化事件,让边跟着更新 PositionChanged?.Invoke(this, EventArgs.Empty); } } private void Node_MouseUp(object sender, MouseButtonEventArgs e) { if (_isDragging) { _isDragging = false; ReleaseMouseCapture(); } } }
- 添加节点到Canvas:可以做个按钮,点击时在鼠标位置生成新的
Node实例,加到Canvas的Children集合里。
二、创建与节点位置绑定的边
边用Line控件实现,关键是让它的起点和终点自动跟随节点位置变化:
- 定义
Edge类继承Line,关联两个Node,并监听它们的位置变化事件:
public class Edge : Line { public Node StartNode { get; set; } public Node EndNode { get; set; } public int Weight { get; set; } = 1; // 默认权重为1,后续可以让用户修改 public Edge(Node start, Node end) { StartNode = start; EndNode = end; Stroke = Brushes.Black; StrokeThickness = 2; // 初始化边的位置 UpdateLinePosition(); // 监听节点位置变化,实时更新边 StartNode.PositionChanged += (s, e) => UpdateLinePosition(); EndNode.PositionChanged += (s, e) => UpdateLinePosition(); } private void UpdateLinePosition() { // 计算节点中心位置(因为椭圆的定位是左上角,所以要加半宽半高) X1 = Canvas.GetLeft(StartNode) + StartNode.Width / 2; Y1 = Canvas.GetTop(StartNode) + StartNode.Height / 2; X2 = Canvas.GetLeft(EndNode) + EndNode.Width / 2; Y2 = Canvas.GetTop(EndNode) + EndNode.Height / 2; } }
- 创建边的逻辑:可以做个“添加边”的模式,比如点击按钮进入加边状态,先点击第一个节点作为起点,再点击第二个节点作为终点,然后生成
Edge实例加到Canvas里。注意要把边的ZIndex设得比节点低,不然会挡住节点影响拖拽。
三、从Canvas读取元素生成图结构
把可视化的节点和边转换成算法能识别的图数据结构:
- 遍历Canvas的
Children,筛选出所有Node和Edge实例 - 构建邻接表(最适合Dijkstra的结构),用字典存储每个节点的邻接节点和对应权重:
private Dictionary<Node, List<(Node Neighbor, int Weight)>> BuildGraph() { var graph = new Dictionary<Node, List<(Node, int)>>(); var nodes = MyCanvas.Children.OfType<Node>().ToList(); var edges = MyCanvas.Children.OfType<Edge>().ToList(); // 初始化每个节点的邻接列表 foreach (var node in nodes) { graph[node] = new List<(Node, int)>(); } // 填充邻接关系(如果是无向图,要添加双向边) foreach (var edge in edges) { graph[edge.StartNode].Add((edge.EndNode, edge.Weight)); graph[edge.EndNode].Add((edge.StartNode, edge.Weight)); } return graph; }
四、Dijkstra算法的实现
有了图结构,算法部分就和常规实现一致了,只是用Node作为节点类型:
public Dictionary<Node, int> RunDijkstra(Node startNode) { var graph = BuildGraph(); var distances = new Dictionary<Node, int>(); // 用优先队列存储(节点,当前距离),按距离排序 var priorityQueue = new PriorityQueue<(Node, int), int>(); // 初始化所有节点的距离为无穷大 foreach (var node in graph.Keys) { distances[node] = int.MaxValue; } distances[startNode] = 0; priorityQueue.Enqueue((startNode, 0), 0); while (priorityQueue.Count > 0) { var (currentNode, currentDistance) = priorityQueue.Dequeue(); // 如果当前记录的距离已经比队列里的小,直接跳过 if (currentDistance > distances[currentNode]) continue; // 遍历邻接节点,更新最短距离 foreach (var (neighbor, weight) in graph[currentNode]) { int newDistance = currentDistance + weight; if (newDistance < distances[neighbor]) { distances[neighbor] = newDistance; priorityQueue.Enqueue((neighbor, newDistance), newDistance); } } } return distances; }
内容的提问来源于stack exchange,提问作者doggoz
相关产品推荐
相关产品推荐

