跳到主要内容
版本:1.0.0

控件插件(Control Plugin)开发

ToolBank 的插件分两大类:工具插件(C++,提供算子,见前文)与控件插件(C# / WPF,提供“用户视图”里的可视化控件,称为 ViewBlock)。控件插件让第三方能够扩展界面层:做一个测量结果表、一个统计仪表、一个你自己的操作面板,插入到用户视图中与程序实时联动。

控件插件 vs 工具插件​

维度工具插件(ToolPlugin)控件插件(ControlPlugin)
语言 / 运行层C++ DLL,算子跑在内核C# DLL(WPF),跑在 UI 进程
扩展点ToolBaseNative + 工厂注册算子IViewBlockProvider 声明一组 ViewBlock
能力图像 / 几何 / 数值计算等算法展示、交互控件;可触发程序运行、读变量
兼容性校验C++ ABI 指纹反射校验是否实现 IViewBlockProvider
状态管理plugins.lock.jsoncontrolplugins.lock.json(物理隔离,互不干扰)
安装目录`Plugins/Localonline/<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。

安装 / 启停 / 重载​

  1. 主菜单 插件 → 插件管理,切换到 🎛 控件插件 页签(双 Tab 界面:🛠 工具插件 / 🎛 控件插件);
  2. 📦 安装本地插件包 Zip → 选择打包好的 Zip,自动完成校验与落位;
  3. 已装列表可 启用 / 停用 / 卸载 / 打开安装目录 / 重载 DLL:
    • 停用后用户视图里不再提供该插件的 ViewBlock(已放置的块会保留占位并提示);
    • 重载:改代码重新编译后直接点重载即可生效,不需要重启整个 ToolBank——这是控件插件在调试期的最大便利;
  4. 控件插件 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 前缀即可开始。