插件开发基础
第四章:插件开发基础
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>net8.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的使用
- 文档操作:获取文档、遍历元素、图层管理
- 用户输入:点输入、文本输入、元素选择
- 元素创建:基本元素和自定义元素的创建
- 事件处理:文档事件和选择事件
- 配置持久化:设置的保存和加载
- 错误处理:异常处理和输入验证
掌握这些基础知识后,下一章我们将深入学习元素类型系统的详细实现。

浙公网安备 33010602011771号