一、设计思路
- 下游节点能够订阅上游节点的数据,首先需要将节点的运行数据存放在一个结果类中,这里我们自定义了一个RunResult的运行结果基类,把VM算子里的运行结果转存到RunResult类中。
- 当用户需要订阅的数据时,动态地生成数据菜单项供用户选择。
- 在流程运行遇到需要订阅的数据时,根据文字描述的地址找到对应的数据。
二、代码实现
1. RunResult运行结果类设计
public abstract class RunResult
{
[Description("运行状态")]
public int NodeStatus { get; set; }
public string Msg { get; set; }
public double RunningTime { get; set; }
public RunResult()
{
Msg = string.Empty;
}
}
其他模块算子运行结果类继承RunResult,以Blob算子为例。
public class BlobRunResult : RunResult
{
[Description("Blob个数")]
public int NumBlobs { get; set; }
[Description("Blob信息集")]
public List<BlobInfo> BlobInfos { get; set; }
public BlobRunResult()
{
NumBlobs = 0;
BlobInfos = new();
}
}
Description特性的作用:
1、为菜单项提供文本,以便显示属性的描述而不是属性名称
2、作为地址的一部分
举个例子,如下图流程所示,【4条件分支】节点在数据订阅,动态生成的二级菜单项的文本内容就是来自Description特性,一级菜单的文本内容就是节点的名字,如1图像源、2灰度化等。
字符串“3BL查找.Blob个数”作为一个地址,在运行到【4条件分支】节点时,程序根据地址找到对应的节点,即【3BL查找】节点,然后根据“Blob个数”找到与之相对应的Description特性,进而得到想要的数据。


2. 动态生成菜单项
思路:每个算法模块都应该根据自己的运行结果生成菜单,最后汇总显示。
单个算法模块的菜单生成
public override ToolStripMenuItem GetMenuItem(Type desiredType)
{
ToolStripMenuItem topMenu = new();
topMenu.Text = Text;
var properties = typeof(BlobRunResult).GetProperties();
foreach (var property in properties)
{
Type propertyType = property.PropertyType;
if (propertyType != desiredType) continue;
var attributes = property.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
var text = ((DescriptionAttribute)attributes[0]).Description;
var menuItem = new ToolStripMenuItem();
menuItem.Text = text;
topMenu.DropDownItems.Add(menuItem);
}
}
return topMenu;
}
整体菜单的生成
private void tbCondition_Click(object sender, EventArgs e)
{
ContextMenuStrip contextMenu = new();
HashSet<Node> nodes = NodeHelper.GetUpstreamNodes(_node);
Type type = typeof(System.Int32);
if (cbParamType.SelectedIndex == 0)
{
type = typeof(System.Int32);
}
else if (cbParamType.SelectedIndex == 1)
{
type = typeof(System.Double);
}
foreach (Node node in nodes)
{
ToolStripMenuItem menuItem = node.GetMenuItem(type);
menuItem.Width = tbCondition.Width;
WireUpClickEvent(tbCondition, menuItem);
contextMenu.Items.Add(menuItem);
}
contextMenu.Show(tbCondition, new Point(0, tbCondition.Height));
}
3. 利用反射+特性获取运行时数据
public override object? GetRunResultByName(string name)
{
string propName = name.Split(".")[1];
var properties = typeof(BlobRunResult).GetProperties();
foreach (var property in properties)
{
var attributes = property.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
var text = (attributes[0] as DescriptionAttribute).Description;
if (text == propName)
{
return property.GetValue(RunResult);
}
}
}
return null;
}
三、优点与不足
优点:对于想提供给用户订阅的数据,只需在某个属性上加Description特性即可,简单方便。生成菜单与获取数据都是动态完成,无需额外编码。
不足:目前只支持到二级菜单,对于复杂类型的变量需要额外处理。