第四章:插件开发基础
4.1 LightCAD插件体系
4.1.1 插件概述
LightCAD的插件系统允许开发者扩展平台功能,插件可以:
- 添加新的元素类型
- 注册新的命令
- 扩展用户界面
- 自定义绘图和编辑行为
- 集成外部系统
4.1.2 插件加载机制
LightCAD启动│▼
扫描插件目录│▼
加载程序集(DLL)│▼
反射查找ILcPlugin实现│▼
创建插件实例│▼
依次调用生命周期方法
4.1.3 插件接口定义
public interface ILcPlugin
{/// <summary>/// 插件加载时调用/// </summary>void Loaded();/// <summary>/// UI初始化时调用/// </summary>void InitUI();/// <summary>/// 完成所有初始化后调用/// </summary>void Completed();/// <summary>/// 文档运行时初始化时调用/// </summary>void OnInitializeDocRt(DocumentRuntime docRt);/// <summary>/// 文档运行时销毁时调用/// </summary>void OnDisposeDocRt(DocumentRuntime docRt);
}
4.2 创建基础插件
4.2.1 最小插件示例
using LightCAD.Core;
using LightCAD.Runtime;namespace MyFirstPlugin
{public class MyFirstPlugin : ILcPlugin{public void Loaded(){Console.WriteLine("MyFirstPlugin: Loaded");}public void InitUI(){Console.WriteLine("MyFirstPlugin: InitUI");}public void Completed(){Console.WriteLine("MyFirstPlugin: Completed");}public void OnInitializeDocRt(DocumentRuntime docRt){Console.WriteLine($"MyFirstPlugin: Document initialized - {docRt.Document.Name}");}public void OnDisposeDocRt(DocumentRuntime docRt){Console.WriteLine($"MyFirstPlugin: Document disposed - {docRt.Document.Name}");}}
}
4.2.2 项目配置
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net10.0-windows</TargetFramework><UseWindowsForms>true</UseWindowsForms><ImplicitUsings>enable</ImplicitUsings><Nullable>enable</Nullable></PropertyGroup><ItemGroup><Reference Include="LightCAD.Core"><HintPath>..\Libs\LightCAD.Core.dll</HintPath></Reference><Reference Include="LightCAD.Runtime"><HintPath>..\Libs\LightCAD.Runtime.dll</HintPath></Reference></ItemGroup>
</Project>
4.3 UI集成
4.3.1 TabItem结构
LightCAD使用Ribbon风格的选项卡界面,主要结构如下:
TabItem (选项卡)
├── Name # 内部名称
├── Text # 显示文本
├── ShortcutKey # 快捷键
└── ButtonGroups # 按钮组列表└── TabButtonGroup└── Buttons # 按钮列表└── TabButton├── Name # 命令名称├── Text # 显示文本├── Icon # 图标├── IsCommand # 是否为命令├── Width # 宽度└── DropDowns # 下拉菜单
4.3.2 创建选项卡
public static TabItem MyTabItem = new TabItem
{Name = "MyTab",Text = "我的工具",ShortcutKey = "ALT-M",ButtonGroups = new List<TabButtonGroup>{new TabButtonGroup{Buttons = new List<TabButton>{new TabButton{Name = "MyCommand1",Text = "功能一",Icon = LoadIcon("icon1.png"),IsCommand = true,},new TabButton{Name = "MyCommand2",Text = "功能二",Icon = LoadIcon("icon2.png"),IsCommand = true,Width = 80,}}}}
};// 在Completed()中注册
public void Completed()
{AppRuntime.UISystem.AddInitTabItems([MyTabItem]);
}
4.3.3 带下拉菜单的按钮
new TabButton
{Name = "DrawTools",Text = "绘图工具",Icon = Properties.Resources.DrawIcon,IsCommand = true,DropDowns = new List<TabButton>{new TabButton{Name = "DrawLine",Text = "画线",Icon = Properties.Resources.LineIcon,IsCommand = true,},new TabButton{Name = "DrawCircle",Text = "画圆",Icon = Properties.Resources.CircleIcon,IsCommand = true,},new TabButton{Name = "DrawRect",Text = "画矩形",Icon = Properties.Resources.RectIcon,IsCommand = true,},}
}
4.3.4 图标资源管理
使用Resources.resx管理图标:
- 在项目中创建
Properties/Resources.resx - 添加图像资源
- 在代码中引用:
// 自动生成的访问器
Icon = Properties.Resources.MyIcon
或者从文件加载:
private static System.Drawing.Image LoadIcon(string path)
{var assembly = Assembly.GetExecutingAssembly();using var stream = assembly.GetManifestResourceStream($"MyPlugin.Resources.{path}");return System.Drawing.Image.FromStream(stream);
}
4.4 命令开发
4.4.1 命令类定义
using LightCAD.Runtime;namespace MyPlugin
{[CommandClass]public class MyCommands{// 命令方法将在这里定义}
}
4.4.2 基本命令
[CommandMethod(Name = "Hello", ShortCuts = "HI")]
public CommandResult HelloWorld(IDocumentEditor docEditor, string[] args)
{// 获取命令控制器var cmdCtrl = docEditor.GetCommandController();// 输出消息cmdCtrl.WriteInfo("Hello, LightCAD World!");return CommandResult.Succ();
}
4.4.3 带参数的命令
[CommandMethod(Name = "SetColor", ShortCuts = "SC")]
public CommandResult SetColor(IDocumentEditor docEditor, string[] args)
{if (args.Length < 1){docEditor.GetCommandController().WriteError("请指定颜色参数");return CommandResult.Fail("缺少参数");}var colorName = args[0];// 处理颜色设置...return CommandResult.Succ();
}
4.4.4 异步命令
[CommandMethod(Name = "LongTask", ShortCuts = "LT")]
public CommandResult LongTask(IDocumentEditor docEditor, string[] args)
{Task.Run(async () =>{var cmdCtrl = docEditor.GetCommandController();cmdCtrl.WriteInfo("开始长时间任务...");await Task.Delay(3000); // 模拟耗时操作cmdCtrl.WriteInfo("任务完成!");});return CommandResult.Succ();
}
4.4.5 CommandResult详解
// 成功结果
CommandResult.Succ()// 失败结果
CommandResult.Fail("错误信息")// 带数据的结果
new CommandResult
{Status = CommandStatus.Success,Data = someData
}
4.5 文档操作
4.5.1 获取当前文档
[CommandMethod(Name = "DocInfo")]
public CommandResult GetDocInfo(IDocumentEditor docEditor, string[] args)
{var docRt = docEditor.GetDocumentRuntime();var doc = docRt.Document;var cmdCtrl = docEditor.GetCommandController();cmdCtrl.WriteInfo($"文档名称: {doc.Name}");cmdCtrl.WriteInfo($"元素数量: {doc.Elements.Count}");cmdCtrl.WriteInfo($"图层数量: {doc.Layers.Count}");return CommandResult.Succ();
}
4.5.2 遍历元素
[CommandMethod(Name = "ListElements")]
public CommandResult ListElements(IDocumentEditor docEditor, string[] args)
{var doc = docEditor.GetDocumentRuntime().Document;var cmdCtrl = docEditor.GetCommandController();foreach (var element in doc.Elements){cmdCtrl.WriteInfo($"元素: {element.Type.Name} - {element.Id}");}return CommandResult.Succ();
}
4.5.3 图层操作
// 获取或创建图层
private LcLayer GetOrCreateLayer(LcDocument doc, string layerName, int color)
{var layer = doc.Layers.FirstOrDefault(l => l.Name == layerName);if (layer == null){layer = doc.CreateObject<LcLayer>();layer.Name = layerName;layer.Color = color;layer.SetLineType(new LcLineType("ByLayer"));layer.Transparency = 0;doc.Layers.Add(layer);}return layer;
}// 使用示例
var layer = GetOrCreateLayer(doc, "MyLayer", 0xFF0000); // 红色图层
element.Layer = layer.Name;
4.6 用户输入
4.6.1 PointInputer - 点输入
[CommandMethod(Name = "GetPoint")]
public async CommandResult GetPoint(IDocumentEditor docEditor, string[] args)
{var pointInputer = new PointInputer(docEditor);// 获取单个点var result = await pointInputer.Execute("请指定点:");if (result.Status == InputStatus.OK){var point = result.Point;docEditor.GetCommandController().WriteInfo($"点坐标: ({point.X}, {point.Y})");}else if (result.Status == InputStatus.Cancel){docEditor.GetCommandController().WriteInfo("操作已取消");}return CommandResult.Succ();
}
4.6.2 获取多个点
[CommandMethod(Name = "GetPoints")]
public async CommandResult GetPoints(IDocumentEditor docEditor, string[] args)
{var pointInputer = new PointInputer(docEditor);var points = new List<Vector2>();var cmdCtrl = docEditor.GetCommandController();cmdCtrl.WriteInfo("指定第一个点:");var result = await pointInputer.Execute();while (result.Status == InputStatus.OK){points.Add(result.Point);cmdCtrl.WriteInfo($"已添加点 {points.Count}: ({result.Point.X}, {result.Point.Y})");result = await pointInputer.Execute("指定下一点或按ESC完成:");}cmdCtrl.WriteInfo($"共获取 {points.Count} 个点");return CommandResult.Succ();
}
4.6.3 CmdTextInputer - 文本输入
[CommandMethod(Name = "GetText")]
public async CommandResult GetText(IDocumentEditor docEditor, string[] args)
{var textInputer = new CmdTextInputer(docEditor);var result = await textInputer.Execute("请输入文本:");if (!string.IsNullOrEmpty(result)){docEditor.GetCommandController().WriteInfo($"输入的文本: {result}");}return CommandResult.Succ();
}
4.6.4 ElementSetInputer - 元素选择
[CommandMethod(Name = "SelectElements")]
public async CommandResult SelectElements(IDocumentEditor docEditor, string[] args)
{var elementInputer = new ElementSetInputer(docEditor);var result = await elementInputer.Execute("请选择元素:");if (result?.ValueX != null){var elements = result.ValueX as List<LcElement>;var cmdCtrl = docEditor.GetCommandController();cmdCtrl.WriteInfo($"选择了 {elements.Count} 个元素");foreach (var ele in elements){cmdCtrl.WriteInfo($" - {ele.Type.DispalyName}");}}return CommandResult.Succ();
}
4.7 元素创建
4.7.1 创建基本元素
// 创建直线
var line = doc.CreateObject<LcLine>();
line.Start = new Vector2(0, 0);
line.End = new Vector2(100, 100);
line.Layer = "0";
vportRt.ActiveElementSet.InsertElement(line);// 创建多段线
var polyline = doc.CreateObject<LcPolyLine>();
polyline.AddVertex(new Vector2(0, 0));
polyline.AddVertex(new Vector2(100, 0));
polyline.AddVertex(new Vector2(100, 100));
polyline.IsClosed = true;
vportRt.ActiveElementSet.InsertElement(polyline);// 创建圆
var circle = doc.CreateObject<LcCircle>();
circle.Center = new Vector2(50, 50);
circle.Radius = 25;
vportRt.ActiveElementSet.InsertElement(circle);
4.7.2 创建自定义元素
[CommandMethod(Name = "CreateLawn")]
public CommandResult CreateLawn(IDocumentEditor docEditor, string[] args)
{var docRt = docEditor.GetDocumentRuntime();var doc = docRt.Document;var vportRt = docRt.ActiveViewportRuntime;// 获取组件定义var lawnDef = docRt.GetUseComDef("场布施工设计.绿色文明", "草坪", null) as QdLawnDef;// 创建草坪元素var lawn = new QdLawn(lawnDef);lawn.Initilize(doc);// 设置轮廓var poly = new Polyline2d();poly.AddCurve(new Line2d(new Vector2(0, 0), new Vector2(100, 0)));poly.AddCurve(new Line2d(new Vector2(100, 0), new Vector2(100, 100)));poly.AddCurve(new Line2d(new Vector2(100, 100), new Vector2(0, 100)));poly.AddCurve(new Line2d(new Vector2(0, 100), new Vector2(0, 0)));lawn.Outline = poly;// 设置其他属性lawn.ResetBoundingBox();lawn.Layer = "Layout_Lawn";lawn.Bottom = 0;// 插入到文档vportRt.ActiveElementSet.InsertElement(lawn);return CommandResult.Succ();
}
4.8 事件处理
4.8.1 文档事件
public void OnInitializeDocRt(DocumentRuntime docRt)
{var doc = docRt.Document;// 订阅元素添加事件doc.ElementAdded += (sender, e) =>{Console.WriteLine($"元素已添加: {e.Element.Type.Name}");};// 订阅元素删除事件doc.ElementRemoved += (sender, e) =>{Console.WriteLine($"元素已删除: {e.Element.Type.Name}");};// 订阅元素修改事件doc.ElementChanged += (sender, e) =>{Console.WriteLine($"元素已修改: {e.Element.Type.Name}");};
}
4.8.2 选择事件
public void OnInitializeDocRt(DocumentRuntime docRt)
{// 订阅选择变化事件docRt.SelectionChanged += (sender, e) =>{var selectedElements = docRt.GetSelectedElements();Console.WriteLine($"选择了 {selectedElements.Count} 个元素");};
}
4.9 配置持久化
4.9.1 使用Settings
// 创建Settings类
public class MyPluginSettings
{private static readonly string SettingsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),"LightCAD", "MyPlugin", "settings.json");public string DefaultLayer { get; set; } = "0";public int DefaultColor { get; set; } = 0xFFFFFF;public double DefaultLineWidth { get; set; } = 1.0;public static MyPluginSettings Load(){if (File.Exists(SettingsPath)){var json = File.ReadAllText(SettingsPath);return JsonConvert.DeserializeObject<MyPluginSettings>(json);}return new MyPluginSettings();}public void Save(){var dir = Path.GetDirectoryName(SettingsPath);if (!Directory.Exists(dir))Directory.CreateDirectory(dir);var json = JsonConvert.SerializeObject(this, Formatting.Indented);File.WriteAllText(SettingsPath, json);}
}
4.9.2 使用示例
public class MyPlugin : ILcPlugin
{private static MyPluginSettings Settings;public void Loaded(){Settings = MyPluginSettings.Load();}public void OnDisposeDocRt(DocumentRuntime docRt){Settings.Save();}
}
4.10 错误处理
4.10.1 命令异常处理
[CommandMethod(Name = "SafeCommand")]
public CommandResult SafeCommand(IDocumentEditor docEditor, string[] args)
{try{// 可能抛出异常的代码DoSomething();return CommandResult.Succ();}catch (ArgumentException ex){docEditor.GetCommandController().WriteError($"参数错误: {ex.Message}");return CommandResult.Fail(ex.Message);}catch (Exception ex){docEditor.GetCommandController().WriteError($"发生错误: {ex.Message}");// 记录详细日志LogError(ex);return CommandResult.Fail(ex.Message);}
}
4.10.2 输入验证
[CommandMethod(Name = "ValidatedInput")]
public async CommandResult ValidatedInput(IDocumentEditor docEditor, string[] args)
{var cmdCtrl = docEditor.GetCommandController();var pointInputer = new PointInputer(docEditor);// 获取第一个点var result1 = await pointInputer.Execute("指定第一个点:");if (result1.Status != InputStatus.OK){cmdCtrl.WriteInfo("操作已取消");return CommandResult.Succ();}// 获取第二个点,并验证不能与第一个点相同Vector2 point2;while (true){var result2 = await pointInputer.Execute("指定第二个点:");if (result2.Status != InputStatus.OK){cmdCtrl.WriteInfo("操作已取消");return CommandResult.Succ();}point2 = result2.Point;if (point2.DistanceTo(result1.Point) > 0.001)break;cmdCtrl.WriteError("第二个点不能与第一个点重合,请重新输入");}// 继续处理...return CommandResult.Succ();
}
4.11 完整插件示例
using System;
using System.Collections.Generic;
using LightCAD.Core;
using LightCAD.Runtime;
using LightCAD.Drawing;
using LightCAD.MathLib;namespace MyCompletePlugin
{/// <summary>/// 完整的插件示例/// </summary>public class MyCompletePlugin : ILcPlugin{public static TabItem MyTabItem = new TabItem{Name = "MyTools",Text = "我的工具",ShortcutKey = "ALT-Y",ButtonGroups = new List<TabButtonGroup>{new TabButtonGroup{Buttons = new List<TabButton>{new TabButton{Name = "DrawRectangle",Text = "绘制矩形",IsCommand = true,},new TabButton{Name = "ShowInfo",Text = "显示信息",IsCommand = true,}}}}};public void Loaded(){Console.WriteLine("MyCompletePlugin loaded");}public void InitUI(){// UI初始化}public void Completed(){AppRuntime.UISystem.AddInitTabItems([MyTabItem]);}public void OnInitializeDocRt(DocumentRuntime docRt){Console.WriteLine($"Document initialized: {docRt.Document.Name}");}public void OnDisposeDocRt(DocumentRuntime docRt){Console.WriteLine($"Document disposed: {docRt.Document.Name}");}}[CommandClass]public class MyCommands{[CommandMethod(Name = "DrawRectangle", ShortCuts = "DR")]public async CommandResult DrawRectangle(IDocumentEditor docEditor, string[] args){var cmdCtrl = docEditor.GetCommandController();var pointInputer = new PointInputer(docEditor);// 获取第一个角点cmdCtrl.WriteInfo("指定矩形第一个角点:");var result1 = await pointInputer.Execute();if (result1.Status != InputStatus.OK)return CommandResult.Succ();// 获取对角点cmdCtrl.WriteInfo("指定矩形对角点:");var result2 = await pointInputer.Execute();if (result2.Status != InputStatus.OK)return CommandResult.Succ();// 创建矩形var docRt = docEditor.GetDocumentRuntime();var doc = docRt.Document;var vportRt = docRt.ActiveViewportRuntime;var p1 = result1.Point;var p2 = result2.Point;var polyline = doc.CreateObject<LcPolyLine>();polyline.AddVertex(new Vector2(p1.X, p1.Y));polyline.AddVertex(new Vector2(p2.X, p1.Y));polyline.AddVertex(new Vector2(p2.X, p2.Y));polyline.AddVertex(new Vector2(p1.X, p2.Y));polyline.IsClosed = true;vportRt.ActiveElementSet.InsertElement(polyline);cmdCtrl.WriteInfo("矩形创建成功");return CommandResult.Succ();}[CommandMethod(Name = "ShowInfo", ShortCuts = "SI")]public CommandResult ShowInfo(IDocumentEditor docEditor, string[] args){var docRt = docEditor.GetDocumentRuntime();var doc = docRt.Document;var cmdCtrl = docEditor.GetCommandController();cmdCtrl.WriteInfo("=== 文档信息 ===");cmdCtrl.WriteInfo($"文档名称: {doc.Name}");cmdCtrl.WriteInfo($"元素总数: {doc.Elements.Count}");cmdCtrl.WriteInfo($"图层数量: {doc.Layers.Count}");cmdCtrl.WriteInfo("================");return CommandResult.Succ();}}
}
4.12 本章小结
本章介绍了LightCAD插件开发的基础知识:
- 插件体系:ILcPlugin接口和生命周期
- UI集成:TabItem、TabButton的创建和配置
- 命令开发:CommandClass和CommandMethod的使用
- 文档操作:获取文档、遍历元素、图层管理
- 用户输入:点输入、文本输入、元素选择
- 元素创建:基本元素和自定义元素的创建
- 事件处理:文档事件和选择事件
- 配置持久化:设置的保存和加载
- 错误处理:异常处理和输入验证
掌握这些基础知识后,下一章我们将深入学习元素类型系统的详细实现。
← 上一章目录下一章 →
