控件插件(Control Plugin)开发
ToolBank 的插件分两大类:工具插件(C++,提供算子,见前文)与控件插件(C# / WPF,提供“用户视图”里的可视化控件,称为 ViewBlock)。控件插件让第三方能够扩展界面层:做一个测量结果表、一个统计仪表、一个你自己的操作面板,插入到用户视图中与程序实时联动。
控件插件 vs 工具插件
| 维度 | 工具插件(ToolPlugin) | 控件插件(ControlPlugin) |
|---|---|---|
| 语言 / 运行层 | C++ DLL,算子跑在内核 | C# DLL(WPF),跑在 UI 进程 |
| 扩展点 | ToolBaseNative + 工厂注册算子 | IViewBlockProvider 声明一组 ViewBlock |
| 能力 | 图像 / 几何 / 数值计算等算法 | 展示、交互控件;可触发程序运行、读变量 |
| 兼容性校验 | C++ ABI 指纹 | 反射校验是否实现 IViewBlockProvider |
| 状态管理 | plugins.lock.json | controlplugins.lock.json(物理隔离,互不干扰) |
| 安装目录 | `Plugins/Local | online/<id>/<ver>/` |
| 生命周期 | 安装 / 启用 / 停用 / 卸载 / 重载 | 同左,支持动态 Reload 无需重启宿主 |
| 市场 | 已开放 | 暂未开放(界面内搜索禁用并提示) |
一句话:工具插件决定“能算什么”,控件插件决定“结果长什么样、用户怎么操作”。
生态现状
- 内置视图块(宿主自带,Base 级自动启用、不可停用):Image2D 图像显示、NumericArray 数值数组、StringArray 字符串数组,以及用户视图的网格 / 叠加注记体系;
- 官方示例:
PlugControl.Example——3 个最小 ViewBlock:Example.TextDisplay(文本显示)、Example.ProgressBar(0-100 进度条)、Example.CounterButton(按钮计数器,含“触发程序运行”的 API 示例),是开发者的最小样板工程; - 官方实用插件:
PlugControl.Camera(Camera.ControlPanel相机控制面板)、PlugControl.ToolHelper(ToolHelper.Launcher例程启动器)等,随产品发布。
manifest 与打包
控件插件同样是一个 Zip:入口 DLL + manifest.json(可选图标)。manifest 遵循 ControlPluginManifest.v1.json:
{
"$schema": "ControlPluginManifest.v1.json",
"pluginId": "PlugControl.Example",
"displayName": "示例控件插件",
"version": "1.0.10.4",
"description": "包含 3 个示例 ViewBlock……",
"vendor": "ToolBank Examples",
"category": "ControlPlugin",
"entryDll": "PlugControl.Example.dll",
"minHostVersion": "1.0.0",
"dependencies": [
{ "id": "MainUI.CV.Viewer.ViewBlocks", "minVersion": "1.0.0" }
],
"installSourceHint": "Local"
}
字段要点:
category必须为ControlPlugin(工具插件是ToolPlugin),宿主据此分派到控件插件管理器;entryDll为实现了IViewBlockProvider的 C# 程序集;dependencies声明对宿主视图块程序集MainUI.CV.Viewer.ViewBlocks的依赖(写交互控件基本都要引用它);- 约定所有 ViewBlock 的
TypeId使用你的插件前缀(如Example.),避免与内置 / 其它插件冲突。
打包即把 PluginId 版本号 目录(DLL + manifest + 依赖 DLL)压成 Zip。
安装 / 启停 / 重载
- 主菜单 插件 → 插件管理,切换到 🎛 控件插件 页签(双 Tab 界面:🛠 工具插件 / 🎛 控件插件);
- 📦 安装本地插件包 Zip → 选择打包好的 Zip,自动完成校验与落位;
- 已装列表可 启用 / 停用 / 卸载 / 打开安装目录 / 重载 DLL:
- 停用后用户视图里不再提供该插件的 ViewBlock(已放置的块会保留占位并提示);
- 重载:改代码重新编译后直接点重载即可生效,不需要重启整个 ToolBank——这是控件插件在调试期的最大便利;
- 控件插件 Tab 下市场功能暂不可用(黄色提示),安装来源目前为本地 Zip。
宿主每次启动时,会通过控件插件管理器统一加载:内置基础程序集(Base)→ 历史遗留目录 DLL → 状态文件中启用的第三方条目。
开发一个 ViewBlock(最小路径)
环境:Visual Studio + C# / WPF 类库(.NET Framework 4.7.2,与宿主一致),引用 MainUI.CV.Viewer.ViewBlocks(接口在 MainUI.CV.Viewer.ViewBlocks.Interfaces / .Models)。
第 1 步:实现 Provider
public class MyViewBlockProvider : IViewBlockProvider
{
public string ProviderName => "我的控件插件";
public List<ViewBlockMetadata> GetSupportedViewBlockTypes()
{
return new List<ViewBlockMetadata>
{
new ViewBlockMetadata
{
TypeId = "My.TextPanel", // 唯一标识,带前缀
DisplayName = "文本面板",
Description = "显示一个字符串变量的最新值。",
VariableType = "string", // 绑定变量的类型
ViewBlockTypeName = typeof(MyTextPanelViewBlock).FullName,
DefaultSize = new Size(240, 120),
ProviderName = ProviderName
}
};
}
public IViewBlock CreateViewBlock(ViewBlockMetadata meta)
{
switch (meta?.TypeId)
{
case "My.TextPanel":
return new MyTextPanelViewBlock();
default:
return null;
}
}
}
第 2 步:实现 ViewBlock(一个控件类 + 一个承载它的 UserControl),绑定到元数据指定的变量后,即可在用户视图中实时显示 / 编辑变量值。
第 3 步:与程序交互(官方示例 CounterButton 展示的标准做法):
// 查 Job 并触发运行(返回结构化结果,可指定 Job 与运行模式)
var jobs = PluginEvents.GetProgramJobs(); // JobIndex/JobName/IsSelected
var result = PluginEvents.RaiseRequestProgramRun(new ProgramRunRequestArgs
{
JobName = targetJob, // 或 JobIndex
RunMode = ProgramRunMode.RunOnce, // RunOnce / RunContinue
});
// ProgramRunResult: Success / Message / SelectedJobName
新旧 API 说明:旧接口 RaiseRequestProgramRunOnce() / RaiseRequestProgramRunContinue() 只能作用于 UI 当前选中的 Job 且无返回结果,仅兼容保留;新接口 RaiseRequestProgramRun(ProgramRunRequestArgs) 可指定 Job、返回结构化结果,新开发一律用新接口。
调试与常见坑
| 现象 | 排查 |
|---|---|
| 安装后被识别为工具插件 / 拒绝安装 | 确认 category 是 ControlPlugin,且 DLL 实现了 IViewBlockProvider |
| 用户视图里找不到我的 ViewBlock | 检查插件是否在 🎛 Tab 处于启用态;TypeId 是否冲突;停用后需重新启用 |
| 控件不显示变量值 | 核对 ViewBlockMetadata.VariableType 与绑定变量的实际类型是否一致 |
| 重载后旧逻辑仍在 | 确认重载的是最新编译 DLL(先停用再重载最稳妥) |
| 依赖缺失报错 | 第三方依赖 DLL 需一并放进包目录,宿主按“插件目录即依赖目录”加载 |
更多代码细节直接参考官方示例工程 PlugControl.Example(含完整 Provider、三种 ViewBlock 与 Program API 交互逻辑),把它复制一份改 TypeId 前缀即可开始。