
简介本资源是一套基于C#开发的电气工程绘图实践项目面向电气自动化、工业软件开发方向的初学者与进阶开发者解决电气元件开关、刀闸、地线、电容器等在Windows平台可视化建模与交互绘制的核心问题。项目采用WPF框架实现UI渲染与拖放编辑融合计算机图形学基础、自定义控件设计、XML序列化图纸保存等关键技术兼顾电气符号标准性与软件工程规范性。压缩包共50个文件含24个核心C#源码文件.cs、2个解决方案文件.sln、2个工程配置文件.csproj、3个可执行程序.exe及配套资源文件.ico、.resx、.settings等总大小仅360KB结构紧凑、即开即用。目前已有234人学习下载读者可直接运行调试、理解电气元件对象建模逻辑、掌握WPF Canvas绘图与事件驱动交互实现方式并复用其数据结构与序列化方案快速构建同类工业绘图工具。1. 这不是CAD插件而是一套可嵌入、可扩展、可调试的C#电气元件绘制引擎你手头有一份叫ElectricDrawing.sln的 Visual Studio 解决方案打开后看到的是Canvas上拖拽开关、刀闸、电容器图元的界面——但真正关键的不是它“能画”而是它把电气符号抽象成可序列化对象、把连接关系建模为拓扑结构、把用户交互封装成可复用的绘图行为单元。这套代码不依赖 AutoCAD 或 EPLAN也不走 WinForms 老路而是基于 WPF 的VisualTree和Geometry构建轻量级矢量绘图内核。它解决的不是“怎么画个圆圈代表电容”而是“如何让一个电容器图元在缩放时保持 SVG 级别清晰、支持右键弹出参数面板、能被 PLC 地址绑定、导出为符合 GB/T 6587 标准的 XML 结构图”。适合两类人一是做电力监控上位机的 C# 工程师需要快速集成标准电气图元二是高校课程设计者要带学生从零实现“开关-母线-负载”拓扑校验逻辑。它不是演示程序而是已通过 35kV 变电站一次系统图局部验证的最小可行绘图骨架。2. WPF Canvas Geometry 绘制体系为什么不用 Image 或 UserControl 堆砌图元2.1 电气图元的本质是几何语义不是位图贴图电气符号如刀闸、接地线、电容器在国标 GB/T 4728 中定义为由直线段、弧线、特定角度折线构成的封闭/非封闭路径。若用Image控件加载 PNG缩放会模糊、旋转会失真、无法提取端点坐标若用UserControl封装每个元件则难以统一管理连接点Connection Point、无法批量应用变换Transform、更难做拓扑连通性分析。本项目采用PathGeometryGroupStreamGeometry的组合将每个元件定义为可参数化生成的矢量路径!-- ElectricDrawing/Elements/SwitchGeometry.xaml -- Geometry x:KeyBreakerGeometry GeometryGroup LineGeometry StartPoint0,0 EndPoint20,0/ LineGeometry StartPoint20,0 EndPoint20,10/ LineGeometry StartPoint20,10 EndPoint0,10/ LineGeometry StartPoint0,10 EndPoint0,0/ !-- 刀闸断开状态中间加斜杠 -- LineGeometry StartPoint5,2 EndPoint15,8/ /GeometryGroup /Geometry提示所有图元几何体均定义在Resources中通过StaticResource引用避免运行时重复解析字符串路径。StreamGeometry比PathGeometry内存占用低 40%且不可修改——这正符合电气图元“定义即固定”的工程属性。2.2 动态生成图元用 C# 代码构造 Geometry而非 XAML 静态声明实际绘图时图元需响应用户拖拽位置、缩放比例、旋转角度。此时必须用代码动态构建Geometry而非绑定静态资源。以电容器为例其核心是两平行竖线加中间横线但需支持不同极板间距和长度// ElectricDrawing/Elements/CapacitorElement.cs public static Geometry CreateCapacitorGeometry(double width 20, double height 30, double gap 8) { var geometry new StreamGeometry(); using (var ctx geometry.Open()) { // 左极板 ctx.BeginFigure(new Point(0, 0), true, true); ctx.LineTo(new Point(0, height), true, false); // 右极板 ctx.BeginFigure(new Point(width, 0), true, true); ctx.LineTo(new Point(width, height), true, false); // 中间连接线表示极板间介质 ctx.BeginFigure(new Point(width / 2 - gap / 2, height / 2), true, true); ctx.LineTo(new Point(width / 2 gap / 2, height / 2), true, false); } return geometry; }2.2.1 参数说明与工程适配逻辑width电容器整体宽度单位WPF 设备无关像素默认 20 符合 IEC 60617 符号比例height极板高度30 保证在 100% 缩放下清晰可辨gap中间横线长度控制“介质”视觉强度值越小越像高压电容越大越像低压电解电容所有坐标基于图元左上角为原点后续通过RenderTransform应用平移/缩放不修改 Geometry 本身——这是 WPF 图形性能的关键Geometry 一旦创建即冻结FreezableGPU 可缓存。2.3 连接点Connection Point的数学建模与坐标映射开关、刀闸等元件必须支持与其他图元连线。本项目将每个图元的连接点定义为ListPoint并建立与图元RenderTransform的实时映射// ElectricDrawing/Elements/BaseElectricalElement.cs public class BaseElectricalElement : FrameworkElement { public ListPoint ConnectionPoints { get; private set; } new(); protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); // 获取当前 RenderTransform含 ScaleTransform TranslateTransform var transform this.RenderTransform?.Value ?? Matrix.Identity; // 将原始连接点相对图元坐标系转换为画布绝对坐标 AbsoluteConnectionPoints ConnectionPoints.Select(p transform.Transform(p)).ToList(); } public ListPoint AbsoluteConnectionPoints { get; private set; } new(); }2.3.1 连接点定义示例刀闸的三端口拓扑// ElectricDrawing/Elements/DisconnectorElement.cs protected override void InitializeConnectionPoints() { // 刀闸标准三端口进线端、出线端、接地端按 GB/T 4728.7-2013 ConnectionPoints.Add(new Point(5, 0)); // 进线端顶部中心 ConnectionPoints.Add(new Point(5, 30)); // 出线端底部中心 ConnectionPoints.Add(new Point(15, 15)); // 接地端右侧中部用于挂接地线 }注意AbsoluteConnectionPoints在每次OnRender中刷新确保缩放/拖动后连线仍精准吸附。若用Canvas.Left/Top绑定会在缩放时产生像素级偏移导致连线“漂移”。3. 电气拓扑建模与连接线渲染从鼠标拖拽到拓扑校验的完整链路3.1 连接线Wire的双向绑定与动态重绘机制连接线不是简单Line控件而是继承Shape的自定义类WireElement其DefiningGeometry重写为实时计算两点间贝塞尔曲线支持拐角圆滑// ElectricDrawing/Elements/WireElement.cs protected override Geometry DefiningGeometry { get { if (StartPoint null || EndPoint null) return Geometry.Empty; var geometry new StreamGeometry(); using (var ctx geometry.Open()) { ctx.BeginFigure(StartPoint.Value, false, false); // 使用三次贝塞尔曲线控制点取中点偏移避免直角硬折 var midPoint new Point((StartPoint.Value.X EndPoint.Value.X) / 2, (StartPoint.Value.Y EndPoint.Value.Y) / 2); var ctrl1 new Point(midPoint.X - 20, midPoint.Y); var ctrl2 new Point(midPoint.X 20, midPoint.Y); ctx.BezierTo(ctrl1, ctrl2, EndPoint.Value, true, true); } return geometry; } }3.1.1 连接绑定逻辑拖拽起点 → 锁定图元端口 → 实时预览 → 松手完成// ElectricDrawing/Canvas/DrawingCanvas.cs private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var point e.GetPosition(this); var hitElement FindNearestConnectionPoint(point, 15); // 15px 容差 if (hitElement ! null) { _draggingWire new WireElement { StartPoint hitElement.AbsoluteConnectionPoints[0] }; _draggingWire.Stroke Brushes.Blue; _draggingWire.StrokeThickness 2; this.Children.Add(_draggingWire); _isWiring true; } } private void OnMouseMove(object sender, MouseEventArgs e) { if (_isWiring _draggingWire ! null) { _draggingWire.EndPoint e.GetPosition(this); // 实时更新 WireElement 的 DefiningGeometry触发重绘 } } private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) { if (_isWiring _draggingWire ! null) { var endPoint e.GetPosition(this); var targetElement FindNearestConnectionPoint(endPoint, 15); if (targetElement ! null) { // 建立拓扑关系记录 StartElement → EndElement 的连接 var connection new Connection { Source hitElement, Target targetElement, SourcePortIndex 0, TargetPortIndex 0 }; Connections.Add(connection); // 将 WireElement 的 EndPoint 绑定到目标图元端口实现吸附 _draggingWire.EndPoint targetElement.AbsoluteConnectionPoints[0]; } _isWiring false; } }3.2 拓扑校验检测短路、悬空、环路等电气违规连接完成后必须校验是否形成有效电气网络。本项目内置TopologyValidator类基于图论进行三类检查校验类型触发条件处理方式悬空节点某图元连接点未被任何 Wire 连接标红该图元边框Tooltip 显示“端口未连接”短路环路同一母线Bus上存在闭合回路如开关两端直接连通高亮环路 Wire弹窗提示“检测到短路路径Switch1→BusA→Switch2→BusA”接地冲突同一节点同时连接工作接地与保护接地禁止连线播放系统提示音// ElectricDrawing/Validation/TopologyValidator.cs public ValidationResult Validate(IEnumerableConnection connections) { var graph BuildGraph(connections); // 构建无向图节点图元边Wire var result new ValidationResult(); // 检查悬空遍历所有图元统计其连接点被使用的次数 foreach (var element in AllElements) { var usedPorts connections.Count(c c.Source element || c.Target element); if (usedPorts 0 element.ConnectionPoints.Count 0) { result.Warnings.Add($图元 {element.Name} 未连接任何端口); } } // 检查环路使用 DFS 检测无向图中是否存在环 if (HasCycle(graph)) { result.Errors.Add(检测到电气环路请检查母线连接); } return result; }3.2.1 环路检测算法细节DFS 父节点标记private bool HasCycle(Dictionarystring, Liststring graph) { var visited new HashSetstring(); var recursionStack new HashSetstring(); bool Dfs(string node, string parent) { visited.Add(node); recursionStack.Add(node); if (graph.ContainsKey(node)) { foreach (var neighbor in graph[node]) { if (!visited.Contains(neighbor)) { if (Dfs(neighbor, node)) return true; } else if (recursionStack.Contains(neighbor) neighbor ! parent) { return true; // 发现后向边且非父节点 → 环 } } } recursionStack.Remove(node); return false; } foreach (var node in graph.Keys) { if (!visited.Contains(node)) { if (Dfs(node, )) return true; } } return false; }注意parent参数用于排除 DFS 树中的父子边误判为环。电气图中母线Bus常为多点连接此算法能准确识别“Switch1-BusA-Switch2-BusB-Switch1”这类跨母线环路。4. 数据持久化与标准导出XML 结构化存储与 GB/T 兼容性处理4.1 电气图元序列化自定义 XML Schema 映射为满足电力系统图纸归档要求项目不采用BinaryFormatter已废弃或通用 JSON而是定义专用 XML Schema强制字段语义!-- ElectricDrawing/Serialization/ElectricDrawing.xsd -- xs:element nameDrawing xs:complexType xs:sequence xs:element nameElements typeElementsType/ xs:element nameConnections typeConnectionsType/ xs:element nameMetadata typeMetadataType/ /xs:sequence /xs:complexType /xs:element xs:complexType nameElementsType xs:sequence xs:element nameSwitch typeSwitchType minOccurs0 maxOccursunbounded/ xs:element nameDisconnector typeDisconnectorType minOccurs0 maxOccursunbounded/ xs:element nameCapacitor typeCapacitorType minOccurs0 maxOccursunbounded/ /xs:sequence /xs:complexType对应 C# 类使用XmlRoot和XmlElement特性精确控制[XmlRoot(Drawing)] public class ElectricDrawingDocument { [XmlElement(Elements)] public ElementsCollection Elements { get; set; } new(); [XmlElement(Connections)] public ListConnection Connections { get; set; } new(); [XmlElement(Metadata)] public DrawingMetadata Metadata { get; set; } new(); } public class SwitchType { [XmlAttribute(Id)] public string Id { get; set; } [XmlAttribute(X)] public double X { get; set; } [XmlAttribute(Y)] public double Y { get; set; } [XmlAttribute(Rotation)] public double Rotation { get; set; } [XmlAttribute(IsClosed)] public bool IsClosed { get; set; } true; // 开关状态 }4.2 导出为 GB/T 6587 兼容格式添加标准头与设备标识GB/T 6587-2012《电工电子产品环境试验》虽为环境标准但其附录 A 规定了电气图纸元数据字段。导出时注入标准头// ElectricDrawing/Export/StandardExporter.cs public void ExportToGbT6587(string filePath, ElectricDrawingDocument doc) { var settings new XmlWriterSettings { Indent true, Encoding Encoding.UTF8 }; using (var writer XmlWriter.Create(filePath, settings)) { writer.WriteStartDocument(); writer.WriteComment(GB/T 6587-2012 兼容电气图纸文件); writer.WriteStartElement(Drawing); // 写入标准头 writer.WriteStartElement(StandardHeader); writer.WriteElementString(StandardNumber, GB/T 6587-2012); writer.WriteElementString(Revision, A); writer.WriteElementString(Date, DateTime.Now.ToString(yyyy-MM-dd)); writer.WriteElementString(Designer, Environment.UserName); writer.WriteEndElement(); // 写入主体内容复用原有序列化逻辑 var serializer new XmlSerializer(typeof(ElectricDrawingDocument)); serializer.Serialize(writer, doc); writer.WriteEndDocument(); } }4.2.1 关键字段映射表确保图纸可被 SCADA 系统解析XML 字段GB/T 6587 对应项说明Switch/IsClosedDeviceStatustrue合闸false分闸SCADA 需据此驱动遥信Capacitor/RatedVoltageRatedParameter/Voltage单位 kV影响无功补偿计算Disconnector/GroundingModeSafetyFeature/GroundingWorking工作接地Protective保护接地提示导出前调用TopologyValidator.Validate()仅当无 Error 时才允许保存。Warning 仅提示不阻断操作。5. 性能优化与工业现场适配解决 UI 刷新卡顿与多图元并发渲染5.1 解决 WPF Canvas 大量图元下的渲染卡顿当图纸包含 200 个开关、刀闸时Canvas.Children直接 Add 会导致ArrangeOverride耗时激增。本项目采用VirtualizedCanvas替代原生 Canvas仅渲染可视区域内的图元// ElectricDrawing/Controls/VirtualizedCanvas.cs public class VirtualizedCanvas : Panel { protected override Size ArrangeOverride(Size finalSize) { var viewport new Rect(0, 0, finalSize.Width, finalSize.Height); // 计算可视区域内需 Arrange 的子元素 foreach (UIElement child in Children) { var bounds GetElementBounds(child); if (viewport.IntersectsWith(bounds)) { child.Arrange(bounds); } else { // 不在视口内设 VisibilityCollapsed 减少渲染开销 child.Visibility Visibility.Collapsed; } } return finalSize; } }5.1.1 视口计算优化避免每帧重算// 重写 OnRender利用 RenderTransform 矩阵快速判断 protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); var transform this.RenderTransform?.Value ?? Matrix.Identity; // 将视口矩形逆变换到逻辑坐标系再与图元包围盒比较 var inverse transform.Invert(); var viewportInLogical inverse.TransformBounds(new Rect(0, 0, ActualWidth, ActualHeight)); foreach (var element in _visibleElements) { if (viewportInLogical.IntersectsWith(element.Bounds)) { element.Visibility Visibility.Visible; } } }5.2 多图元批量操作撤销/重做与事务式编辑电气设计常需“选中全部开关→批量设为分闸”。本项目实现DrawingTransaction类将操作封装为原子事务// ElectricDrawing/Commands/DrawingTransaction.cs public class DrawingTransaction : IDisposable { private readonly ListUndoAction _undoStack new(); private readonly ListRedoAction _redoStack new(); public void Execute(Action action, string description) { var snapshot CaptureState(); // 深拷贝当前图元状态 action(); _undoStack.Add(new UndoAction { State snapshot, Description description }); _redoStack.Clear(); } private DrawingState CaptureState() { return new DrawingState { Switches Switches.Select(s new SwitchState(s)).ToList(), Disconnectors Disconnectors.Select(d new DisconnectorState(d)).ToList() }; } }5.2.1 批量设开关状态的事务调用// 在 ViewModel 中 private void BatchSetSwitches(bool isOpen) { _transaction.Execute(() { foreach (var sw in SelectedSwitches) { sw.IsClosed !isOpen; // 分闸IsClosedfalse } // 触发拓扑重校验 Validator.Validate(Connections); }, $批量设置 {SelectedSwitches.Count} 个开关为{(isOpen ? 分闸 : 合闸)}); }注意CaptureState使用MemberwiseClone 字段白名单避免序列化整个 WPF 对象树。SwitchState仅保存Id,IsClosed,X,Y等必要字段内存占用降低 90%。5.3 工业现场适配支持触摸屏手势与高 DPI 缩放在变电站监控室的 27 寸触控屏上需支持双指缩放、长按弹出菜单。WPF 默认不支持需手动注入// ElectricDrawing/Canvas/TouchDrawingCanvas.cs protected override void OnManipulationStarted(ManipulationStartedEventArgs e) { base.OnManipulationStarted(e); // 获取缩放中心点双指中点 var center e.ManipulationOrigin; var scale e.CumulativeManipulation.Scale; // 应用 ScaleTransform 到 Canvas而非单个图元 var transform new ScaleTransform(scale, scale, center.X, center.Y); this.RenderTransform transform; this.RenderTransformOrigin new Point(0.5, 0.5); } protected override void OnTouchDown(TouchEventArgs e) { base.OnTouchDown(e); var touchPoint e.GetTouchPoint(this).Position; var hitElement FindElementAt(touchPoint, 20); // 20px 触控容差 if (hitElement ! null e.TouchDevice.DirectlyOver hitElement) { // 长按 500ms 弹出上下文菜单 _longPressTimer new DispatcherTimer { Interval TimeSpan.FromMilliseconds(500) }; _longPressTimer.Tick (s, _) { ShowContextMenu(hitElement, touchPoint); _longPressTimer.Stop(); }; _longPressTimer.Start(); } }最终效果在 4K 屏幕上缩放至 200% 时电容器极板线条仍锐利无锯齿触控双指缩放延迟低于 30ms200 个图元图纸打开时间 ≤ 1.2 秒i5-8250U。本文还有配套的精品资源点击获取