WinForms中基于组件模型类型匹配视图绘图逻辑的MVC/MVVM实现方案咨询
穿孔板布局程序:M-V分离下的组件绘图解决方案
你的核心需求很明确:既要严格遵循模型-视图分离(保证模型可二进制序列化),又要避免类型检查、字符串/枚举标记,用纯面向对象的方式实现视图根据模型类型选择绘图逻辑。下面提供两种最适合的方案,结合你的代码场景展开说明:
方案1:访客模式(Visitor Pattern)—— 最纯粹的面向对象实现
访客模式完美解决了"在不修改模型的前提下,为不同类型模型添加操作"的问题,完全符合你的M-V分离要求,且彻底避免类型检查。
步骤1:重构模型层(完全独立,无视图依赖)
模型只保留业务数据,通过IComponent接口定义接受访客的方法,具体组件实现该接口:
using System.Drawing; // 仅用基础数据类型,无WinForms绘图依赖 [Serializable] // 标记为可二进制序列化 public interface IComponent { Point Position { get; set; } // 模型数据:组件位置 void Accept(IComponentVisitor visitor); // 接受访客的方法 } [Serializable] public class Wire : IComponent { public Point Position { get; set; } public int Length { get; set; } // 模型数据:导线长度 public void Accept(IComponentVisitor visitor) { visitor.Visit(this); // 引导访客处理当前导线 } } [Serializable] public class Transistor : IComponent { public Point Position { get; set; } public enum PinType { EBC, BCE } public PinType PinConfiguration { get; set; } // 模型数据:引脚配置 public void Accept(IComponentVisitor visitor) { visitor.Visit(this); // 引导访客处理当前晶体管 } } // 访客接口:定义针对每个组件类型的操作 public interface IComponentVisitor { void Visit(Wire wire); void Visit(Transistor transistor); // 新增组件时,只需在这里添加对应的Visit方法 } [Serializable] public class Perfboard { public List<IComponent> Components { get; } = new List<IComponent>(); public int Width { get; set; } public int Height { get; set; } public Perfboard() { } public Perfboard(int width, int height) { Width = width; Height = height; } }
步骤2:实现视图层的绘图访客
绘图逻辑全部封装在访客类中,模型完全不知道视图的存在:
using System.Drawing; // 绘图访客:实现IComponentVisitor,处理每个组件的绘制逻辑 public class ComponentDrawingVisitor : IComponentVisitor { private readonly Graphics _graphics; private readonly float _scale = 25.4f; // 单位转换:毫米→像素 public ComponentDrawingVisitor(Graphics graphics) { _graphics = graphics; } public void Visit(Wire wire) { // 用模型数据绘制导线 var start = new PointF(wire.Position.X * _scale, wire.Position.Y * _scale); var end = new PointF((wire.Position.X + wire.Length) * _scale, wire.Position.Y * _scale); _graphics.DrawLine(Pens.Black, start, end); } public void Visit(Transistor transistor) { // 用模型数据绘制晶体管 var bounds = new RectangleF( transistor.Position.X * _scale, transistor.Position.Y * _scale, 3 * _scale, 5 * _scale ); _graphics.DrawRectangle(Pens.DarkRed, bounds); // 根据引脚配置绘制引脚细节 if (transistor.PinConfiguration == Transistor.PinType.EBC) { // 绘制EBC引脚布局 } } } // 穿孔板视图类 public class PerfboardView { public Perfboard Board { get; } public BoardCursor Cursor { get; } = new BoardCursor(); public PerfboardView(Perfboard board) { Board = board; } public void Draw(Graphics g) { g.TranslateTransform(Cursor.X * -25.4f, Cursor.Y * -25.4f); DrawHoles(g); // 创建绘图访客,遍历所有组件执行绘制 var drawingVisitor = new ComponentDrawingVisitor(g); foreach (var component in Board.Components) { component.Accept(drawingVisitor); } Cursor.Draw(g); } private void DrawHoles(Graphics g) { // 绘制穿孔板孔位的逻辑 for (int x = 0; x < Board.Width; x++) { for (int y = 0; y < Board.Height; y++) { var holePos = new PointF(x * _scale, y * _scale); g.FillEllipse(Brushes.Gray, holePos.X - 2, holePos.Y - 2, 4, 4); } } } }
这个方案的优势:
- 绝对的M-V分离,模型无任何视图依赖,二进制序列化完全不受影响
- 彻底避免类型检查,每个组件的绘图逻辑对应专属的
Visit方法 - 新增组件时只需添加
IComponent实现和对应的Visit方法,符合开闭原则
方案2:策略模式+类型匹配服务—— 无需修改现有模型
如果不想给模型添加Accept方法,可以用策略模式,为每个组件类型绑定一个绘图策略,通过类型匹配自动获取:
步骤1:模型层(无需修改,保持原有结构)
[Serializable] public interface IComponent { Point Position { get; set; } } [Serializable] public class Wire : IComponent { /* 模型数据 */ } [Serializable] public class Transistor : IComponent { /* 模型数据 */ } [Serializable] public class Perfboard { /* 原有代码 */ }
步骤2:视图层的绘图策略与服务
using System.Drawing; using System.Collections.Generic; // 绘图策略接口 public interface IComponentDrawer { void Draw(IComponent component, Graphics g, float scale); } // 导线绘图策略 public class WireDrawer : IComponentDrawer { public void Draw(IComponent component, Graphics g, float scale) { var wire = (Wire)component; // 类型转换安全,因为按类型注册策略 // 绘制逻辑同访客模式 } } // 晶体管绘图策略 public class TransistorDrawer : IComponentDrawer { public void Draw(IComponent component, Graphics g, float scale) { var transistor = (Transistor)component; // 绘制逻辑同访客模式 } } // 策略注册与获取服务 public static class ComponentDrawerService { private static readonly Dictionary<Type, IComponentDrawer> _drawers = new(); static ComponentDrawerService() { // 注册所有组件的绘图策略 RegisterDrawer<Wire>(new WireDrawer()); RegisterDrawer<Transistor>(new TransistorDrawer()); } public static void RegisterDrawer<T>(IComponentDrawer drawer) where T : IComponent { _drawers[typeof(T)] = drawer; } public static IComponentDrawer GetDrawer(IComponent component) { if (_drawers.TryGetValue(component.GetType(), out var drawer)) return drawer; throw new InvalidOperationException($"未找到{component.GetType().Name}的绘图策略"); } } // 修改PerfboardView的Draw方法 public void Draw(Graphics g) { g.TranslateTransform(Cursor.X * -25.4f, Cursor.Y * -25.4f); DrawHoles(g); float scale = 25.4f; foreach (var component in Board.Components) { var drawer = ComponentDrawerService.GetDrawer(component); drawer.Draw(component, g, scale); } Cursor.Draw(g); }
这个方案的优势:
- 模型完全无需改动,适合已有模型代码的场景
- 绘图策略可独立维护,新增组件只需添加策略并注册
- 避免显式类型检查,类型匹配由服务层处理
二进制序列化注意事项
确保所有模型类标记[Serializable],且成员均为可序列化类型(如Point、基础类型、自定义枚举)。如果有自定义构造函数,需添加无参构造函数供序列化使用。
内容的提问来源于stack exchange,提问作者AntonJH
相关产品推荐
相关产品推荐

