diff --git a/FlowStartTool/Properties/launchSettings.json b/FlowStartTool/Properties/launchSettings.json
new file mode 100644
index 0000000..a742007
--- /dev/null
+++ b/FlowStartTool/Properties/launchSettings.json
@@ -0,0 +1,10 @@
+{
+ "profiles": {
+ "Serein.FlowStartTool": {
+ "commandName": "Project"
+ },
+ "配置文件 1": {
+ "commandName": "DebugRoslynComponent"
+ }
+ }
+}
\ No newline at end of file
diff --git a/FlowStartTool/Serein.FlowStartTool.csproj b/FlowStartTool/Serein.FlowStartTool.csproj
index bb0d729..1d48b61 100644
--- a/FlowStartTool/Serein.FlowStartTool.csproj
+++ b/FlowStartTool/Serein.FlowStartTool.csproj
@@ -9,6 +9,9 @@
enable
prompt
starter
+
+
+
@@ -23,8 +26,16 @@
+
+
+
+
+
+
-
+
+
diff --git a/Library/FlowNode/Attribute.cs b/Library/FlowNode/Attribute.cs
index 22a16c7..f282b59 100644
--- a/Library/FlowNode/Attribute.cs
+++ b/Library/FlowNode/Attribute.cs
@@ -6,9 +6,16 @@ using System.Threading.Tasks;
namespace Serein.Library
{
+ ///
+ /// 标识一个类中的某些字段需要生成相应代码
+ ///
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
- internal sealed class AutoPropertyAttribute : Attribute
+ public sealed class AutoPropertyAttribute : Attribute
{
+ ///
+ /// 属性路径
+ /// CustomNode : 自定义节点
+ ///
public string ValuePath = string.Empty;
}
@@ -16,10 +23,19 @@ namespace Serein.Library
/// 自动生成环境的属性
///
[AttributeUsage(AttributeTargets.Field, Inherited = true)]
- internal sealed class PropertyInfoAttribute : Attribute
+ public sealed class PropertyInfoAttribute : Attribute
{
+ ///
+ /// 是否通知UI
+ ///
public bool IsNotification = false;
+ ///
+ /// 是否使用Console.WriteLine打印
+ ///
public bool IsPrint = false;
+ ///
+ /// 是否禁止参数进行修改(初始化后不能再通过setter修改)
+ ///
public bool IsProtection = false;
}
diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs
index d6ce6ea..07cd638 100644
--- a/Library/FlowNode/MethodDetails.cs
+++ b/Library/FlowNode/MethodDetails.cs
@@ -9,7 +9,7 @@ namespace Serein.Library
///
/// 每个节点有独自的MethodDetails实例
///
- [AutoProperty(ValuePath = nameof(MethodDetails))]
+ [NodeProperty(ValuePath = NodeValuePath.Method)]
public partial class MethodDetails
{
private readonly IFlowEnvironment env;
@@ -142,7 +142,7 @@ namespace Serein.Library
MethodLockName = this.MethodLockName,
IsProtectionParameter = this.IsProtectionParameter,
};
- md.ParameterDetailss = this.ParameterDetailss.Select(p => p.CloneOfClone(env, nodeModel)).ToArray(); // 拷贝属于节点方法的新入参描述
+ md.ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfClone(env, nodeModel)).ToArray(); // 拷贝属于节点方法的新入参描述
return md;
}
diff --git a/Library/FlowNode/NodeDebugSetting.cs b/Library/FlowNode/NodeDebugSetting.cs
index 8c3dc45..7cfc4fd 100644
--- a/Library/FlowNode/NodeDebugSetting.cs
+++ b/Library/FlowNode/NodeDebugSetting.cs
@@ -9,7 +9,7 @@ namespace Serein.Library
///
/// 节点调试设置,用于中断节点的运行
///
- [AutoProperty(ValuePath = nameof(NodeDebugSetting))]
+ [NodeProperty(ValuePath = NodeValuePath.DebugSetting)]
public partial class NodeDebugSetting
{
private readonly NodeModelBase nodeModel;
@@ -33,6 +33,13 @@ namespace Serein.Library
[PropertyInfo]
private InterruptClass _interruptClass = InterruptClass.None;
+ ///
+ /// 中断级别,暂时停止继续执行后继分支。
+ ///
+ [PropertyInfo(IsNotification = true)]
+ private bool _isInterrupt = false;
+
+
///
/// 取消中断的回调函数
///
diff --git a/Library/FlowNode/NodeModelBaseData.cs b/Library/FlowNode/NodeModelBaseData.cs
index e1f7798..cb92d99 100644
--- a/Library/FlowNode/NodeModelBaseData.cs
+++ b/Library/FlowNode/NodeModelBaseData.cs
@@ -1,4 +1,5 @@
using Serein.Library.Api;
+using Serein.Library.NodeGenerator;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -10,7 +11,7 @@ namespace Serein.Library
///
/// 节点基类(数据):条件控件,动作控件,条件区域,动作区域
///
- [AutoProperty(ValuePath = nameof(NodeModelBase))] // 是否更名为 NodeProperty?
+ [NodeProperty(ValuePath = NodeValuePath.None)]
public abstract partial class NodeModelBase : IDynamicFlowNode
{
diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs
index 54f6fb8..0a3ae20 100644
--- a/Library/FlowNode/NodeModelBaseFunc.cs
+++ b/Library/FlowNode/NodeModelBaseFunc.cs
@@ -86,7 +86,13 @@ namespace Serein.Library
///
public virtual NodeModelBase LoadInfo(NodeInfo nodeInfo)
{
- this.Guid = nodeInfo.Guid;
+ this.Guid = nodeInfo.Guid;
+
+ if (nodeInfo.Position is null)
+ {
+ nodeInfo.Position = new PositionOfUI(0, 0);
+ }
+ this.Position = nodeInfo.Position;// 加载位置信息
if (this.MethodDetails != null)
{
for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
@@ -96,7 +102,6 @@ namespace Serein.Library
this.MethodDetails.ParameterDetailss[i].DataValue = pd.Value;
}
}
- this.Position = nodeInfo.Position;// 加载位置信息
return this;
}
diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs
index 09116b0..54b25e8 100644
--- a/Library/FlowNode/ParameterDetails.cs
+++ b/Library/FlowNode/ParameterDetails.cs
@@ -11,7 +11,7 @@ namespace Serein.Library
///
/// 节点入参参数详情
///
- [AutoProperty(ValuePath = nameof(ParameterDetails))]
+ [NodeProperty(ValuePath = NodeValuePath.Parameter)]
public partial class ParameterDetails
{
private readonly IFlowEnvironment env;
diff --git a/Library/FlowNode/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs
index 473103b..1dcf25c 100644
--- a/Library/FlowNode/SereinProjectData.cs
+++ b/Library/FlowNode/SereinProjectData.cs
@@ -11,7 +11,7 @@ namespace Serein.Library
{
///
- /// 环境信息(远程控制用)
+ /// 环境信息
///
public class FlowEnvInfo
{
@@ -27,10 +27,18 @@ namespace Serein.Library
// IOC节点对象信息
}
+ ///
+ /// 程序集相关的方法信息
+ ///
public class LibraryMds
{
+ ///
+ /// 程序集FullName
+ ///
public string LibraryName { get; set; }
-
+ ///
+ /// 相关的方法详情
+ ///
public MethodDetailsInfo[] Mds { get; set; }
}
@@ -70,7 +78,7 @@ namespace Serein.Library
}
///
- /// 基础
+ /// 基础,项目文件相关
///
public class Basic
{
@@ -87,7 +95,7 @@ namespace Serein.Library
public string Versions { get; set; }
}
///
- /// 画布
+ /// 画布信息,项目文件相关
///
public class FlowCanvas
{
@@ -122,7 +130,7 @@ namespace Serein.Library
}
///
- /// DLL
+ /// 项目依赖的程序集,项目文件相关
///
public class Library
{
@@ -144,7 +152,7 @@ namespace Serein.Library
}
///
- /// 节点
+ /// 节点信息,项目文件相关
///
public class NodeInfo
{
@@ -215,7 +223,7 @@ namespace Serein.Library
}
///
- /// 显示参数
+ /// 显示参数,项目文件相关
///
public class Parameterdata
{
diff --git a/Library/Network/WebSocket/Attribute.cs b/Library/Network/WebSocket/Attribute.cs
index 940ee2d..995ea75 100644
--- a/Library/Network/WebSocket/Attribute.cs
+++ b/Library/Network/WebSocket/Attribute.cs
@@ -27,6 +27,7 @@ namespace Serein.Library.Network.WebSocketCommunication
{
public string ThemeKey;
public string DataKey;
+ public string MsgIdKey;
}
@@ -71,13 +72,19 @@ namespace Serein.Library.Network.WebSocketCommunication
}
///
- /// 使用消息DataKey整体数据
+ /// 使用 DataKey 整体数据
///
[AttributeUsage(AttributeTargets.Parameter)]
- public sealed class UseMsgDataAttribute : Attribute
+ public sealed class UseDataAttribute : Attribute
+ {
+ }
+ ///
+ /// 使用 MsgIdKey 整体数据
+ ///
+ [AttributeUsage(AttributeTargets.Parameter)]
+ public sealed class UseMsgIdAttribute : Attribute
{
}
-
internal class SocketHandleModule
{
diff --git a/Library/Network/WebSocket/Handle/Attribute.cs b/Library/Network/WebSocket/Handle/Attribute.cs
index d5f2287..8184f84 100644
--- a/Library/Network/WebSocket/Handle/Attribute.cs
+++ b/Library/Network/WebSocket/Handle/Attribute.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
namespace Serein.Library.Network.WebSocketCommunication.Handle
@@ -12,4 +13,55 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
public sealed class NeedfulAttribute : Attribute
{
}
+
+ ///
+ /// 消息ID生成器
+ ///
+ public class MsgIdHelper
+ {
+ private static readonly long _epoch = new DateTime(2023, 1, 1).Ticks; // 自定义起始时间
+ private static long _lastTimestamp = -1L; // 上一次生成 ID 的时间戳
+ private static long _sequence = 0L; // 序列号
+
+ ///
+ /// 获取新的ID
+ ///
+ public static long NewId => GenerateId();
+
+
+ ///
+ /// 生成消息ID
+ ///
+ ///
+ public static long GenerateId()
+ {
+ long timestamp = DateTime.UtcNow.Ticks;
+
+ // 如果时间戳是一样的,递增序列号
+ if (timestamp == _lastTimestamp)
+ {
+ // 使用原子操作增加序列号
+ _sequence = Interlocked.Increment(ref _sequence);
+ if (_sequence > 999999) // 序列号最大值,6位
+ {
+ // 等待下一毫秒
+ while (timestamp <= _lastTimestamp)
+ {
+ timestamp = DateTime.UtcNow.Ticks;
+ }
+ }
+ }
+ else
+ {
+ _sequence = 0; // 重置序列号
+ }
+
+ _lastTimestamp = timestamp;
+
+ // 生成 ID:时间戳和序列号拼接
+ return (timestamp - _epoch) * 1000 + _sequence; // 返回 ID
+ }
+ }
+
+
}
diff --git a/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs
index 87adcbc..5bbc321 100644
--- a/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs
+++ b/Library/Network/WebSocket/Handle/JsonMsgHandleConfig.cs
@@ -41,7 +41,8 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
this.OnExceptionTracking = onExceptionTracking;
this.ArgNotNull = ArgNotNull;
- this.useMsgData = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray();
+ this.useData = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray();
+ this.useMsgId = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray();
#if NET5_0_OR_GREATER
this.IsCheckArgNotNull = parameterInfos.Select(p => p.GetCustomAttribute() != null).ToArray();
#endif
@@ -101,17 +102,24 @@ namespace Serein.Library.Network.WebSocketCommunication.Handle
///
private readonly Type[] ParameterType;
///
- /// 是否使用整体data参数
+ /// 是否使Data整体内容作为入参参数
///
- private readonly bool[] useMsgData;
+ private readonly bool[] useData;
+ ///
+ /// 是否使用消息ID作为入参参数
+ ///
+ private readonly bool[] useMsgId;
///
/// 是否检查变量为空
///
private readonly bool[] IsCheckArgNotNull;
+ //private object ConvertArg(Type type, string argName )
+ //{
+ //}
- public async void Handle(Func
@@ -30,6 +30,10 @@
+
+
+
+
diff --git a/Library/Utils/RemoteEnvControl.cs b/Library/Utils/RemoteEnvControl.cs
index 643ed78..15e8cab 100644
--- a/Library/Utils/RemoteEnvControl.cs
+++ b/Library/Utils/RemoteEnvControl.cs
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using Serein.Library.Network.WebSocketCommunication;
using System;
using System.Collections.Generic;
@@ -15,29 +16,52 @@ namespace Serein.Library.Utils
public class RemoteEnvControl
{
///
- /// 配置远程连接IP端口
+ /// 远程环境配置
///
- public RemoteEnvControl(string addres, int port, object token)
+ public class ControlConfiguration
{
- this.Addres = addres;
- this.Port = port;
- this.Token = token;
+ ///
+ /// 远程环境的网络地址
+ ///
+ public string Addres { get; set; }
+
+ ///
+ /// 远程环境的对外端口
+ ///
+ public int Port { get; set; }
+
+ ///
+ /// 登录远程环境必须携带的token(可以为可序列化的JSON对象)
+ ///
+ public object Token { get; set; }
+
+ ///
+ /// 有关消息ID的 Json Key
+ ///
+ public string MsgIdJsonKey { get; set; }
+ ///
+ /// 有关消息主题的 Json Key
+ ///
+ public string ThemeJsonKey { get; set; }
+ ///
+ /// 有关数据的 Json Key
+ ///
+ public string DataJsonKey { get; set; }
}
///
- /// 远程环境的网络地址
+ /// 配置远程连接IP端口
///
- public string Addres { get; }
+ public RemoteEnvControl(ControlConfiguration controlConfiguration)
+ {
+ Config = controlConfiguration;
+ }
///
- /// 远程环境的对外端口
+ /// 配置信息
///
- public int Port { get; }
+ public ControlConfiguration Config { get; }
- ///
- /// 登录远程环境必须携带的token(可以为可序列化的JSON对象)
- ///
- public object Token { get; }
@@ -49,8 +73,8 @@ namespace Serein.Library.Utils
///
/// 是否连接到了远程环境
///
- public bool IsConnectdRemoteEnv { get => isConnectdRemoteEnv; }
- private bool isConnectdRemoteEnv = false;
+ //public bool IsConnectdRemoteEnv { get => isConnectdRemoteEnv; }
+ //private bool isConnectdRemoteEnv = false;
///
/// 尝试连接到远程环境
@@ -59,12 +83,12 @@ namespace Serein.Library.Utils
public async Task ConnectAsync()
{
// 第2种,WebSocket连接到远程环境,实时接收远程环境的响应?
- Console.WriteLine($"准备连接:{Addres}:{Port},{Token}");
+ Console.WriteLine($"准备连接:{Config.Addres}:{Config.Port},{Config.Token}");
bool success = false;
try
{
var tcpClient = new TcpClient();
- var result = tcpClient.BeginConnect(Addres, Port, null, null);
+ var result = tcpClient.BeginConnect(Config.Addres, Config.Port, null, null);
success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(3));
}
finally
@@ -73,14 +97,14 @@ namespace Serein.Library.Utils
}
if (!success)
{
- Console.WriteLine($"无法连通远程端口 {Addres}:{Port}");
+ Console.WriteLine($"无法连通远程端口 {Config.Addres}:{Config.Port}");
return false;
}
else
{
- var url = $"ws://{Addres}:{Port}/";
+ var url = $"ws://{Config.Addres}:{Config.Port}/";
var result = await EnvClient.ConnectAsync(url); // 尝试连接远程环境
- this.isConnectdRemoteEnv = result;
+ //this.isConnectdRemoteEnv = result;
return result;
}
}
@@ -90,18 +114,53 @@ namespace Serein.Library.Utils
///
/// 发送消息
///
+ ///
///
///
///
- public async Task SendAsync(string theme, object data)
+ public async Task SendAsync(string msgId , string theme, object data)
{
- var sendMsg = new
+ //var sendMsg = new
+ //{
+ // theme = theme,
+ // token = this.Token,
+ // data = data,
+ //};
+ //var msg = JsonConvert.SerializeObject(sendMsg);
+ JObject jsonData;
+
+ if (data is null)
{
- theme = theme,
- token = this.Token,
- data = data,
- };
- var msg = JsonConvert.SerializeObject(sendMsg);
+ jsonData = new JObject()
+ {
+ [Config.MsgIdJsonKey] = msgId,
+ [Config.ThemeJsonKey] = theme,
+ };
+ }
+ else
+ {
+ JToken dataToken;
+ if (data is System.Collections.IEnumerable || data is Array)
+ {
+ dataToken = JArray.FromObject(data);
+ }
+ else
+ {
+ dataToken = JObject.FromObject(data);
+ }
+
+ jsonData = new JObject()
+ {
+ [Config.MsgIdJsonKey] = msgId,
+ [Config.ThemeJsonKey] = theme,
+ [Config.DataJsonKey] = dataToken
+ };
+ }
+
+ var msg = jsonData.ToString();
+ //Console.WriteLine(msg);
+ //Console.WriteLine();
+
await EnvClient.SendAsync(msg);
}
@@ -113,8 +172,6 @@ namespace Serein.Library.Utils
-
-
}
diff --git a/NodeFlow/Env/EnvMsgTheme.cs b/NodeFlow/Env/EnvMsgTheme.cs
index ee193ce..04fe42a 100644
--- a/NodeFlow/Env/EnvMsgTheme.cs
+++ b/NodeFlow/Env/EnvMsgTheme.cs
@@ -1,10 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace Serein.NodeFlow.Env
+namespace Serein.NodeFlow.Env
{
///
/// 消息主题
diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs
index 9a00c63..d23b216 100644
--- a/NodeFlow/Env/FlowEnvironment.cs
+++ b/NodeFlow/Env/FlowEnvironment.cs
@@ -1,28 +1,15 @@
using Newtonsoft.Json;
-using Newtonsoft.Json.Bson;
-using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
-using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Utils;
using Serein.Library.Utils.SereinExpression;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Tool;
-using System;
-using System.Collections;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Net.Sockets;
-using System.Numerics;
using System.Reflection;
-using System.Security.Cryptography;
-using System.Threading;
using System.Xml.Linq;
using static Serein.Library.Utils.ChannelFlowInterrupt;
-using static Serein.NodeFlow.FlowStarter;
-using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Serein.NodeFlow.Env
{
@@ -41,6 +28,7 @@ namespace Serein.NodeFlow.Env
public const string SpaceName = $"{nameof(Serein)}.{nameof(NodeFlow)}.{nameof(Model)}";
public const string ThemeKey = "theme";
public const string DataKey = "data";
+ public const string MsgIdKey = "msgid";
///
/// 流程运行环境
@@ -80,10 +68,6 @@ namespace Serein.NodeFlow.Env
-
-
-
-
///
/// 打开远程管理
///
@@ -112,6 +96,7 @@ namespace Serein.NodeFlow.Env
Console.WriteLine("结束远程管理异常:" + ex);
}
}
+
#endregion
#region 环境运行事件
@@ -347,7 +332,6 @@ namespace Serein.NodeFlow.Env
}
-
///
/// 异步运行
///
@@ -566,8 +550,17 @@ namespace Serein.NodeFlow.Env
}
else
{
+ MethodDetails? methodDetails = null;
+ if (!string.IsNullOrEmpty(nodeInfo.MethodName))
+ {
+ MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 加载项目时尝试获取方法信息
+ }
+ else
+ {
- MethodDetailss.TryGetValue(nodeInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息
+ }
+
+
var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点
nodeModel.LoadInfo(nodeInfo); // 创建节点model
if (nodeModel is null)
@@ -575,6 +568,8 @@ namespace Serein.NodeFlow.Env
nodeInfo.Guid = string.Empty;
continue;
}
+
+
TryAddNode(nodeModel); // 加载项目时将节点加载到环境中
if (nodeInfo.ChildNodeGuids?.Length > 0)
{
@@ -662,9 +657,6 @@ namespace Serein.NodeFlow.Env
}
-
-
-
///
/// 加载远程环境
///
@@ -679,7 +671,17 @@ namespace Serein.NodeFlow.Env
return (false, null);
}
// 没有连接远程环境,可以重新连接
- var remoteEnvControl = new RemoteEnvControl(addres, port, token);
+
+ var controlConfiguration = new RemoteEnvControl.ControlConfiguration
+ {
+ Addres = addres,
+ Port = port,
+ Token = token,
+ ThemeJsonKey = FlowEnvironment.ThemeKey,
+ MsgIdJsonKey = FlowEnvironment.MsgIdKey,
+ DataJsonKey = FlowEnvironment.DataKey,
+ };
+ var remoteEnvControl = new RemoteEnvControl(controlConfiguration);
var result = await remoteEnvControl.ConnectAsync();
if (!result)
{
@@ -805,7 +807,6 @@ namespace Serein.NodeFlow.Env
}
}
-
TryAddNode(nodeModel);
nodeModel.Position = position;
@@ -1199,30 +1200,11 @@ namespace Serein.NodeFlow.Env
}
NodeValueChangeLogger.Add((nodeGuid, path, value));
var setExp = $"@Set .{path} = {value}"; // 生成 set 表达式
- SerinExpressionEvaluator.Evaluate(setExp, nodeModel, out _); // 更改对应的数据
-
-
-
- //Console.WriteLine($"本地环境收到数据更改通知:{value}");
//var getExp = $"@Get .{path}";
- ////Console.WriteLine($"取值表达式:{getExp}");
+ SerinExpressionEvaluator.Evaluate(setExp, nodeModel, out _); // 更改对应的数据
//var getResult = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _);
- ////Console.WriteLine($"原数据 :{getResult}");
- //if (getResult.Equals(value))
- //{
- // Console.WriteLine("无须修改");
- // return;
- //}
-
-
- //NodeValueChangeLogger.Add((nodeGuid, path, value));
-
-
- //var setExp = $"@Set .{path} = {value}";
- ////Console.WriteLine($"设值表达式:{setExp}");
- //SerinExpressionEvaluator.Evaluate(setExp, nodeModel, out _);
- //getResult = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _);
- //Console.WriteLine($"新数据 :{getResult}");
+ //Console.WriteLine($"Set表达式:{setExp},result : {getResult}");
+
}
diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs
index 05255ce..a9848ef 100644
--- a/NodeFlow/Env/FlowEnvironmentDecorator.cs
+++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs
@@ -1,14 +1,6 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
-using Serein.Library.Web;
-using Serein.NodeFlow.Tool;
-using System;
-using System.Collections.Generic;
-using System.Data;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs
index cda1add..6173f04 100644
--- a/NodeFlow/Env/FlowFunc.cs
+++ b/NodeFlow/Env/FlowFunc.cs
@@ -1,12 +1,6 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Model;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
diff --git a/NodeFlow/Env/MsgControllerOfClient.cs b/NodeFlow/Env/MsgControllerOfClient.cs
index 8eaeaec..53548c8 100644
--- a/NodeFlow/Env/MsgControllerOfClient.cs
+++ b/NodeFlow/Env/MsgControllerOfClient.cs
@@ -1,12 +1,7 @@
-using Newtonsoft.Json;
-using Serein.Library;
+using Serein.Library;
using Serein.Library.Network.WebSocketCommunication;
+using Serein.Library.Network.WebSocketCommunication.Handle;
using Serein.Library.Utils;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
@@ -17,33 +12,43 @@ namespace Serein.NodeFlow.Env
/// 客户端的消息管理(用于处理服务端的响应)
///
- [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
+ [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey,
+ DataKey = FlowEnvironment.DataKey,
+ MsgIdKey = FlowEnvironment.MsgIdKey)]
public class MsgControllerOfClient : ISocketHandleModule
{
public Guid HandleGuid => new Guid();
- private readonly Func SendCommandAsync;
+
+ // 消息主题,data - task等待
+ private readonly Func SendCommandFunc;
private readonly RemoteFlowEnvironment remoteFlowEnvironment;
- public MsgControllerOfClient(RemoteFlowEnvironment remoteFlowEnvironment, Func func)
+ public MsgControllerOfClient(RemoteFlowEnvironment remoteFlowEnvironment, Func func)
{
this.remoteFlowEnvironment = remoteFlowEnvironment;
- SendCommandAsync = func;
+ SendCommandFunc = func;
+ }
+ private async Task SendCommandAsync(string msgId, string theme, object? data)
+ {
+ await SendCommandFunc.Invoke(msgId, theme, data);
}
+
///
- /// 发送请求并等待远程环境响应
+ /// 发送请求
///
///
/// 超时触发
- public async Task SendAsync(string signal, object? sendData = null, int overtimeInMs = 100)
+ public async Task SendAsync(string signal, object? data = null, int overtimeInMs = 100)
{
//Console.WriteLine($"指令[{signal}],value:{JsonConvert.SerializeObject(sendData)}");
if (!DebounceHelper.CanExecute(signal, overtimeInMs))
{
return;
}
- await SendCommandAsync.Invoke(signal, sendData);
+ var msgId = MsgIdHelper.GenerateId().ToString();
+ await SendCommandAsync(msgId, signal, data);
}
///
@@ -51,11 +56,13 @@ namespace Serein.NodeFlow.Env
///
///
/// 超时触发
- public async Task SendAndWaitDataAsync(string signal, object? sendData = null, int overtimeInMs = 50)
+ public async Task SendAndWaitDataAsync(string theme, object? data = null, int overtimeInMs = 50)
{
//Console.WriteLine($"指令[{signal}],value:{JsonConvert.SerializeObject(sendData)}");
- _ = SendCommandAsync.Invoke(signal, sendData);
- return await remoteFlowEnvironment.WaitData(signal);
+
+ var msgId = MsgIdHelper.GenerateId().ToString();
+ _ = SendCommandAsync(msgId, theme, data);
+ return await remoteFlowEnvironment.WaitData(msgId);
//if (DebounceHelper.CanExecute(signal, overtimeInMs))
//{
@@ -86,63 +93,63 @@ namespace Serein.NodeFlow.Env
///
/// 远程环境发来项目信息
///
+ ///
///
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
- public void GetEnvInfo([UseMsgData] FlowEnvInfo flowEnvInfo)
+ public void GetEnvInfo([UseMsgId] string msgId, [UseData] FlowEnvInfo flowEnvInfo)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetEnvInfo, flowEnvInfo);
+ remoteFlowEnvironment.TriggerSignal(msgId, flowEnvInfo);
}
-
-
///
/// 远程环境发来项目信息
///
+ ///
///
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetProjectInfo)]
- public void GetProjectInfo([UseMsgData] SereinProjectData sereinProjectData)
+ public void GetProjectInfo([UseMsgId] string msgId, [UseData] SereinProjectData sereinProjectData)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, sereinProjectData);
+ remoteFlowEnvironment.TriggerSignal(msgId, sereinProjectData);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.SetNodeInterrupt)]
- public void SetNodeInterrupt()
+ public void SetNodeInterrupt([UseMsgId] string msgId)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.GetProjectInfo, null);
+ remoteFlowEnvironment.TriggerSignal(msgId, null);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.AddInterruptExpression)]
- public void AddInterruptExpression()
+ public void AddInterruptExpression([UseMsgId] string msgId)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.AddInterruptExpression, null);
+ remoteFlowEnvironment.TriggerSignal(msgId, null);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateNode)]
- public void CreateNode([UseMsgData] NodeInfo nodeInfo)
+ public void CreateNode([UseMsgId] string msgId, [UseData] NodeInfo nodeInfo)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.CreateNode, nodeInfo);
+ remoteFlowEnvironment.TriggerSignal(msgId, nodeInfo);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveNode)]
- public void RemoveNode(bool state)
+ public void RemoveNode([UseMsgId] string msgId, bool state)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.RemoveNode, state);
+ remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.ConnectNode)]
- public void ConnectNode(bool state)
+ public void ConnectNode([UseMsgId] string msgId, bool state)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.ConnectNode, state);
+ remoteFlowEnvironment.TriggerSignal(msgId, state);
}
[AutoSocketHandle(ThemeValue = EnvMsgTheme.RemoveConnect)]
- public void RemoveConnect(bool state)
+ public void RemoveConnect([UseMsgId] string msgId, bool state)
{
- remoteFlowEnvironment.TriggerSignal(EnvMsgTheme.RemoveConnect, state);
+ remoteFlowEnvironment.TriggerSignal(msgId, state);
}
diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs
index c838bf6..5597b8b 100644
--- a/NodeFlow/Env/MsgControllerOfServer.cs
+++ b/NodeFlow/Env/MsgControllerOfServer.cs
@@ -1,22 +1,18 @@
using Newtonsoft.Json.Linq;
-using Serein.Library.Api;
using Serein.Library;
+using Serein.Library.Api;
using Serein.Library.Network.WebSocketCommunication;
using Serein.Library.Network.WebSocketCommunication.Handle;
using Serein.Library.Utils;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics.CodeAnalysis;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Serein.NodeFlow.Env
{
///
/// 服务端的消息管理(用于处理客户端的请求)
///
- [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey, DataKey = FlowEnvironment.DataKey)]
+ [AutoSocketModule(ThemeKey = FlowEnvironment.ThemeKey,
+ DataKey = FlowEnvironment.DataKey,
+ MsgIdKey = FlowEnvironment.MsgIdKey)]
public class MsgControllerOfServer : ISocketHandleModule
{
///
@@ -225,7 +221,7 @@ namespace Serein.NodeFlow.Env
///
- /// 获取当前环境信息(远程连接)
+ /// 获取当前环境信息
///
///
[AutoSocketHandle(ThemeValue = EnvMsgTheme.GetEnvInfo)]
@@ -316,6 +312,7 @@ namespace Serein.NodeFlow.Env
var nodeInfo = await environment.CreateNodeAsync(nodeControlType, position, mdInfo); // 监听到客户端创建节点的请求
return nodeInfo;
}
+
///
/// 从远程环境移除节点
///
@@ -464,10 +461,6 @@ namespace Serein.NodeFlow.Env
}
-
-
-
-
}
diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs
index a15ee0a..9b963f7 100644
--- a/NodeFlow/Env/RemoteFlowEnvironment.cs
+++ b/NodeFlow/Env/RemoteFlowEnvironment.cs
@@ -3,8 +3,6 @@ using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow.Tool;
using System.Collections.Concurrent;
-using System.Threading;
-using System.Xml.Linq;
namespace Serein.NodeFlow.Env
{
@@ -104,9 +102,8 @@ namespace Serein.NodeFlow.Env
public void LoadProject(FlowEnvInfo flowEnvInfo, string filePath)
{
- Console.WriteLine("远程环境尚未实现的接口:LoadProject");
+ //Console.WriteLine("远程环境尚未实现的接口:LoadProject");
-
// dll面板
var libmds = flowEnvInfo.LibraryMds;
foreach (var lib in libmds)
@@ -143,8 +140,11 @@ namespace Serein.NodeFlow.Env
else
{
- MethodDetails? methodDetails;
- MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 尝试获取方法信息
+ MethodDetails? methodDetails = null;
+ if (!string.IsNullOrEmpty(nodeInfo.MethodName))
+ {
+ MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 加载远程环境时尝试获取方法信息
+ }
var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载远程项目时创建节点
nodeModel.LoadInfo(nodeInfo); // 创建节点model
@@ -160,7 +160,6 @@ namespace Serein.NodeFlow.Env
{
regionChildNodes.Add((nodeModel, nodeInfo.ChildNodeGuids));
UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position)));
- //OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, nodeInfo.Position));
}
else
{
@@ -181,7 +180,6 @@ namespace Serein.NodeFlow.Env
continue;
}
// 存在节点
- //OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid));
UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(childNode, true, item.region.Guid)));
}
}
@@ -252,7 +250,10 @@ namespace Serein.NodeFlow.Env
});
SetStartNode(projectData.StartNode);
- OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs());
+ UIContextOperation?.Invoke(() =>
+ {
+ OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs());
+ });
}
private bool TryAddNode(NodeModelBase nodeModel)
@@ -394,7 +395,10 @@ namespace Serein.NodeFlow.Env
public void MoveNode(string nodeGuid, double x, double y)
{
- OnNodeMoved.Invoke(new NodeMovedEventArgs(nodeGuid, x, y));
+ UIContextOperation.Invoke(() =>
+ {
+ OnNodeMoved.Invoke(new NodeMovedEventArgs(nodeGuid, x, y));
+ });
_ = msgClient.SendAsync(EnvMsgTheme.MoveNode,
new
{
@@ -404,13 +408,14 @@ namespace Serein.NodeFlow.Env
});
}
+
public void SetStartNode(string nodeGuid)
{
_ = msgClient.SendAsync(EnvMsgTheme.SetStartNode, new
{
nodeGuid
});
- // UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, StartNode.Guid)));
+ //UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(nodeGuid,nodeGuid)));
}
public async Task ConnectNodeAsync(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType)
@@ -440,13 +445,22 @@ namespace Serein.NodeFlow.Env
mdInfo = methodDetailsInfo,
});
- MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息
+ MethodDetails? methodDetails = null;
+ if (!string.IsNullOrEmpty(nodeInfo.MethodName))
+ {
+ MethodDetailss.TryGetValue(nodeInfo.MethodName, out methodDetails);// 加载远程环境时尝试获取方法信息
+ }
+
+ //MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息
var nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 远程环境下加载节点
nodeModel.LoadInfo(nodeInfo);
TryAddNode(nodeModel);
// 通知UI更改
- OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
+ UIContextOperation.Invoke(() =>
+ {
+ OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeModel, position));
+ });
return nodeInfo;
}
@@ -460,10 +474,13 @@ namespace Serein.NodeFlow.Env
});
if (result)
{
- OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid,
+ UIContextOperation.Invoke(() =>
+ {
+ OnNodeConnectChange?.Invoke(new NodeConnectChangeEventArgs(fromNodeGuid,
toNodeGuid,
connectionType,
NodeConnectChangeEventArgs.ConnectChangeType.Remote));
+ });
}
return result;
}
@@ -476,7 +493,10 @@ namespace Serein.NodeFlow.Env
});
if (result)
{
- OnNodeRemove?.Invoke(new NodeRemoveEventArgs(nodeGuid));
+ UIContextOperation.Invoke(() =>
+ {
+ OnNodeRemove?.Invoke(new NodeRemoveEventArgs(nodeGuid));
+ });
}
else
{
diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs
index 327ebb4..38cdb85 100644
--- a/NodeFlow/Model/CompositeConditionNode.cs
+++ b/NodeFlow/Model/CompositeConditionNode.cs
@@ -4,20 +4,38 @@ using Serein.Library.Api;
namespace Serein.NodeFlow.Model
{
+
///
/// 组合条件节点(用于条件区域)
///
- public class CompositeConditionNode : NodeModelBase
+ [NodeProperty(ValuePath = NodeValuePath.Node)]
+ public partial class CompositeConditionNode : NodeModelBase
+ {
+ ///
+ /// 条件节点集合
+ ///
+ [PropertyInfo]
+ private List _conditionNodes;
+ }
+
+
+ ///
+ /// 组合条件节点(用于条件区域)
+ ///
+ public partial class CompositeConditionNode : NodeModelBase
{
public CompositeConditionNode(IFlowEnvironment environment):base(environment)
{
}
- public List ConditionNodes { get; } = [];
public void AddNode(SingleConditionNode node)
{
+ if(ConditionNodes is null)
+ {
+ ConditionNodes = new List();
+ }
ConditionNodes.Add(node);
MethodDetails ??= node.MethodDetails;
}
@@ -93,6 +111,7 @@ namespace Serein.NodeFlow.Model
ParameterData = parameterData.ToArray(),
ErrorNodes = errorNodes.ToArray(),
ChildNodeGuids = ConditionNodes.Select(node => node.Guid).ToArray(),
+ Position = Position,
};
}
diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs
index a4ad8f5..c9b9738 100644
--- a/NodeFlow/Model/SingleConditionNode.cs
+++ b/NodeFlow/Model/SingleConditionNode.cs
@@ -1,35 +1,51 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils.SereinExpression;
+using System.ComponentModel;
namespace Serein.NodeFlow.Model
{
///
/// 条件节点(用于条件控件)
///
- public class SingleConditionNode : NodeModelBase
+ [NodeProperty(ValuePath = NodeValuePath.Node)]
+ public partial class SingleConditionNode : NodeModelBase
{
- public SingleConditionNode(IFlowEnvironment environment):base(environment)
- {
-
- }
///
/// 是否为自定义参数
///
- public bool IsCustomData { get; set; }
+ [PropertyInfo(IsNotification = true)]
+ private bool _isCustomData;
+
///
/// 自定义参数值
///
- public object? CustomData { get; set; }
+ [PropertyInfo(IsNotification = true)]
+
+ private object? _customData;
///
/// 条件表达式
///
+ [PropertyInfo(IsNotification = true)]
+ private string _expression;
- public string Expression { get; set; }
+ }
+ public partial class SingleConditionNode : NodeModelBase
+ {
+ public SingleConditionNode(IFlowEnvironment environment):base(environment)
+ {
+ this.IsCustomData = false;
+ this.CustomData = null;
+ this.Expression = "PASS";
+ }
- //public override object? Executing(IDynamicContext context)
+ ///
+ /// 重写节点的方法执行
+ ///
+ ///
+ ///
public override Task ExecutingAsync(IDynamicContext context)
{
// 接收上一节点参数or自定义参数内容
@@ -94,46 +110,20 @@ namespace Serein.NodeFlow.Model
public override NodeModelBase LoadInfo(NodeInfo nodeInfo)
{
var node = this;
- if (node != null)
+ node.Guid = nodeInfo.Guid;
+ this.Position = nodeInfo.Position;// 加载位置信息
+ for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
{
- node.Guid = nodeInfo.Guid;
- for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
- {
- Parameterdata? pd = nodeInfo.ParameterData[i];
- node.IsCustomData = pd.State;
- node.CustomData = pd.Value;
- node.Expression = pd.Expression;
+ Parameterdata? pd = nodeInfo.ParameterData[i];
+ node.IsCustomData = pd.State;
+ node.CustomData = pd.Value;
+ node.Expression = pd.Expression;
- }
}
return this;
}
- //public override void Execute(DynamicContext context)
- //{
- // CurrentState = Judge(context, base.MethodDetails);
- //}
-
- //private bool Judge(DynamicContext context, MethodDetails md)
- //{
- // try
- // {
- // if (DelegateCache.GlobalDicDelegates.TryGetValue(md.MethodName, out Delegate del))
- // {
- // object[] parameters = GetParameters(context, md);
- // var temp = del.DynamicInvoke(parameters);
- // //context.GetData(GetDyPreviousKey());
- // return (bool)temp;
- // }
- // }
- // catch (Exception ex)
- // {
- // Debug.Write(ex.Message);
- // }
- // return false;
- //}
-
-
+
}
diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs
index d799c42..0af3db8 100644
--- a/NodeFlow/Model/SingleExpOpNode.cs
+++ b/NodeFlow/Model/SingleExpOpNode.cs
@@ -1,23 +1,32 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils.SereinExpression;
+using System.Reactive;
namespace Serein.NodeFlow.Model
{
///
/// Expression Operation - 表达式操作
///
- public class SingleExpOpNode : NodeModelBase
+ [NodeProperty(ValuePath = NodeValuePath.Node)]
+ public partial class SingleExpOpNode : NodeModelBase
{
- public SingleExpOpNode(IFlowEnvironment environment) : base(environment)
- {
-
- }
///
/// 表达式
///
- public string Expression { get; set; }
+ [PropertyInfo(IsNotification = true)]
+ private string _expression;
+ }
+
+
+
+ public partial class SingleExpOpNode : NodeModelBase
+ {
+ public SingleExpOpNode(IFlowEnvironment environment) : base(environment)
+ {
+
+ }
//public override async Task Executing(IDynamicContext context)
public override Task ExecutingAsync(IDynamicContext context)
@@ -31,7 +40,7 @@ namespace Serein.NodeFlow.Model
object? result = null;
if (isChange)
{
- result = newData;
+ result = newData;
}
else
{
@@ -52,7 +61,7 @@ namespace Serein.NodeFlow.Model
public override Parameterdata[] GetParameterdatas()
{
- return [new Parameterdata{ Expression = Expression}];
+ return [new Parameterdata { Expression = Expression }];
}
@@ -60,13 +69,11 @@ namespace Serein.NodeFlow.Model
public override NodeModelBase LoadInfo(NodeInfo nodeInfo)
{
var node = this;
- if (node != null)
+ this.Position = nodeInfo.Position;// 加载位置信息
+ node.Guid = nodeInfo.Guid;
+ for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
{
- node.Guid = nodeInfo.Guid;
- for (int i = 0; i < nodeInfo.ParameterData.Length; i++)
- {
- node.Expression = nodeInfo.ParameterData[i].Expression;
- }
+ node.Expression = nodeInfo.ParameterData[i].Expression;
}
return this;
}
diff --git a/NodeFlow/Serein.NodeFlow.csproj b/NodeFlow/Serein.NodeFlow.csproj
index 881f2d1..51e7c4b 100644
--- a/NodeFlow/Serein.NodeFlow.csproj
+++ b/NodeFlow/Serein.NodeFlow.csproj
@@ -1,7 +1,7 @@
- 1.0.16
+ 1.0.17
net8.0
enable
enable
@@ -16,7 +16,10 @@
MIT
true
True
-
+
+ true
+ true
+ .\obj\g
@@ -63,6 +66,8 @@
+
+
diff --git a/NodeFlow/Tool/ObjDynamicCreateHelper.cs b/NodeFlow/Tool/ObjDynamicCreateHelper.cs
index d269cf0..ad1f9bc 100644
--- a/NodeFlow/Tool/ObjDynamicCreateHelper.cs
+++ b/NodeFlow/Tool/ObjDynamicCreateHelper.cs
@@ -6,7 +6,8 @@ namespace Serein.NodeFlow.Tool
{
public class ObjDynamicCreateHelper
- {// 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式)
+ {
+ // 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式)
static Dictionary typeCache = new Dictionary();
public static object Resolve(Dictionary properties, string typeName)
diff --git a/Serein.Library.MyGenerator/Attribute.cs b/Serein.Library.MyGenerator/Attribute.cs
new file mode 100644
index 0000000..6f9bd35
--- /dev/null
+++ b/Serein.Library.MyGenerator/Attribute.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+
+namespace Serein.Library
+{
+
+ ///
+ /// 通过枚举来区分该怎么生成代码
+ ///
+ public enum NodeValuePath
+ {
+ ///
+ /// 默认值
+ ///
+ None,
+ ///
+ /// 节点本身
+ ///
+ Node,
+ ///
+ /// 节点对应的方法
+ ///
+ Method,
+ ///
+ /// 节点方法对应的入参
+ ///
+ Parameter,
+ ///
+ /// 节点的调试设置
+ ///
+ DebugSetting,
+
+ }
+
+
+ ///
+ /// 标识一个类中的某些字段需要生成相应代码
+ ///
+ [AttributeUsage(AttributeTargets.Class, Inherited = true)]
+ public sealed class NodePropertyAttribute : Attribute
+ {
+ ///
+ /// 属性路径
+ /// CustomNode : 自定义节点
+ ///
+ public NodeValuePath ValuePath = NodeValuePath.None;
+ }
+
+ ///
+ /// 自动生成环境的属性
+ ///
+ [AttributeUsage(AttributeTargets.Field, Inherited = true)]
+ public sealed class PropertyInfoAttribute : Attribute
+ {
+ ///
+ /// 是否通知UI
+ ///
+ public bool IsNotification = false;
+ ///
+ /// 是否使用Console.WriteLine打印
+ ///
+ public bool IsPrint = false;
+ ///
+ /// 是否禁止参数进行修改(初始化后不能再通过 Setter 修改)
+ ///
+ public bool IsProtection = false;
+ }
+
+}
diff --git a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs
index 0e4cbbf..860ff5b 100644
--- a/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs
+++ b/Serein.Library.MyGenerator/ParameterDetailsPropertyGenerator.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
@@ -20,6 +21,8 @@ namespace Serein.Library.NodeGenerator
[Generator]
public class MyPropertyGenerator : IIncrementalGenerator
{
+ internal static NodePropertyAttribute NodeProperty = new NodePropertyAttribute();
+ internal static PropertyInfoAttribute PropertyInfo = new PropertyInfoAttribute();
///
/// 初始化生成器,定义需要执行的生成逻辑。
@@ -48,7 +51,7 @@ namespace Serein.Library.NodeGenerator
// 检查类的特性列表,看看是否存在 MyClassAttribute
if (classDeclaration.AttributeLists
.SelectMany(attrList => attrList.Attributes)
- .Any(attr => semanticModel.GetSymbolInfo(attr).Symbol?.ContainingType.Name == "AutoPropertyAttribute"))
+ .Any(attr => semanticModel.GetSymbolInfo(attr).Symbol?.ContainingType.Name == nameof(NodePropertyAttribute)))
{
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration); // 获取类的符号
var classInfo = classSymbol.BuildCacheOfClass();
@@ -127,7 +130,7 @@ namespace Serein.Library.NodeGenerator
sb.AppendLine($" public partial class {className} : System.ComponentModel.INotifyPropertyChanged");
sb.AppendLine(" {");
- var path = classInfo["AutoPropertyAttribute"]["ValuePath"];
+ var path = classInfo[nameof(NodePropertyAttribute)]["ValuePath"];
//
@@ -156,10 +159,10 @@ namespace Serein.Library.NodeGenerator
var propertyName = field.ToPropertyName(); // 转为合适的属性名称
var attributeInfo = fieldKV.Value; // 缓存的特性信息
+ var isProtection = attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsProtection), "true"); // 是否为保护字段
- var isProtection = attributeInfo.Search("PropertyInfo", "IsProtection", "true"); // 是否为保护字段
-
-
+
+
// 生成 getter / setter
sb.AppendLine(leadingTrivia);
sb.AppendLine($" public {fieldType} {propertyName}");
@@ -169,34 +172,40 @@ namespace Serein.Library.NodeGenerator
sb.AppendLine(" {");
sb.AppendLine($" if ({fieldName} {(isProtection ? "== default" : "!= value")})"); // 非保护的Setter
sb.AppendLine(" {");
- if (attributeInfo.Search("PropertyInfo", "IsPrint", "true")) // 是否打印
+ if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsPrint), "true")) // 是否打印
{
sb.AddCode(5, $"Console.WriteLine({fieldName});");
}
- if (attributeInfo.Search("PropertyInfo", "IsNotification", "true")) // 是否通知
+ if (attributeInfo.Search(nameof(PropertyInfo), nameof(PropertyInfo.IsNotification), "true")) // 是否通知
{
- if (classInfo.ExitsPath("NodeModelBase"))
+ if (classInfo.ExitsPath(nameof(NodeValuePath.Node))) // 节点 or 自定义节点
{
- sb.AddCode(5, $"this.env?.NotificationNodeValueChangeAsync(this.Guid, .nameof({propertyName}), value);");
+ sb.AddCode(5, $"((NodeModelBase)this).Env?.NotificationNodeValueChangeAsync(this.Guid, nameof({propertyName}), value); // 通知远程环境属性发生改变了");
}
- else if (classInfo.ExitsPath("MethodDetails"))
+ else if (classInfo.ExitsPath(nameof(NodeValuePath.Method))) // 节点方法详情
{
- sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.\"+nameof({propertyName}), value);");
+ sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.\"+nameof({propertyName}), value); // 通知远程环境属性发生改变了");
}
- else if (classInfo.ExitsPath("ParameterDetails"))
+ else if (classInfo.ExitsPath(nameof(NodeValuePath.Parameter))) // 节点方法入参参数描述
{
- sb.AddCode(5, "nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.ParameterDetailss[\"+$\"{Index}\"+\"]." + $"\"+nameof({propertyName}),value);");
+ sb.AddCode(5, "nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"MethodDetails.ParameterDetailss[\"+$\"{Index}\"+\"]." + $"\"+nameof({propertyName}),value); // 通知远程环境属性发生改变了");
}
- else if (classInfo.ExitsPath("NodeDebugSetting"))
+ else if (classInfo.ExitsPath(nameof(NodeValuePath.DebugSetting))) // 节点的调试信息
{
- sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"DebugSetting.\"+nameof({propertyName}), value);");
+ sb.AddCode(5, $"nodeModel?.Env?.NotificationNodeValueChangeAsync(nodeModel.Guid, \"DebugSetting.\"+nameof({propertyName}), value); // 通知远程环境属性发生改变了");
}
}
- sb.AppendLine($" {fieldName} = value;");
- sb.AppendLine($" OnPropertyChanged(); // 先更改属性,然后通知属性发生改变了");
+ sb.AppendLine($" SetProperty<{fieldType}>(ref {fieldName}, value); // 通知UI属性发生改变了");
+ //sb.AppendLine($" {fieldName} = value;");
+ //sb.AppendLine($" OnPropertyChanged(); // 通知UI属性发生改变了");
sb.AppendLine(" }");
sb.AppendLine(" }");
sb.AppendLine(" }"); // 属性的结尾大括号
+ //if (!isProtection && field.TryGetDefaultValue(out var defaultValue))
+ //{
+
+ // sb.AppendLine($" }} = {defaultValue}");
+ //}
}
@@ -206,19 +215,29 @@ namespace Serein.Library.NodeGenerator
sb.AppendLine(" /// ");
sb.AppendLine(" public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;");
- sb.AppendLine(" /// ");
- sb.AppendLine(" /// 略");
- sb.AppendLine(" /// 此方法为自动生成");
+ sb.AppendLine(" protected void SetProperty(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) ");
+ sb.AppendLine(" { ");
+ sb.AppendLine(" if (Equals(storage, value)) ");
+ sb.AppendLine(" { ");
+ sb.AppendLine(" return; ");
+ sb.AppendLine(" } ");
+ sb.AppendLine(" ");
+ sb.AppendLine(" storage = value; ");
+ sb.AppendLine(" OnPropertyChanged(propertyName); ");
+ sb.AppendLine(" } ");
+
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" /// 略 ");
+ sb.AppendLine(" /// 此方法为自动生成 ");
sb.AppendLine(" /// ");
- sb.AppendLine(" /// ");
- sb.AppendLine(" ");
- sb.AppendLine(" protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null)");
+ sb.AppendLine(" /// ");
+ sb.AppendLine(" ");
+ sb.AppendLine(" protected void OnPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) ");
sb.AppendLine(" {");
//sb.AppendLine(" Console.WriteLine(\"测试:\"+ propertyName);");
- sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));");
+ sb.AppendLine(" PropertyChanged?.Invoke(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); ");
sb.AppendLine(" }");
-
}
finally
{
@@ -347,7 +366,12 @@ namespace Serein.Library.NodeGenerator
{
var key = cata.Key;
var value = cata.Value.Value;
- if (!attributeInfo.ContainsKey(key))
+ if (nameof(NodePropertyAttribute).Equals(attributeName))
+ {
+ string literal = Enum.GetName(typeof(NodeValuePath), cata.Value.Value);
+ attributeInfo.Add(key, literal);
+ }
+ else
{
attributeInfo.Add(key, value);
}
@@ -374,6 +398,29 @@ namespace Serein.Library.NodeGenerator
var propertyName = fieldName.StartsWith("_") ? char.ToUpper(fieldName[1]) + fieldName.Substring(2) : char.ToUpper(fieldName[0]) + fieldName.Substring(1); // 创建属性名称
return propertyName;
}
+
+ ///
+ /// 判断字段是否有默认值
+ ///
+ ///
+ ///
+ ///
+ public static bool TryGetDefaultValue(this FieldDeclarationSyntax field ,out string defaultValue)
+ {
+ if (field.Declaration.Variables.First().Initializer != null)
+ {
+ defaultValue = field.Declaration.Variables.First().Initializer.Value.ToString();
+ return true;
+ }
+ else
+ {
+ defaultValue = null;
+ return false;
+ }
+ }
+
+
+
///
/// 判断字段是否为只读
///
@@ -500,14 +547,13 @@ namespace Serein.Library.NodeGenerator
public static bool ExitsPath(this Dictionary> classInfo, string valuePath)
{
- // var path = classInfo["AutoPropertyAttribute"]["ValuePath"];
-
- if (!classInfo.TryGetValue("AutoPropertyAttribute", out var keyValuePairs))
+
+ if (!classInfo.TryGetValue(nameof(NodePropertyAttribute), out var keyValuePairs))
{
return false;
}
- if (!keyValuePairs.TryGetValue("ValuePath", out var value))
+ if (!keyValuePairs.TryGetValue(nameof(MyPropertyGenerator.NodeProperty.ValuePath), out var value))
{
return false;
}
diff --git a/Serein.Library.MyGenerator/Properties/launchSettings.json b/Serein.Library.MyGenerator/Properties/launchSettings.json
index 93ccb52..9e26dfe 100644
--- a/Serein.Library.MyGenerator/Properties/launchSettings.json
+++ b/Serein.Library.MyGenerator/Properties/launchSettings.json
@@ -1,7 +1 @@
-{
- "profiles": {
- "配置文件 2": {
- "commandName": "DebugRoslynComponent"
- }
- }
-}
\ No newline at end of file
+{}
\ No newline at end of file
diff --git a/Serein.Library.MyGenerator/Serein.Library.NodeGenerator.csproj b/Serein.Library.MyGenerator/Serein.Library.NodeGenerator.csproj
index d648318..125db7c 100644
--- a/Serein.Library.MyGenerator/Serein.Library.NodeGenerator.csproj
+++ b/Serein.Library.MyGenerator/Serein.Library.NodeGenerator.csproj
@@ -3,6 +3,7 @@
netstandard2.0
true
+
D:\Project\C#\DynamicControl\SereinFlow\.Output
True
SereinFow
@@ -12,7 +13,6 @@
MIT
True
true
-
diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs
index 2e8b8b3..6903f23 100644
--- a/WorkBench/App.xaml.cs
+++ b/WorkBench/App.xaml.cs
@@ -10,7 +10,9 @@ namespace Serein.Workbench
///
public partial class App : Application
{
+#if DEBUG
+#endif
public static SereinProjectData? FlowProjectData { get; set; }
public static string FileDataPath { get; set; } = "";
@@ -66,7 +68,7 @@ namespace Serein.Workbench
}
#if DEBUG
- else if(1 == 1)
+ else if(1 == 11)
{
//string filePath = @"F:\临时\project\new project.dnf";
diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs
index e956b7a..759c7e5 100644
--- a/WorkBench/MainWindow.xaml.cs
+++ b/WorkBench/MainWindow.xaml.cs
@@ -461,6 +461,11 @@ namespace Serein.Workbench
return;
}
+ if(nodeModelBase is null)
+ {
+ Console.WriteLine("OnNodeCreateEvent事件接收到意外的返回值");
+ return;
+ }
// MethodDetails methodDetailss = eventArgs.MethodDetailss;
PositionOfUI position = eventArgs.Position;
@@ -577,15 +582,15 @@ namespace Serein.Workbench
{
string nodeGuid = eventArgs.NodeGuid;
if (!TryGetControl(nodeGuid, out var nodeControl)) return;
- if (eventArgs.Class == InterruptClass.None)
- {
- nodeControl.ViewModel.IsInterrupt = false;
- }
- else
- {
- nodeControl.ViewModel.IsInterrupt = true;
- }
+ //if (eventArgs.Class == InterruptClass.None)
+ //{
+ // nodeControl.ViewModel.IsInterrupt = false;
+ //}
+ //else
+ //{
+ // nodeControl.ViewModel.IsInterrupt = true;
+ //}
foreach (var menuItem in nodeControl.ContextMenu.Items)
{
@@ -1203,35 +1208,48 @@ namespace Serein.Workbench
///
///
///
- private async void FlowChartCanvas_Drop(object sender, DragEventArgs e)
+ private void FlowChartCanvas_Drop(object sender, DragEventArgs e)
{
- var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
- PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
- if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
+ try
{
- if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
+ var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
+ PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
+ if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
{
- await EnvDecorator.CreateNodeAsync(nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象
- }
- }
- else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas))
- {
- if (e.Data.GetData(MouseNodeType.CreateBaseNodeInCanvas) is Type droppedType)
- {
- NodeControlType nodeControlType = droppedType switch
+ if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
{
- Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) => NodeControlType.ConditionRegion, // 条件区域
- Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition,
- Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp,
- _ => NodeControlType.None,
- };
- if(nodeControlType != NodeControlType.None)
- {
- await EnvDecorator.CreateNodeAsync(nodeControlType, position); // 创建基础节点对象
+ Task.Run(async () =>
+ {
+ await EnvDecorator.CreateNodeAsync(nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象
+ });
}
}
+ else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas))
+ {
+ if (e.Data.GetData(MouseNodeType.CreateBaseNodeInCanvas) is Type droppedType)
+ {
+ NodeControlType nodeControlType = droppedType switch
+ {
+ Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) => NodeControlType.ConditionRegion, // 条件区域
+ Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition,
+ Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp,
+ _ => NodeControlType.None,
+ };
+ if (nodeControlType != NodeControlType.None)
+ {
+ Task.Run(async () =>
+ {
+ await EnvDecorator.CreateNodeAsync(nodeControlType, position); // 创建基础节点对象
+ });
+ }
+ }
+ }
+ e.Handled = true;
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine(ex);
}
- e.Handled = true;
}
///
@@ -1897,7 +1915,7 @@ namespace Serein.Workbench
//Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
foreach (var node in selectNodeControls)
{
- node.ViewModel.IsSelect =true;
+ //node.ViewModel.IsSelect =true;
// node.ViewModel.CancelSelect();
node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
node.BorderThickness = new Thickness(4);
@@ -1908,7 +1926,7 @@ namespace Serein.Workbench
IsSelectControl = false;
foreach (var nodeControl in selectNodeControls)
{
- nodeControl.ViewModel.IsSelect = false;
+ //nodeControl.ViewModel.IsSelect = false;
nodeControl.BorderBrush = Brushes.Black;
nodeControl.BorderThickness = new Thickness(0);
if (nodeControl.ViewModel.NodeModel.IsStart)
diff --git a/WorkBench/MainWindowViewModel.cs b/WorkBench/MainWindowViewModel.cs
index e9565db..3a2d45e 100644
--- a/WorkBench/MainWindowViewModel.cs
+++ b/WorkBench/MainWindowViewModel.cs
@@ -40,6 +40,7 @@ namespace Serein.Workbench
else
{
FlowEnvironment = new FlowEnvironmentDecorator(uIContextOperation);
+ //_ = FlowEnvironment.StartRemoteServerAsync();
this.window = window;
}
}
diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs
index 00890e3..56af0d8 100644
--- a/WorkBench/Node/NodeControlViewModelBase.cs
+++ b/WorkBench/Node/NodeControlViewModelBase.cs
@@ -9,83 +9,97 @@ namespace Serein.Workbench.Node.ViewModel
public NodeControlViewModelBase(NodeModelBase nodeModel)
{
NodeModel = nodeModel;
- MethodDetails = NodeModel.MethodDetails;
// 订阅来自 NodeModel 的通知事件
}
+
+ private NodeModelBase _nodeModelBase;
///
/// 对应的节点实体类
///
- internal NodeModelBase NodeModel { get; }
-
-
- private bool isSelect;
- ///
- /// 表示节点控件是否被选中
- ///
- internal bool IsSelect
+ public NodeModelBase NodeModel
{
- get => isSelect;
- set
+ get => _nodeModelBase; set
{
- isSelect = value;
- OnPropertyChanged();
+ if (value != null)
+ {
+ _nodeModelBase = value;
+ OnPropertyChanged();
+ }
}
}
+
+ //private bool isSelect;
+ /////
+ ///// 表示节点控件是否被选中
+ /////
+ //internal bool IsSelect
+ //{
+ // get => isSelect;
+ // set
+ // {
+ // isSelect = value;
+ // OnPropertyChanged();
+ // }
+ //}
+
+
+
+ //private bool isInterrupt;
+ ///////
+ /////// 控制中断状态的视觉效果
+ ///////
+ //public bool IsInterrupt
+ //{
+ // get => NodeModel.DebugSetting.IsInterrupt;
+ // set
+ // {
+ // NodeModel.DebugSetting.IsInterrupt = value;
+ // OnPropertyChanged();
+ // }
+ //}
+
+ public event PropertyChangedEventHandler? PropertyChanged;
+ protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ //Console.WriteLine(propertyName);
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+
+
///
/// 使节点获得中断能力(以及是否启用节点)
///
- public NodeDebugSetting DebugSetting
- {
- get => NodeModel.DebugSetting;
- set
- {
- if (value != null)
- {
- NodeModel.DebugSetting = value;
- OnPropertyChanged();
- }
- }
- }
+ //public NodeDebugSetting DebugSetting
+ //{
+ // get => Node.DebugSetting;
+ // set
+ // {
+ // if (value != null)
+ // {
+ // Node.DebugSetting = value;
+ // OnPropertyChanged();
+ // }
+ // }
+ //}
///
/// 使节点能够表达方法信息
///
- public MethodDetails MethodDetails
- {
- get => NodeModel.MethodDetails;
- set
- {
- if(value != null)
- {
- NodeModel.MethodDetails = value;
- OnPropertyChanged();
- }
- }
- }
+ //public MethodDetails MethodDetails
+ //{
+ // get => Node.MethodDetails;
+ // set
+ // {
+ // if(value != null)
+ // {
+ // Node.MethodDetails = value;
+ // OnPropertyChanged();
+ // }
+ // }
+ //}
- private bool isInterrupt;
- ///
- /// 控制中断状态的视觉效果
- ///
- public bool IsInterrupt
- {
- get => isInterrupt;
- set
- {
- isInterrupt = value;
- OnPropertyChanged();
- }
- }
-
- public event PropertyChangedEventHandler? PropertyChanged;
- protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
-
-
}
}
diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml
index f6762b4..3cce56b 100644
--- a/WorkBench/Node/View/ActionNodeControl.xaml
+++ b/WorkBench/Node/View/ActionNodeControl.xaml
@@ -21,17 +21,20 @@
-
+
-
+
+
+
+