From 51bdbab4d126229700e7ec23ab2733248f9fcf7a Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Fri, 27 Sep 2024 23:47:25 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=86=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=A0=91=E9=A2=84=E8=A7=88=E3=80=81=E8=8A=82=E7=82=B9=E5=AE=9A?= =?UTF-8?q?=E4=BD=8D=EF=BC=8C=E5=AE=B9=E5=99=A8=E5=AF=B9=E8=B1=A1=E9=A2=84?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowEnvironment.cs | 77 ++--- Net461DllTest/Flow/LogicControl.cs | 6 +- Net461DllTest/Flow/ViewLogicControl.cs | 12 +- Net461DllTest/Signal/OrderSignal.cs | 11 +- NodeFlow/FlowEnvironment.cs | 15 +- NodeFlow/FlowStarter.cs | 8 +- NodeFlow/Tool/ExpressionHelper.cs | 1 + NodeFlow/Tool/ObjDynamicCreateHelper.cs | 308 ++++++++++++++++++ .../SereinExpression/SereinConditionParser.cs | 20 +- .../SerinExpressionEvaluator.cs | 181 ++++++++-- WorkBench/App.xaml.cs | 4 + WorkBench/LogWindow.xaml | 2 +- WorkBench/MainWindow.xaml | 48 ++- WorkBench/MainWindow.xaml.cs | 231 +++++++++++-- WorkBench/Node/NodeControlViewModelBase.cs | 2 +- WorkBench/Themes/NodeTreeItemViewControl.xaml | 17 +- .../Themes/NodeTreeItemViewControl.xaml.cs | 31 +- 17 files changed, 793 insertions(+), 181 deletions(-) create mode 100644 NodeFlow/Tool/ObjDynamicCreateHelper.cs diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 8662879..f02f3be 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -345,32 +345,23 @@ namespace Serein.Library.Api } public string Key { get; private set; } public object Instance { get; private set; } - } - //public class IOCMembersChangedEventArgs : FlowEventArgs - //{ - // //public enum EventType - // //{ - // // /// - // // /// 登记了类型 - // // /// - // // Registered, - // // /// - // // /// 构建了类型 - // // /// - // // Completeuild, - // //} - // public IOCMembersChangedEventArgs(Type[] types, object[] dependencies, object[] unfinishedDependencies) - // { - // this.Types = types; - // this.Dependencies = dependencies; - // this.UnfinishedDependencies = unfinishedDependencies; - // } - // public Type[] Types { get; protected set; } - // public object[] Dependencies { get; private set; } - // public object[] UnfinishedDependencies { get; private set; } - //} + /// + /// 节点需要定位 + /// + /// + public delegate void NodeLocatedHandler(NodeLocatedEventArgs eventArgs); + + public class NodeLocatedEventArgs : FlowEventArgs + { + public NodeLocatedEventArgs(string nodeGuid) + { + NodeGuid = nodeGuid; + } + public string NodeGuid { get; private set; } + } + public interface IFlowEnvironment { #region 属性 @@ -447,6 +438,11 @@ namespace Serein.Library.Api event IOCMembersChangedHandler OnIOCMembersChanged; + /// + /// 节点需要定位 + /// + event NodeLocatedHandler OnNodeLocate; + #endregion @@ -461,7 +457,7 @@ namespace Serein.Library.Api //bool TryGetNodeData(string methodName, out NodeData node); - #region Workbench + #region 环境基础接口 /// /// 保存当前项目 @@ -501,8 +497,6 @@ namespace Serein.Library.Api /// void Exit(); - - /// /// 设置流程起点节点 /// @@ -549,21 +543,7 @@ namespace Serein.Library.Api /// /// bool AddInterruptExpression(string key, string expression); - /// - /// 添加作用于指定节点的中断表达式 - /// - /// - /// - /// - // bool AddInterruptExpression(string nodeGuid,string expression); - - // - // 设置节点数据监视状态 - // - // 需要监视的节点Guid - // 是否监视 - // void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor); - + /// /// 监视指定对象 /// @@ -591,7 +571,7 @@ namespace Serein.Library.Api #endregion - #region Start + #region 启动器调用 /// /// 流程启动器调用,监视数据更新通知 @@ -609,6 +589,17 @@ namespace Serein.Library.Api void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type); + #endregion + + + #region UI视觉 + + /// + /// 节点定位 + /// + /// + void NodeLocated(string nodeGuid); + #endregion } } diff --git a/Net461DllTest/Flow/LogicControl.cs b/Net461DllTest/Flow/LogicControl.cs index 0562ac4..58a46b2 100644 --- a/Net461DllTest/Flow/LogicControl.cs +++ b/Net461DllTest/Flow/LogicControl.cs @@ -17,10 +17,10 @@ using System.Threading.Tasks; namespace Net461DllTest.Flow { - [DynamicFlow] // 标记该类存在节点方法 + [DynamicFlow] public class LogicControl { - [AutoInjection] // 标记该属性为依赖项,需要注入 + [AutoInjection] public PlcDevice MyPlc { get; set; } @@ -58,7 +58,7 @@ namespace Net461DllTest.Flow #region 触发器 [NodeAction(NodeType.Flipflop, "等待信号触发", ReturnType = typeof(int))] - public async Task WaitTask(OrderSignal order = OrderSignal.A) + public async Task WaitTask(OrderSignal order = OrderSignal.Command_1) { try { diff --git a/Net461DllTest/Flow/ViewLogicControl.cs b/Net461DllTest/Flow/ViewLogicControl.cs index ef15caa..3bc4435 100644 --- a/Net461DllTest/Flow/ViewLogicControl.cs +++ b/Net461DllTest/Flow/ViewLogicControl.cs @@ -19,12 +19,13 @@ namespace Net461DllTest.Flow { private List
forms = new List(); - public void OpenView(Form form) + public void OpenView(Form form, bool isTop) { form.FormClosing += (s, e) => { // 关闭窗体时执行一些关于逻辑层的操作 }; + form.TopMost = isTop; form.Show(); forms.Add(form); } @@ -67,21 +68,20 @@ namespace Net461DllTest.Flow [NodeAction(NodeType.Action, "打开窗体(指定枚举值)")] - public void OpenForm(IDynamicContext context, FromId fromId = FromId.None) + public void OpenForm(IDynamicContext context, FromId fromId = FromId.None, bool isTop = true) { var fromType = EnumHelper.GetBoundValue(fromId, attr => attr.Value); if (fromType is null) return; if (context.Env.IOC.Instantiate(fromType) is Form form) { - ViewManagement.OpenView(form); - + ViewManagement.OpenView(form, isTop); } } [NodeAction(NodeType.Action, "打开窗体(使用转换器)")] - public void OpenForm2([EnumTypeConvertor(typeof(FromId))] Form form) + public void OpenForm2([EnumTypeConvertor(typeof(FromId))] Form form, bool isTop = true) { - ViewManagement.OpenView(form); + ViewManagement.OpenView(form, isTop); } diff --git a/Net461DllTest/Signal/OrderSignal.cs b/Net461DllTest/Signal/OrderSignal.cs index e986f14..6ad3bf6 100644 --- a/Net461DllTest/Signal/OrderSignal.cs +++ b/Net461DllTest/Signal/OrderSignal.cs @@ -10,12 +10,9 @@ namespace Net461DllTest.Signal { public enum OrderSignal { - A, - B, - C, - D, - E, - F, - G + View_1, + View_2, + Command_1, + Command_2, } } diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index 4c7410e..24b6215 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -122,6 +122,10 @@ namespace Serein.NodeFlow ///
public event IOCMembersChangedHandler OnIOCMembersChanged; + /// + /// 节点需要定位 + /// + public event NodeLocatedHandler OnNodeLocate; #endregion #region 属性 @@ -1037,7 +1041,16 @@ namespace Serein.NodeFlow #endregion - #region 网络交互 + #region 视觉效果 + + /// + /// 定位节点 + /// + /// + public void NodeLocated(string nodeGuid) + { + OnNodeLocate?.Invoke(new NodeLocatedEventArgs(nodeGuid)); + } #endregion diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 1b58a00..e3c3ed9 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -150,10 +150,10 @@ namespace Serein.NodeFlow #region 初始化运行环境的Ioc容器 // 清除节点使用的对象,筛选出需要初始化的方法描述 var thisRuningMds = new List(); - thisRuningMds.AddRange(runNodeMd.Where(md => md is not null)); - thisRuningMds.AddRange(initMethods.Where(md => md is not null)); - thisRuningMds.AddRange(loadingMethods.Where(md => md is not null)); - thisRuningMds.AddRange(exitMethods.Where(md => md is not null)); + thisRuningMds.AddRange(runNodeMd.Where(md => md?.ActingInstanceType is not null)); + thisRuningMds.AddRange(initMethods.Where(md => md?.ActingInstanceType is not null)); + thisRuningMds.AddRange(loadingMethods.Where(md => md?.ActingInstanceType is not null)); + thisRuningMds.AddRange(exitMethods.Where(md => md?.ActingInstanceType is not null)); // .AddRange(initMethods).AddRange(loadingMethods).a foreach (var nodeMd in thisRuningMds) diff --git a/NodeFlow/Tool/ExpressionHelper.cs b/NodeFlow/Tool/ExpressionHelper.cs index 5e75af5..832d86b 100644 --- a/NodeFlow/Tool/ExpressionHelper.cs +++ b/NodeFlow/Tool/ExpressionHelper.cs @@ -673,6 +673,7 @@ namespace Serein.NodeFlow.Tool */ #endregion + #region 暂时不删(已注释) /* /// /// 表达式树构建多个参数,有返回值的方法 diff --git a/NodeFlow/Tool/ObjDynamicCreateHelper.cs b/NodeFlow/Tool/ObjDynamicCreateHelper.cs new file mode 100644 index 0000000..a30fcf2 --- /dev/null +++ b/NodeFlow/Tool/ObjDynamicCreateHelper.cs @@ -0,0 +1,308 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; + + +namespace Serein.NodeFlow.Tool +{ + + public class ObjDynamicCreateHelper + {// 类型缓存,键为类型的唯一名称(可以根据实际需求调整生成方式) + static Dictionary typeCache = new Dictionary(); + + public static object Resolve(Dictionary properties, string typeName) + { + var obj = CreateObjectWithProperties(properties, typeName); + //SetPropertyValues(obj, properties); + return obj; + } + public static bool TryResolve(Dictionary properties, string typeName, out object? result) + { + result = CreateObjectWithProperties(properties, typeName); + bool success = SetPropertyValuesWithValidation(result, properties); + return success; + // 打印赋值结果 + + } + // 递归方法:打印对象属性及类型 + public static void PrintObjectProperties(object obj, string indent = "") + { + var objType = obj.GetType(); + foreach (var prop in objType.GetProperties()) + { + var value = prop.GetValue(obj); + Console.WriteLine($"{indent}{prop.Name} (Type: {prop.PropertyType.Name}): {value}"); + + if (value != null) + { + if (prop.PropertyType.IsArray) // 处理数组类型 + { + var array = (Array)value; + Console.WriteLine($"{indent}{prop.Name} is an array with {array.Length} elements:"); + for (int i = 0; i < array.Length; i++) + { + var element = array.GetValue(i); + if (element != null && element.GetType().IsClass && !(element is string)) + { + Console.WriteLine($"{indent}\tArray[{i}] (Type: {element.GetType().Name}) contains a nested object:"); + PrintObjectProperties(element, indent + "\t\t"); + } + else + { + Console.WriteLine($"{indent}\tArray[{i}] (Type: {element?.GetType().Name}): {element}"); + } + } + } + else if (value.GetType().IsClass && !(value is string)) // 处理嵌套对象 + { + Console.WriteLine($"{indent}{prop.Name} contains a nested object:"); + PrintObjectProperties(value, indent + "\t"); + } + } + } + } + + + + // 方法 1: 创建动态类型及其对象实例 + public static object CreateObjectWithProperties(Dictionary properties, string typeName) + { + // 如果类型已经缓存,直接返回缓存的类型 + if (typeCache.ContainsKey(typeName)) + { + return Activator.CreateInstance(typeCache[typeName]); + } + + // 定义动态程序集和模块 + var assemblyName = new AssemblyName("DynamicAssembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); + + // 定义动态类型 + var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public); + + // 为每个属性名和值添加相应的属性到动态类型中 + foreach (var kvp in properties) + { + string propName = kvp.Key; + object propValue = kvp.Value; + Type propType; + + if (propValue is IList>) // 处理数组类型 + { + var nestedPropValue = (propValue as IList>)[0]; + var nestedType = CreateObjectWithProperties(nestedPropValue, $"{propName}Element"); + propType = nestedType.GetType().MakeArrayType(); // 创建数组类型 + } + else if(propValue is Dictionary nestedProperties) + { + // 如果值是嵌套的字典,递归创建嵌套类型 + propType = CreateObjectWithProperties(nestedProperties, $"{typeName}_{propName}").GetType(); + } + else + { + // 如果是普通类型,使用值的类型 + propType = propValue?.GetType() ?? typeof(object); + } + + // 定义私有字段和公共属性 + var fieldBuilder = typeBuilder.DefineField("_" + propName, propType, FieldAttributes.Private); + var propertyBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, propType, null); + + // 定义 getter 方法 + var getMethodBuilder = typeBuilder.DefineMethod( + "get_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + propType, + Type.EmptyTypes); + + var getIL = getMethodBuilder.GetILGenerator(); + getIL.Emit(OpCodes.Ldarg_0); + getIL.Emit(OpCodes.Ldfld, fieldBuilder); + getIL.Emit(OpCodes.Ret); + + // 定义 setter 方法 + var setMethodBuilder = typeBuilder.DefineMethod( + "set_" + propName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + null, + new Type[] { propType }); + + var setIL = setMethodBuilder.GetILGenerator(); + setIL.Emit(OpCodes.Ldarg_0); + setIL.Emit(OpCodes.Ldarg_1); + setIL.Emit(OpCodes.Stfld, fieldBuilder); + setIL.Emit(OpCodes.Ret); + + // 将 getter 和 setter 方法添加到属性 + propertyBuilder.SetGetMethod(getMethodBuilder); + propertyBuilder.SetSetMethod(setMethodBuilder); + } + + // 创建类型并缓存 + var dynamicType = typeBuilder.CreateType(); + typeCache[typeName] = dynamicType; + + // 创建对象实例 + return Activator.CreateInstance(dynamicType); + } + + // 方法 2: 递归设置对象的属性值 + public static void SetPropertyValues(object obj, Dictionary properties) + { + var objType = obj.GetType(); + + foreach (var kvp in properties) + { + var propInfo = objType.GetProperty(kvp.Key); + object value = kvp.Value; + + // 如果值是嵌套的字典类型,递归处理嵌套对象 + if (value is Dictionary nestedProperties) + { + // 创建嵌套对象 + var nestedObj = Activator.CreateInstance(propInfo.PropertyType); + + // 递归设置嵌套对象的值 + SetPropertyValues(nestedObj, nestedProperties); + + // 将嵌套对象赋值给属性 + propInfo.SetValue(obj, nestedObj); + } + else + { + // 直接赋值给属性 + propInfo.SetValue(obj, value); + } + } + } + // 方法 2: 递归设置对象的属性值(带验证) + + public static bool SetPropertyValuesWithValidation(object obj, Dictionary properties) + { + var objType = obj.GetType(); + bool allSuccessful = true; // 标记是否所有属性赋值成功 + + foreach (var kvp in properties) + { + var propName = kvp.Key; + var propValue = kvp.Value; + + var propInfo = objType.GetProperty(propName); + + if (propInfo == null) + { + // 属性不存在,打印警告并标记失败 + Console.WriteLine($"Warning: 属性 '{propName}' 不存在于类型 '{objType.Name}' 中,跳过赋值。"); + allSuccessful = false; + continue; + } + + // 检查属性类型是否与要赋的值兼容 + var targetType = propInfo.PropertyType; + if (!IsCompatibleType(targetType, propValue)) + { + // 如果类型不兼容,打印错误并标记失败 + Console.WriteLine($"Error: 无法将类型 '{propValue?.GetType().Name}' 赋值给属性 '{propName}' (Type: {targetType.Name}),跳过赋值。"); + allSuccessful = false; + continue; + } + + try + { + // 如果值是一个嵌套对象,递归赋值 + if (propValue is Dictionary nestedProperties) + { + var nestedObj = Activator.CreateInstance(propInfo.PropertyType); + if (SetPropertyValuesWithValidation(nestedObj, nestedProperties)) + { + propInfo.SetValue(obj, nestedObj); + } + else + { + allSuccessful = false; // 嵌套赋值失败 + } + } + else if (propValue is IList> list) // 处理列表 + { + // 获取目标类型的数组元素类型 + var elementType = propInfo.PropertyType.GetElementType(); + var array = Array.CreateInstance(elementType, list.Count); + + for (int i = 0; i < list.Count; i++) + { + var item = Activator.CreateInstance(elementType); + if (SetPropertyValuesWithValidation(item, list[i])) + { + array.SetValue(item, i); + } + else + { + allSuccessful = false; // 赋值失败 + } + } + + propInfo.SetValue(obj, array); + } + else + { + // 直接赋值 + propInfo.SetValue(obj, propValue); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: 为属性 '{propName}' 赋值时发生异常:{ex.Message}"); + allSuccessful = false; + } + } + + return allSuccessful; + } + + // 检查类型兼容性的方法(支持嵌套类型) + static bool IsCompatibleType(Type targetType, object value) + { + if (value == null) + { + // 如果值为null,且目标类型是引用类型或者可空类型,则兼容 + return !targetType.IsValueType || Nullable.GetUnderlyingType(targetType) != null; + } + + // 检查值的类型是否与目标类型相同,或是否可以转换为目标类型 + if (targetType.IsAssignableFrom(value.GetType())) + { + return true; + } + + // 处理数组类型 + if (targetType.IsArray) + { + // 检查数组的元素类型与值的类型兼容 + var elementType = targetType.GetElementType(); + return value is IList>; // 假设值是一个列表,具体处理逻辑在赋值时 + } + + // 处理嵌套类型的情况 + if (value is Dictionary && targetType.IsClass && !targetType.IsPrimitive) + { + // 如果目标类型是一个复杂对象,并且值是一个字典,可能是嵌套对象 + return true; // 假设可以递归处理嵌套对象 + } + + return false; + } + + + + + } +} \ No newline at end of file diff --git a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs index 4b486ab..f5008e4 100644 --- a/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs +++ b/NodeFlow/Tool/SereinExpression/SereinConditionParser.cs @@ -13,7 +13,10 @@ namespace Serein.NodeFlow.Tool.SereinExpression { try { - + if (string.IsNullOrEmpty(expression)) + { + return false; + } var parse = ConditionParse(data, expression); var result = parse.Evaluate(data); return result; @@ -104,7 +107,8 @@ namespace Serein.NodeFlow.Tool.SereinExpression { // 如果不需要转为指定类型 memberPath = operatorStr; - targetObj = GetMemberValue(data, operatorStr); + targetObj = SerinExpressionEvaluator.Evaluate("@get " + operatorStr, data, out _); + //targetObj = GetMemberValue(data, operatorStr); type = targetObj.GetType(); operatorStr = parts[1].ToLower(); // valueStr = string.Join(' ', parts.Skip(2)); @@ -189,18 +193,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression } - - - //int value = int.Parse(valueStr, CultureInfo.InvariantCulture); - - //return new MemberConditionResolver - //{ - // TargetObj = targetObj, - // //MemberPath = memberPath, - // Op = ParseValueTypeOperator(operatorStr), - // Value = value, - // ArithmeticExpression = GetArithmeticExpression(parts[0]) - //}; } #endregion #region 解析类型 double diff --git a/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs b/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs index 6cf1d02..9125247 100644 --- a/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs +++ b/NodeFlow/Tool/SereinExpression/SerinExpressionEvaluator.cs @@ -121,40 +121,98 @@ namespace Serein.NodeFlow.Tool.SereinExpression /// private static object GetMember(object target, string memberPath) { + // 分割成员路径,按 '.' 处理多级访问 var members = memberPath.Split('.'); + foreach (var member in members) { + if (target == null) return null; - if (target is null) return null; - - - var property = target.GetType().GetProperty(member); - if (property != null) + // 检查成员是否包含数组索引,例如 "cars[0]" + var arrayIndexStart = member.IndexOf('['); + if (arrayIndexStart != -1) { - - target = property.GetValue(target); - - } - else - { - var field = target.GetType().GetField(member); - if (field != null) + // 解析数组/集合名与索引部分 + var arrayName = member.Substring(0, arrayIndexStart); + var arrayIndexEnd = member.IndexOf(']'); + if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1) { + throw new ArgumentException($"Invalid array syntax for member {member}"); + } - target = field.GetValue(target); + // 提取数组索引 + var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); + if (!int.TryParse(indexStr, out int index)) + { + throw new ArgumentException($"Invalid array index '{indexStr}' for member {member}"); + } + // 获取数组或集合对象 + var arrayProperty = target.GetType().GetProperty(arrayName); + if (arrayProperty != null) + { + target = arrayProperty.GetValue(target); } else { - throw new ArgumentException($"Member {member} not found on target."); + var arrayField = target.GetType().GetField(arrayName); + if (arrayField != null) + { + target = arrayField.GetValue(target); + } + else + { + throw new ArgumentException($"Member {arrayName} not found on target."); + } + } + + // 访问数组或集合中的指定索引 + if (target is Array array) + { + if (index < 0 || index >= array.Length) + { + throw new ArgumentException($"Index {index} out of bounds for array {arrayName}"); + } + target = array.GetValue(index); + } + else if (target is IList list) + { + if (index < 0 || index >= list.Count) + { + throw new ArgumentException($"Index {index} out of bounds for list {arrayName}"); + } + target = list[index]; + } + else + { + throw new ArgumentException($"Member {arrayName} is not an array or list."); + } + } + else + { + // 处理非数组情况的属性或字段 + var property = target.GetType().GetProperty(member); + if (property != null) + { + target = property.GetValue(target); + } + else + { + var field = target.GetType().GetField(member); + if (field != null) + { + target = field.GetValue(target); + } + else + { + throw new ArgumentException($"Member {member} not found on target."); + } } } } - - return target; - } + /// /// 设置目标的值 /// @@ -178,26 +236,85 @@ namespace Serein.NodeFlow.Tool.SereinExpression { var member = members[i]; - var property = target.GetType().GetProperty(member); - - if (property != null) + // 检查是否包含数组索引 + var arrayIndexStart = member.IndexOf('['); + if (arrayIndexStart != -1) { - - target = property.GetValue(target); - - } - else - { - var field = target.GetType().GetField(member); - if (field != null) + // 解析数组名和索引 + var arrayName = member.Substring(0, arrayIndexStart); + var arrayIndexEnd = member.IndexOf(']'); + if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1) { + throw new ArgumentException($"Invalid array syntax for member {member}"); + } - target = field.GetValue(target); + var indexStr = member.Substring(arrayIndexStart + 1, arrayIndexEnd - arrayIndexStart - 1); + if (!int.TryParse(indexStr, out int index)) + { + throw new ArgumentException($"Invalid array index '{indexStr}' for member {member}"); + } + // 获取数组或集合 + var arrayProperty = target.GetType().GetProperty(arrayName); + if (arrayProperty != null) + { + target = arrayProperty.GetValue(target); } else { - throw new ArgumentException($"Member {member} not found on target."); + var arrayField = target.GetType().GetField(arrayName); + if (arrayField != null) + { + target = arrayField.GetValue(target); + } + else + { + throw new ArgumentException($"Member {arrayName} not found on target."); + } + + } + + // 获取目标数组或集合中的指定元素 + if (target is Array array) + { + if (index < 0 || index >= array.Length) + { + throw new ArgumentException($"Index {index} out of bounds for array {arrayName}"); + } + target = array.GetValue(index); + } + else if (target is IList list) + { + if (index < 0 || index >= list.Count) + { + throw new ArgumentException($"Index {index} out of bounds for list {arrayName}"); + } + target = list[index]; + } + else + { + throw new ArgumentException($"Member {arrayName} is not an array or list."); + } + } + else + { + // 处理非数组情况的属性或字段 + var property = target.GetType().GetProperty(member); + if (property != null) + { + target = property.GetValue(target); + } + else + { + var field = target.GetType().GetField(member); + if (field != null) + { + target = field.GetValue(target); + } + else + { + throw new ArgumentException($"Member {member} not found on target."); + } } } } @@ -205,7 +322,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression var lastMember = members.Last(); var lastProperty = target.GetType().GetProperty(lastMember); - if (lastProperty != null) { var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType); @@ -227,7 +343,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression return target; } - /// /// 计算数学简单表达式 /// diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 20aa405..189ed28 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using Serein.Library.Entity; using Serein.NodeFlow; +using Serein.NodeFlow.Tool; using System.Diagnostics; using System.Windows; @@ -35,6 +36,9 @@ namespace Serein.WorkBench public App() { + + + #if false //测试 操作表达式,条件表达式 #region 测试数据 string expression = ""; diff --git a/WorkBench/LogWindow.xaml b/WorkBench/LogWindow.xaml index 0fe4f79..43dde90 100644 --- a/WorkBench/LogWindow.xaml +++ b/WorkBench/LogWindow.xaml @@ -5,7 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:Serein.WorkBench" mc:Ignorable="d" - Topmost="False" + Topmost="True" Title="LogWindow" Height="600" Width="400" Closing="Window_Closing"> diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 81588a9..331533c 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -23,7 +23,11 @@ - + + + + + @@ -31,7 +35,15 @@ - + + + + + + + + + @@ -64,31 +76,11 @@ - + - - - - - - - - - - - - - - - - - - - - + + + - + diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index ac1fac0..bfd0d44 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -7,6 +7,8 @@ using Serein.Library.Utils; using Serein.NodeFlow; using Serein.NodeFlow.Base; using Serein.NodeFlow.Model; +using Serein.NodeFlow.Tool; +using Serein.NodeFlow.Tool.SereinExpression; using Serein.WorkBench.Node; using Serein.WorkBench.Node.View; using Serein.WorkBench.Node.ViewModel; @@ -14,6 +16,7 @@ using Serein.WorkBench.Themes; using Serein.WorkBench.tool; using System.IO; using System.Reflection; +using System.Text.Json; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; @@ -189,7 +192,12 @@ namespace Serein.WorkBench FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged; + FlowEnvironment.OnNodeLocate += FlowEnvironment_OnNodeLocate; + } + + + private void InitCanvasUI() { canvasTransformGroup = new TransformGroup(); @@ -200,7 +208,6 @@ namespace Serein.WorkBench canvasTransformGroup.Children.Add(translateTransform); FlowChartCanvas.RenderTransform = canvasTransformGroup; - //FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5); } private LogWindow InitConsoleOut() @@ -260,11 +267,6 @@ namespace Serein.WorkBench /// private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) { - //foreach(var connection in Connections) - //{ - // connection.Refresh(); - //} - //Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight)); } /// @@ -319,8 +321,16 @@ namespace Serein.WorkBench { string fromNodeGuid = eventArgs.FromNodeGuid; string toNodeGuid = eventArgs.ToNodeGuid; - NodeControlBase fromNode = GuidToControl(fromNodeGuid); - NodeControlBase toNode = GuidToControl(toNodeGuid); + NodeControlBase? fromNode = GuidToControl(fromNodeGuid); + NodeControlBase? toNode = GuidToControl(toNodeGuid); + if(fromNode != null && toNode != null) + { + + } + else + { + return; + } ConnectionType connectionType = eventArgs.ConnectionType; Action? action = null; @@ -387,7 +397,8 @@ namespace Serein.WorkBench private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs) { var nodeGuid = eventArgs.NodeGuid; - NodeControlBase nodeControl = GuidToControl(nodeGuid); + NodeControlBase? nodeControl = GuidToControl(nodeGuid); + if (nodeControl is null) return; if (selectNodeControls.Count > 0) { if (selectNodeControls.Contains(nodeControl)) @@ -489,11 +500,12 @@ namespace Serein.WorkBench { string oldNodeGuid = eventArgs.OldNodeGuid; string newNodeGuid = eventArgs.NewNodeGuid; - NodeControlBase newStartNodeControl = GuidToControl(newNodeGuid); - + NodeControlBase? newStartNodeControl = GuidToControl(newNodeGuid); + if (newStartNodeControl is null) return; if (!string.IsNullOrEmpty(oldNodeGuid)) { - NodeControlBase oldStartNodeControl = GuidToControl(oldNodeGuid); + NodeControlBase? oldStartNodeControl = GuidToControl(oldNodeGuid); + if (oldStartNodeControl is null) return; oldStartNodeControl.BorderBrush = Brushes.Black; oldStartNodeControl.BorderThickness = new Thickness(0); } @@ -554,7 +566,8 @@ namespace Serein.WorkBench private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; - NodeControlBase nodeControl = GuidToControl(nodeGuid); + NodeControlBase? nodeControl = GuidToControl(nodeGuid); + if (nodeControl is null) return; if (eventArgs.Class == InterruptClass.None) { nodeControl.ViewModel.IsInterrupt = false; @@ -591,7 +604,7 @@ namespace Serein.WorkBench private void FlowEnvironment_OnInterruptTrigger(InterruptTriggerEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; - NodeControlBase nodeControl = GuidToControl(nodeGuid); + NodeControlBase? nodeControl = GuidToControl(nodeGuid); if (nodeControl is null) return; if(eventArgs.Type == InterruptTriggerEventArgs.InterruptTriggerType.Exp) { @@ -613,24 +626,118 @@ namespace Serein.WorkBench IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance); } + /// + /// 节点需要定位 + /// + /// + /// + private void FlowEnvironment_OnNodeLocate(NodeLocatedEventArgs eventArgs) + { + NodeControlBase? nodeControl = GuidToControl(eventArgs.NodeGuid); + if (nodeControl is null) return; + //scaleTransform.ScaleX = 1; + //scaleTransform.ScaleY = 1; + // 获取控件在 FlowChartCanvas 上的相对位置 + Rect controlBounds = VisualTreeHelper.GetDescendantBounds(nodeControl); + Point controlPosition = nodeControl.TransformToAncestor(FlowChartCanvas).Transform(new Point(0, 0)); + + // 获取控件在画布上的中心点 + double controlCenterX = controlPosition.X + controlBounds.Width / 2; + double controlCenterY = controlPosition.Y + controlBounds.Height / 2; + + // 考虑缩放因素计算目标位置的中心点 + double scaledCenterX = controlCenterX * scaleTransform.ScaleX; + double scaledCenterY = controlCenterY * scaleTransform.ScaleY; + + + //// 计算画布的可视区域大小 + //double visibleAreaLeft = scaledCenterX; + //double visibleAreaTop = scaledCenterY; + //double visibleAreaRight = scaledCenterX + FlowChartStackGrid.ActualWidth; + //double visibleAreaBottom = scaledCenterY + FlowChartStackGrid.ActualHeight; + //// 检查控件中心点是否在可视区域内 + //bool isInView = scaledCenterX >= visibleAreaLeft && scaledCenterX <= visibleAreaRight && + // scaledCenterY >= visibleAreaTop && scaledCenterY <= visibleAreaBottom; + + //Console.WriteLine($"isInView :{isInView}"); + + //if (!isInView) + //{ + + //} + // 计算平移偏移量,使得控件在可视区域的中心 + double translateX = scaledCenterX - FlowChartStackGrid.ActualWidth / 2; + double translateY = scaledCenterY - FlowChartStackGrid.ActualHeight / 2; + + var translate = this.translateTransform; + // 应用平移变换 + translate.X = 0; + translate.Y = 0; + translate.X -= translateX; + translate.Y -= translateY; + + // 设置RenderTransform以实现移动效果 + TranslateTransform translateTransform = new TranslateTransform(); + nodeControl.RenderTransform = translateTransform; + ElasticAnimation(nodeControl, translateTransform, 4,1,0.5); + + } + /// + /// 控件抖动 + /// 来源:https://www.cnblogs.com/RedSky/p/17705411.html + /// 作者:HotSky + /// (……太好用了) + /// + /// + /// 抖动第一下偏移量 + /// 减弱幅度(小于等于power,大于0) + /// 持续系数(大于0),越大时间越长, + private static void ElasticAnimation(NodeControlBase? nodeControl, TranslateTransform translate, int power, int range = 1, double speed = 1) + { + DoubleAnimationUsingKeyFrames animation1 = new DoubleAnimationUsingKeyFrames(); + for (int i = power, j = 1; i >= 0; i -= range) + { + animation1.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + animation1.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + } + translate.BeginAnimation(TranslateTransform.YProperty, animation1); + DoubleAnimationUsingKeyFrames animation2 = new DoubleAnimationUsingKeyFrames(); + for (int i = power, j = 1; i >= 0; i -= range) + { + animation2.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + animation2.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed))); + } + translate.BeginAnimation(TranslateTransform.XProperty, animation2); + + animation2.Completed += (s, e) => + { + nodeControl.RenderTransform = null; // 或者重新设置为默认值 + }; + } + /// /// Guid 转 NodeControl /// /// 节点Guid /// 节点Model /// 无法获取节点、Guid/节点为null时报错 - private NodeControlBase GuidToControl(string nodeGuid) + private NodeControlBase? GuidToControl(string nodeGuid) { if (string.IsNullOrEmpty(nodeGuid)) { + return null; throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid)); } if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase? nodeControl) || nodeControl is null) { + return null; + throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid)); } return nodeControl; } + + #endregion #region 加载项目文件后触发事件相关方法 @@ -800,6 +907,7 @@ namespace Serein.WorkBench #endregion + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid))); contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid))); @@ -948,7 +1056,7 @@ namespace Serein.WorkBench #endregion - #region 与流程图相关的拖拽操作 + #region 与流程图与节点相关 /// /// 鼠标在画布移动。 @@ -1138,7 +1246,7 @@ namespace Serein.WorkBench if(sender is NodeControlBase nodeControl) { ChangeViewerObjOfNode(nodeControl); - if (nodeControl.ViewModel.Node.MethodDetails.IsProtectionParameter) return; + if (nodeControl?.ViewModel?.Node?.MethodDetails?.IsProtectionParameter == true) return; IsControlDragging = true; startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 ((UIElement)sender).CaptureMouse(); // 捕获鼠标 @@ -1168,7 +1276,7 @@ namespace Serein.WorkBench // 获取element控件的旧位置 double oldLeft = Canvas.GetLeft(element); double oldTop = Canvas.GetTop(element); - + // 计算被选择控件的偏移量 double deltaX = (int)(currentPosition.X - startControlDragPoint.X); double deltaY = (int)(currentPosition.Y - startControlDragPoint.Y); @@ -1221,12 +1329,12 @@ namespace Serein.WorkBench { return; } - double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量 double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量 double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距 double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距 + //Console.WriteLine((Canvas.GetLeft(block), Canvas.GetTop(block))); // 限制控件不超出FlowChartCanvas的边界 if (newLeft >= 0 && newLeft + block.ActualWidth <= FlowChartCanvas.ActualWidth) @@ -1245,9 +1353,9 @@ namespace Serein.WorkBench } private void ChangeViewerObjOfNode(NodeControlBase nodeControl) { - var node = nodeControl?.ViewModel?.Node; - if (node is not null && node.MethodDetails.ReturnType != typeof(void)) + //if (node is not null && (node.MethodDetails is null || node.MethodDetails.ReturnType != typeof(void)) + if (node is not null && node?.MethodDetails?.ReturnType != typeof(void)) { var key = node.Guid; var instance = node.GetFlowData(); @@ -1421,8 +1529,6 @@ namespace Serein.WorkBench } #endregion - - #region 拖动画布实现缩放平移效果 private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e) { @@ -1446,19 +1552,19 @@ namespace Serein.WorkBench { // if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) { - if (e.Delta < 0 && scaleTransform.ScaleX < 0.2) return; + if (e.Delta < 0 && scaleTransform.ScaleX < 0.05) return; if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return; // 获取鼠标在 Canvas 内的相对位置 var mousePosition = e.GetPosition(FlowChartCanvas); // 缩放因子,根据滚轮方向调整 - double zoomFactor = e.Delta > 0 ? 0.1 : -0.1; - //double zoomFactor = e.Delta > 0 ? 1.1 : 0.9; + //double zoomFactor = e.Delta > 0 ? 0.1 : -0.1; + double zoomFactor = e.Delta > 0 ? 1.1 : 0.9; // 当前缩放比例 double oldScale = scaleTransform.ScaleX; - // double newScale = oldScale * zoomFactor; - double newScale = oldScale + zoomFactor; + double newScale = oldScale * zoomFactor; + //double newScale = oldScale + zoomFactor; // 更新缩放比例 scaleTransform.ScaleX = newScale; scaleTransform.ScaleY = newScale; @@ -1608,6 +1714,8 @@ namespace Serein.WorkBench #endregion #endregion + + #endregion #region 画布中框选节点控件动作 @@ -1706,7 +1814,6 @@ namespace Serein.WorkBench } e.Handled = true; // 防止事件传播影响其他控件 - } /// @@ -2256,6 +2363,8 @@ namespace Serein.WorkBench private void UnloadAllButton_Click(object sender, RoutedEventArgs e) { FlowEnvironment.ClearAll(); + + } /// /// 卸载DLL文件,清空当前项目 @@ -2450,6 +2559,70 @@ namespace Serein.WorkBench } } + /// + /// 对象装箱测试 + /// + /// + /// + private void ButtonTestExpObj_Click(object sender, RoutedEventArgs e) + { + string jsonString = + """ + { + "Name": "张三", + "Age": 24, + "Address": { + "City": "北京", + "PostalCode": "10000" + } + } + """; + + var externalData = new Dictionary + { + { "Name", "John" }, + { "Age", 30 }, + { "Addresses", new List> + { + new Dictionary + { + { "Street", "123 Main St" }, + { "City", "New York" } + }, + new Dictionary + { + { "Street", "456 Another St" }, + { "City", "Los Angeles" } + } + } + } + }; + + if (!ObjDynamicCreateHelper.TryResolve(externalData, "RootType",out var result)) + { + Console.WriteLine("赋值过程中有错误,请检查属性名和类型!"); + + } + ObjDynamicCreateHelper.PrintObjectProperties(result); + Console.WriteLine( ); + var exp = "@set .Addresses[1].Street = qwq"; + var data = SerinExpressionEvaluator.Evaluate(exp, result, out bool isChange); + exp = "@get .Addresses[1].Street"; + data = SerinExpressionEvaluator.Evaluate(exp,result, out isChange); + Console.WriteLine($"{exp} => {data}"); + } + + /// + /// 重置画布 + /// + /// + /// + private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e) + { + translateTransform.X = 0; + translateTransform.Y = 0; + } + } #region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线 diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs index 5abe74b..91273af 100644 --- a/WorkBench/Node/NodeControlViewModelBase.cs +++ b/WorkBench/Node/NodeControlViewModelBase.cs @@ -34,7 +34,7 @@ namespace Serein.WorkBench.Node.ViewModel set { isSelect = value; - // OnPropertyChanged(); + OnPropertyChanged(); } } diff --git a/WorkBench/Themes/NodeTreeItemViewControl.xaml b/WorkBench/Themes/NodeTreeItemViewControl.xaml index 47be97c..52b6dac 100644 --- a/WorkBench/Themes/NodeTreeItemViewControl.xaml +++ b/WorkBench/Themes/NodeTreeItemViewControl.xaml @@ -5,8 +5,15 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.WorkBench.Themes" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> + d:DesignHeight="400" d:DesignWidth="200"> + + + @@ -21,7 +28,7 @@ - + @@ -29,7 +36,7 @@ - + @@ -38,7 +45,7 @@ - + @@ -46,7 +53,7 @@ - + diff --git a/WorkBench/Themes/NodeTreeItemViewControl.xaml.cs b/WorkBench/Themes/NodeTreeItemViewControl.xaml.cs index 20be0b1..d5b5a9e 100644 --- a/WorkBench/Themes/NodeTreeItemViewControl.xaml.cs +++ b/WorkBench/Themes/NodeTreeItemViewControl.xaml.cs @@ -74,9 +74,14 @@ namespace Serein.WorkBench.Themes {ConnectionType.IsError, []}, } }; + string itemName = rootNodeModel?.MethodDetails?.MethodTips; + if (string.IsNullOrEmpty(itemName)) + { + itemName = rootNodeModel.ControlType.ToString(); + } var rootNode = new TreeViewItem { - Header = rootNodeModel.MethodDetails.MethodTips, + Header = itemName, Tag = nodeTreeModel, }; LoadNodeItem(this, nodeTreeModel); @@ -136,19 +141,27 @@ namespace Serein.WorkBench.Themes RootNode = child, ChildNodes = child.SuccessorNodes, }; + string itemName = child?.MethodDetails?.MethodTips; + if (string.IsNullOrEmpty(itemName)) + { + itemName = child.ControlType.ToString(); + } TreeViewItem treeViewItem = new TreeViewItem { - Header = child.MethodDetails.MethodTips, + Header = itemName, Tag = tmpNodeTreeModel }; treeViewItem.Expanded += TreeViewItem_Expanded; - ContextMenu contextMenu = new ContextMenu(); + + var contextMenu = new ContextMenu(); contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", (s, e) => { flowEnvironment.StartFlowInSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid); })); - treeViewItem.ContextMenu = contextMenu; + contextMenu.Items.Add(MainWindow.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocated(tmpNodeTreeModel.RootNode.Guid))); + treeViewItem.ContextMenu = contextMenu; + treeViewItem.Margin = new Thickness(-20, 0, 0, 0); treeViewer.Items.Add(treeViewItem); } guid.Visibility = Visibility.Visible; @@ -190,12 +203,18 @@ namespace Serein.WorkBench.Themes RootNode = childNodeModel, ChildNodes = childNodeModel.SuccessorNodes, }; + + string itemName = childNodeModel?.MethodDetails?.MethodTips; + if (string.IsNullOrEmpty(itemName)) + { + itemName = childNodeModel.ControlType.ToString(); + } TreeViewItem treeViewItem = new TreeViewItem { - Header = childNodeModel.MethodDetails.MethodTips, + Header = itemName, Tag = tempNodeTreeModel }; - treeViewItem.Margin = new Thickness(-15, 0, 0, 0); + treeViewItem.Margin = new Thickness(-20, 0, 0, 0); treeViewItem.Visibility = Visibility.Visible; treeView.Items.Add(treeViewItem); }