From 8b4ec31d83908c6c81188d12a54eb49a01b9dcab Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sat, 2 Nov 2024 22:11:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E4=BA=86=E5=AF=B9NodeaAction?= =?UTF-8?q?=E7=89=B9=E6=80=A7=E6=A0=87=E8=AE=B0=E6=96=B9=E6=B3=95=E4=B8=AD?= =?UTF-8?q?=EF=BC=8C=E5=AF=B9params=E5=8F=AF=E5=8F=98=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IFlowEnvironment.cs | 9 + Library/FlowNode/MethodDetails.cs | 63 +++++-- Library/FlowNode/NodeModelBaseFunc.cs | 78 +++++---- Library/FlowNode/ParameterDetails.cs | 3 + Library/FlowNode/ParameterDetailsInfo.cs | 5 + Library/Utils/EmitHelper.cs | 13 +- NodeFlow/Env/FlowEnvironment.cs | 23 ++- NodeFlow/Env/FlowEnvironmentDecorator.cs | 12 +- NodeFlow/Env/FlowFunc.cs | 2 +- NodeFlow/Env/RemoteFlowEnvironment.cs | 13 ++ NodeFlow/Tool/NodeMethodDetailsHelper.cs | 56 +++--- WorkBench/Themes/MethodDetailsControl.xaml | 161 +++++++++--------- WorkBench/Themes/MethodDetailsControl.xaml.cs | 32 +++- .../Node/Junction/JunctionControlBase.cs | 145 +++++++++++++++- Workbench/Node/RelayCommand.cs | 26 +++ 15 files changed, 472 insertions(+), 169 deletions(-) create mode 100644 Workbench/Node/RelayCommand.cs diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index cef123b..46e5615 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -844,6 +844,15 @@ namespace Serein.Library.Api /// Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value); + /// + /// 改变可选参数的数目 + /// + /// 对应的节点Guid + /// true,增加参数;false,减少参数 + /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) + /// + Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex); + /// /// 获取方法描述信息 diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index f20ba18..402490b 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -76,7 +76,7 @@ namespace Serein.Library /// 0表示第一个参数是可选参数 /// [PropertyInfo] - private int _isParamsArgIndex = -1; + private int _paramsArgIndex = -1; /// /// 出参类型 @@ -89,42 +89,72 @@ namespace Serein.Library public partial class MethodDetails { - #region 新增可选参数 + #region 更改可变参数 /// - /// 新增可选参数 + /// 是否存在可变参数(-1表示不存在) + /// + public bool HasParamsArg => _paramsArgIndex >= 0; + + /// + /// 新增可变参数 /// /// - public void AddParamsArg(int index = 0) + public bool AddParamsArg(int index = 0) { - if (IsParamsArgIndex >= 0 // 方法是否包含可选参数 + if (ParamsArgIndex >= 0 // 方法是否包含可变参数 && index >= 0 // 如果包含,则判断从哪个参数赋值 - && index >= IsParamsArgIndex // 需要判断是否为可选参数的部分 + && index >= ParamsArgIndex // 需要判断是否为可选参数的部分 && index < ParameterDetailss.Length) // 防止下标越界 { var newPd = ParameterDetailss[index].CloneOfModel(this.NodeModel); // 复制出属于本身节点的参数描述 newPd.Index = ParameterDetailss.Length; // 更新索引 newPd.IsParams = true; ParameterDetailss = AddToArray(ParameterDetailss, newPd); // 新增 + return true; + } + else + { + return false; } } /// - /// 移除可选参数 + /// 移除可变参数 /// /// - public void RemoveParamsArg(int index = 0) + public bool RemoveParamsArg(int index = 0) { - if (IsParamsArgIndex >= 0 // 方法是否包含可选参数 + if (ParamsArgIndex >= 0 // 方法是否包含可变参数 && index >= 0 // 如果包含,则判断从哪个参数赋值 - && index >= IsParamsArgIndex // 需要判断是否为可选参数的部分 + && index > ParamsArgIndex // 需要判断是否为可选参数的部分,并且不能删除原始的可变参数描述 && index < ParameterDetailss.Length) // 防止下标越界 { - //var newPd = ParameterDetailss[index].CloneOfModel(this.NodeModel); // 复制出属于本身节点的参数描述 - //newPd.Index = ParameterDetailss.Length; // 更新索引 ParameterDetailss[index] = null; // 释放对象引用 - ParameterDetailss = RemoteToArray(ParameterDetailss, index); // 新增 + var tmp = RemoteToArray(ParameterDetailss, index); // 新增; + UpdateParamIndex(ref tmp); + ParameterDetailss = tmp; // 新增 + return true; + } + else + { + return false; + } + + } + + /// + /// 更新参数的索引 + /// + /// + private void UpdateParamIndex(ref ParameterDetails[] parameterDetails) + { + for (int i = 0; i < parameterDetails.Length; i++) + { + var pd = parameterDetails[i]; + pd.Index = i; } } + public static T[] AddToArray(T[] original, T newObject) { // 创建一个新数组,比原数组大1 @@ -197,7 +227,7 @@ namespace Serein.Library MethodDynamicType = nodeType; ReturnType = Type.GetType(Info.ReturnTypeFullName); ParameterDetailss = Info.ParameterDetailsInfos.Select(pinfo => new ParameterDetails(pinfo)).ToArray(); - IsParamsArgIndex = Info.IsParamsArgIndex; + ParamsArgIndex = Info.IsParamsArgIndex; } /// @@ -213,7 +243,7 @@ namespace Serein.Library NodeType = this.MethodDynamicType.ToString(), ParameterDetailsInfos = this.ParameterDetailss.Select(p => p.ToInfo()).ToArray(), ReturnTypeFullName = this.ReturnType.FullName, - IsParamsArgIndex = this.IsParamsArgIndex, + IsParamsArgIndex = this.ParamsArgIndex, }; } @@ -234,7 +264,7 @@ namespace Serein.Library MethodName = this.MethodName, MethodLockName = this.MethodLockName, IsProtectionParameter = this.IsProtectionParameter, - IsParamsArgIndex= this.IsParamsArgIndex, + ParamsArgIndex = this.ParamsArgIndex, }; md.ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(); // 拷贝属于节点方法的新入参描述 return md; @@ -251,6 +281,7 @@ namespace Serein.Library for (int i = 0; i < ParameterDetailss.Length; i++) { ParameterDetails arg = this.ParameterDetailss[i]; + sb.AppendLine(arg.ToString()); } sb.AppendLine($""); sb.AppendLine($"返回值信息:"); diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index 0842402..db95286 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -323,28 +323,35 @@ namespace Serein.Library return null;// md.ActingInstance } - object[] parameters = new object[md.ParameterDetailss.Length]; + object[] parameters; - //var previousFlowData = nodeModel.PreviousNode?.FlowData; // 当前传递的数据 - - - object[] paramsArgs = null; // 初始化可选参数 - int paramsArgIndex = 0; // 可选参数下标 - if (md.IsParamsArgIndex >= 0) // 是否存在入参参数 + Array paramsArgs = null; // 初始化可选参数 + int paramsArgIndex = 0; // 可选参数下标,与 object[] paramsArgs 一起使用 + Type paramsArgType = null; // 可变参数的参数类型 + if (md.ParamsArgIndex >= 0) { - // 可选参数数组长度 = 方法参数个数 - ( 可选入参下标 + 1 ) - int paramsLength = md.ParameterDetailss.Length - md.IsParamsArgIndex; - paramsArgs = new object[paramsLength]; // 重新实例化可选参数 - parameters[md.ParameterDetailss.Length-1] = paramsArgs; // 如果存在可选参数,入参参数最后一项则为可选参数 + // 存在可变入参参数 + paramsArgType = md.ParameterDetailss[md.ParamsArgIndex].DataType.GetElementType(); // 获取可变参数的参数类型 + // 可变参数数组长度 = 方法参数个数 - ( 可选入参下标 + 1 ) + int paramsLength = md.ParameterDetailss.Length - md.ParamsArgIndex; + //paramsArgs = paramsArgType.MakeArrayType(paramsLength); + paramsArgs = Array.CreateInstance(paramsArgType, paramsLength);// 可变参数 + parameters = new object[md.ParamsArgIndex+1]; // 调用方法的入参数组 + parameters[md.ParamsArgIndex] = paramsArgs; // 如果存在可选参数,入参参数最后一项则为可变参数 + } + else + { + // 不存在可选参数 + parameters = new object[md.ParameterDetailss.Length]; // 调用方法的入参数组 } bool hasParams = false; - for (int i = 0; i < parameters.Length; i++) + for (int i = 0; i < md.ParameterDetailss.Length; i++) { var pd = md.ParameterDetailss[i]; // 方法入参描述 // 入参参数下标循环到可选参数时,开始写入到可选参数数组 - if(i >= md.IsParamsArgIndex) + if(paramsArgs != null && i >= md.ParamsArgIndex) { // 控制参数赋值方向: // true => paramsArgs @@ -352,13 +359,17 @@ namespace Serein.Library hasParams = true; } + // 可选参数为 Array 类型,所以需要获取子项类型 + // 如果 hasParams 为 true ,说明一定存在可选参数,所以 paramsArgType 一定不为 null + Type argDataType = hasParams ? paramsArgType : pd.DataType; + #region 获取基础的上下文数据 - if (pd.DataType == typeof(IFlowEnvironment)) // 获取流程上下文 + if (argDataType == typeof(IFlowEnvironment)) // 获取流程上下文 { parameters[i] = nodeModel.Env; continue; } - if (pd.DataType == typeof(IDynamicContext)) // 获取流程上下文 + if (argDataType == typeof(IDynamicContext)) // 获取流程上下文 { parameters[i] = context; continue; @@ -367,7 +378,7 @@ namespace Serein.Library #region 确定[预入参]数据 object inputParameter; // 存放解析的临时参数 - if (pd.IsExplicitData) // 判断是否使用显示的输入参数 + if (pd.IsExplicitData && !pd.DataValue.StartsWith("@get", StringComparison.OrdinalIgnoreCase)) // 判断是否使用显示的输入参数 { // 使用输入的固定值 inputParameter = pd.DataValue; @@ -415,10 +426,10 @@ namespace Serein.Library } #region 对于非值类型的null检查 - if (!pd.DataType.IsValueType && inputParameter is null) + if (!argDataType.IsValueType && inputParameter is null) { parameters[i] = null; - throw new Exception($"[arg{pd.Index}][{pd.Name}][{pd.DataType}]参数不能为null"); + throw new Exception($"[arg{pd.Index}][{pd.Name}][{argDataType}]参数不能为null"); // continue; } #endregion @@ -452,8 +463,9 @@ namespace Serein.Library { if (hasParams) { + paramsArgs.SetValue(value, paramsArgIndex++); // 处理可选参数 - paramsArgs[paramsArgIndex++] = value; + //paramsArgs[paramsArgIndex++] = value; } else { @@ -466,7 +478,7 @@ namespace Serein.Library #region 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型,如果获取成功(不为null)会跳过循环 // 入参存在基于BinValue的类型转换器,获取枚举转换器中记录的类型 - if (pd.ExplicitType.IsEnum && pd.DataType != pd.ExplicitType) + if (pd.ExplicitType.IsEnum && argDataType != pd.ExplicitType) { var resultEnum = Enum.Parse(pd.ExplicitType, pd.DataValue); // 获取绑定的类型 @@ -483,7 +495,8 @@ namespace Serein.Library if (hasParams) { // 处理可选参数 - paramsArgs[paramsArgIndex++] = value; + paramsArgs.SetValue(value, paramsArgIndex++); + //paramsArgs[paramsArgIndex++] = value; } else { @@ -498,30 +511,30 @@ namespace Serein.Library #region 对入参数据尝试进行转换 object tmpVaue = null; // 临时存放数据,最后才判断是否放置可选参数数组 - if (inputParameter.GetType() == pd.DataType) + if (inputParameter.GetType() == argDataType) { tmpVaue = inputParameter; // 类型一致无需转换,直接装入入参数组 } - else if (pd.DataType.IsValueType) + else if (argDataType.IsValueType) { // 值类型 var valueStr = inputParameter?.ToString(); - tmpVaue = valueStr.ToValueData(pd.DataType); // 类型不一致,尝试进行转换,如果转换失败返回类型对应的默认值 + tmpVaue = valueStr.ToValueData(argDataType); // 类型不一致,尝试进行转换,如果转换失败返回类型对应的默认值 } else { // 引用类型 - if (pd.DataType == typeof(string)) // 转为字符串 + if (argDataType == typeof(string)) // 转为字符串 { var valueStr = inputParameter?.ToString(); tmpVaue = valueStr; } - else if(pd.DataType.IsSubclassOf(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 子类/实现类 + else if(argDataType.IsSubclassOf(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 子类/实现类 { // 方法入参中,父类不能隐式转为子类,这里需要进行强制转换 - tmpVaue = ObjectConvertHelper.ConvertParentToChild(inputParameter, pd.DataType); + tmpVaue = ObjectConvertHelper.ConvertParentToChild(inputParameter, argDataType); } - else if(pd.DataType.IsAssignableFrom(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 父类/接口 + else if(argDataType.IsAssignableFrom(inputParameter.GetType())) // 入参类型 是 预入参数据类型 的 父类/接口 { tmpVaue = inputParameter; } @@ -530,12 +543,12 @@ namespace Serein.Library //{ // var enumerableMethods = typeof(Enumerable).GetMethods(); // 获取所有的 Enumerable 扩展方法 // MethodInfo conversionMethod; - // if (pd.DataType.IsArray) // 转为数组 + // if (argDataType.IsArray) // 转为数组 // { // parameters[i] = inputParameter; // conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToArray" && m.IsGenericMethodDefinition); // } - // else if (pd.DataType.GetGenericTypeDefinition() == typeof(List<>)) // 转为集合 + // else if (argDataType.GetGenericTypeDefinition() == typeof(List<>)) // 转为集合 // { // conversionMethod = enumerableMethods.FirstOrDefault(m => m.Name == "ToList" && m.IsGenericMethodDefinition); // } @@ -543,7 +556,7 @@ namespace Serein.Library // { // throw new InvalidOperationException("输入对象不是集合或目标类型不支持(目前仅支持Array、List的自动转换)"); // } - // var genericMethod = conversionMethod.MakeGenericMethod(pd.DataType); + // var genericMethod = conversionMethod.MakeGenericMethod(argDataType); // var result = genericMethod.Invoke(null, new object[] { collection }); // parameters[i] = result; //} @@ -556,7 +569,8 @@ namespace Serein.Library if (hasParams) { // 处理可选参数 - paramsArgs[paramsArgIndex++] = tmpVaue; + paramsArgs.SetValue(tmpVaue, paramsArgIndex++); + //paramsArgs[paramsArgIndex++] = tmpVaue; } else { diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 83a6ad6..d92b483 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -133,6 +133,7 @@ namespace Serein.Library ExplicitType = Type.GetType(info.ExplicitTypeFullName); ExplicitTypeName = info.ExplicitTypeName; Items = info.Items; + IsParams = info.IsParams; } /// @@ -144,6 +145,7 @@ namespace Serein.Library return new ParameterDetailsInfo { Index = this.Index, + IsParams = this.IsParams, DataTypeFullName = this.DataType.FullName, Name = this.Name, ExplicitTypeFullName = this.ExplicitType.FullName, @@ -170,6 +172,7 @@ namespace Serein.Library Name = this.Name, DataValue = string.IsNullOrEmpty(DataValue) ? string.Empty : DataValue, Items = this.Items?.Select(it => it).ToArray(), + IsParams = this.IsParams, }; return pd; diff --git a/Library/FlowNode/ParameterDetailsInfo.cs b/Library/FlowNode/ParameterDetailsInfo.cs index c1feb54..4fc6905 100644 --- a/Library/FlowNode/ParameterDetailsInfo.cs +++ b/Library/FlowNode/ParameterDetailsInfo.cs @@ -17,6 +17,11 @@ namespace Serein.Library /// public int Index { get; set; } + /// + /// 是否为可变参数 + /// + public bool IsParams { get; set; } + /// /// 方法需要的类型 /// diff --git a/Library/Utils/EmitHelper.cs b/Library/Utils/EmitHelper.cs index 1ab5e89..0cf481a 100644 --- a/Library/Utils/EmitHelper.cs +++ b/Library/Utils/EmitHelper.cs @@ -29,6 +29,7 @@ namespace Serein.Library.Utils /// HasResultTask, } + public static bool IsGenericTask(Type returnType, out Type taskResult) { // 判断是否为 Task 类型或泛型 Task @@ -51,6 +52,8 @@ namespace Serein.Library.Utils } } + + /// /// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托 /// @@ -82,6 +85,8 @@ namespace Serein.Library.Utils } } + + dynamicMethod = new DynamicMethod( name: methodInfo.Name + "_DynamicEmitMethod", returnType: returnType, @@ -89,8 +94,6 @@ namespace Serein.Library.Utils restrictedSkipVisibility: true // 跳过私有方法访问限制 ); - - var il = dynamicMethod.GetILGenerator(); // 加载实例 (this) @@ -110,10 +113,16 @@ namespace Serein.Library.Utils { il.Emit(OpCodes.Unbox_Any, paramType); } + //else if (paramType.IsGenericParameter) // 如果是泛型参数,直接转换 + //{ + // il.Emit(OpCodes.Castclass, paramType); + //} else // 如果是引用类型,直接转换 { il.Emit(OpCodes.Castclass, paramType); } + + } // 调用方法 diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 064e39d..ab303cc 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -1316,7 +1316,28 @@ namespace Serein.NodeFlow.Env } - + /// + /// 改变可选参数的数目 + /// + /// 对应的节点Guid + /// true,增加参数;false,减少参数 + /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) + /// + public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + { + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return false; + bool isPass; + if (isAdd) + { + isPass = nodeModel.MethodDetails.AddParamsArg(paramIndex); + } + else + { + isPass = nodeModel.MethodDetails.RemoveParamsArg(paramIndex); + } + return isPass; + } /// diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 142ca55..1b802ad 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -425,7 +425,17 @@ namespace Serein.NodeFlow.Env } - + /// + /// 改变可选参数的数目 + /// + /// 对应的节点Guid + /// true,增加参数;false,减少参数 + /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) + /// + public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + { + return await currentFlowEnvironment.ChangeParameter(nodeGuid, isAdd, paramIndex); + } #region 流程依赖类库的接口 diff --git a/NodeFlow/Env/FlowFunc.cs b/NodeFlow/Env/FlowFunc.cs index cb4c2c3..46f7421 100644 --- a/NodeFlow/Env/FlowFunc.cs +++ b/NodeFlow/Env/FlowFunc.cs @@ -52,7 +52,7 @@ namespace Serein.NodeFlow.Env { methodDetails = new MethodDetails(); } - var md = methodDetails.CloneOfNode(nodeModel.Env, nodeModel); + var md = methodDetails.CloneOfNode(nodeModel); nodeModel.DisplayName = md.MethodAnotherName; nodeModel.MethodDetails = md; nodeModel.OnCreating(); diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index 2312ae8..87a94f0 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -819,6 +819,7 @@ namespace Serein.NodeFlow.Env UIContextOperation?.Invoke(() => OnNodeLocated?.Invoke(new NodeLocatedEventArgs(nodeGuid))); } + public async Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value) { if(IsLoadingProject || IsLoadingNode) @@ -836,6 +837,18 @@ namespace Serein.NodeFlow.Env } + /// + /// 改变可选参数的数目 + /// + /// 对应的节点Guid + /// true,增加参数;false,减少参数 + /// 以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数) + /// + public async Task ChangeParameter(string nodeGuid, bool isAdd, int paramIndex) + { + Console.WriteLine("远程环境尚未实现的接口:ChangeParameter"); + return false; + } #region 流程依赖类库的接口 diff --git a/NodeFlow/Tool/NodeMethodDetailsHelper.cs b/NodeFlow/Tool/NodeMethodDetailsHelper.cs index 1b5a371..18c61e4 100644 --- a/NodeFlow/Tool/NodeMethodDetailsHelper.cs +++ b/NodeFlow/Tool/NodeMethodDetailsHelper.cs @@ -4,6 +4,7 @@ using Serein.Library; using System.Collections.Concurrent; using System.Reflection; using Serein.Library.FlowNode; +using System.Diagnostics; namespace Serein.NodeFlow.Tool; @@ -31,8 +32,12 @@ public static class NodeMethodDetailsHelper } //var dllTypeName = $"{assemblyName}.{type.Name}"; var dllTypeMethodName = $"{assemblyName}.{type.Name}.{method.Name}"; - + Console.WriteLine("loading method : " +dllTypeMethodName); + Debug.WriteLine("loading method : " +dllTypeMethodName); var explicitDataOfParameters = GetExplicitDataOfParameters(method.GetParameters()); + + + //// 通过表达式树生成委托 //var methodDelegate = GenerateMethodDelegate(type, // 方法所在的对象类型 // method, // 方法信息 @@ -92,9 +97,13 @@ public static class NodeMethodDetailsHelper var asyncPrefix = "[异步]"; // IsGenericTask(returnType) ? "[async]" : ; var methodMethodAnotherName = isTask ? asyncPrefix + attribute.AnotherName : attribute.AnotherName; + bool hasParamsArg = false; + if (explicitDataOfParameters.Length > 0) + { + hasParamsArg = explicitDataOfParameters[^1].IsParams; // 取最后一个参数描述,判断是否为params 入参 + } - - var md = new MethodDetails() // 从DLL生成方法描述 + var md = new MethodDetails() // 从DLL生成方法描述(元数据) { ActingInstanceType = type, // ActingInstance = instance, @@ -104,6 +113,8 @@ public static class NodeMethodDetailsHelper MethodAnotherName = methodMethodAnotherName, ParameterDetailss = explicitDataOfParameters, ReturnType = returnType, + // 如果存在可变参数,取最后一个元素的下标,否则为-1; + ParamsArgIndex = hasParamsArg ? explicitDataOfParameters.Length-1 : -1, }; var dd = new DelegateDetails(emitMethodType, methodDelegate) ; return (md, dd); @@ -144,16 +155,19 @@ public static class NodeMethodDetailsHelper private static ParameterDetails[] GetExplicitDataOfParameters(ParameterInfo[] parameters) { - return parameters.Select((it, index) => + var tempParams = parameters.Select((it, index) => { Type paremType; - - if (it.GetCustomAttribute() is EnumTypeConvertorAttribute attribute1 && attribute1 is not null) + + #region 存在“枚举=>类型”转换器 + if (it.GetCustomAttribute() is EnumTypeConvertorAttribute attribute1 && attribute1 is not null) { // 存在类型选择器 paremType = attribute1.EnumType; - return GetExplicitDataOfParameter(it, index, paremType, true); + return GetExplicitDataOfParameter(it, index, paremType, true); // “枚举=>类型”转换器 获取参数 } + #endregion + #region 存在自定义的转换器 else if (it.GetCustomAttribute() is BindConvertorAttribute attribute2 && attribute2 is not null) { paremType = attribute2.EnumType; @@ -180,29 +194,22 @@ public static class NodeMethodDetailsHelper return methodInfo?.Invoke(obj, [enumValue]); } // 确保实例实现了所需接口 - ParameterDetails ed = GetExplicitDataOfParameter(it, index, paremType, true, func); + ParameterDetails ed = GetExplicitDataOfParameter(it, index, paremType, true, func); // 自定义的转换器 获取参数 return ed; } + #endregion + #region 常规方法的获取参数 else { - return GetExplicitDataOfParameter(it, index, it.ParameterType, it.HasDefaultValue); - } - //string explicitTypeName = GetExplicitTypeName(paremType); - //var items = GetExplicitItems(paremType, explicitTypeName); - //if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 - //return new ExplicitData - //{ - // IsExplicitData = attribute is null ? it.HasDefaultValue: true, - // Index = index, - // ExplicitTypeName = explicitTypeName, - // ExplicitType = paremType, - // DataType = it.ParameterType, - // ParameterName = it.Name, - // DataValue = it.HasDefaultValue ? it?.DefaultValue?.ToString() : "", - // Items = items.ToArray(), - //}; + var tmp = GetExplicitDataOfParameter(it, index, it.ParameterType, it.HasDefaultValue); // 常规方法的获取参数 + return tmp; + } + #endregion }).ToArray(); + + + return tempParams; } private static ParameterDetails GetExplicitDataOfParameter(ParameterInfo parameterInfo, @@ -213,7 +220,6 @@ public static class NodeMethodDetailsHelper { bool hasParams = parameterInfo.IsDefined(typeof(ParamArrayAttribute)); // 判断是否为可变参数 - string explicitTypeName = GetExplicitTypeName(paremType); var items = GetExplicitItems(paremType, explicitTypeName); if ("Bool".Equals(explicitTypeName)) explicitTypeName = "Select"; // 布尔值 转为 可选类型 diff --git a/WorkBench/Themes/MethodDetailsControl.xaml b/WorkBench/Themes/MethodDetailsControl.xaml index 8e53246..de53695 100644 --- a/WorkBench/Themes/MethodDetailsControl.xaml +++ b/WorkBench/Themes/MethodDetailsControl.xaml @@ -2,130 +2,125 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Serein.Workbench.Themes" xmlns:view="clr-namespace:Serein.Workbench.Node.View" - xmlns:sys="clr-namespace:System;assembly=mscorlib" > + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:converters="clr-namespace:Serein.Workbench.Tool.Converters"> - + + - - + + + + - + diff --git a/WorkBench/Themes/MethodDetailsControl.xaml.cs b/WorkBench/Themes/MethodDetailsControl.xaml.cs index 270614e..32caab5 100644 --- a/WorkBench/Themes/MethodDetailsControl.xaml.cs +++ b/WorkBench/Themes/MethodDetailsControl.xaml.cs @@ -1,9 +1,11 @@ using Serein.Library; +using Serein.Workbench.Node; using System.Collections; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Data; +using System.Windows.Input; namespace Serein.Workbench.Themes { @@ -15,10 +17,12 @@ namespace Serein.Workbench.Themes { if (isEnabled) { + // 返回文本框 if (valueType == typeof(string) || valueType == typeof(int) || valueType == typeof(double)) { return "TextBoxTemplate"; } + // 返回可选列表框 else if (typeof(IEnumerable).IsAssignableFrom(valueType)) { return "ComboBoxTemplate"; @@ -44,23 +48,43 @@ namespace Serein.Workbench.Themes static MethodDetailsControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(MethodDetailsControl), new FrameworkPropertyMetadata(typeof(MethodDetailsControl))); + } - + #region 绑定的方法信息 public MethodDetails MethodDetails { get { return (MethodDetails)GetValue(MethodDetailsProperty); } set { SetValue(MethodDetailsProperty, value); } } - public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register("MethodDetails", typeof(MethodDetails), + public static readonly DependencyProperty MethodDetailsProperty = DependencyProperty.Register(nameof(MethodDetails), typeof(MethodDetails), typeof(MethodDetailsControl), new PropertyMetadata(null, new PropertyChangedCallback(OnPropertyChange))); + #endregion + + static void OnPropertyChange(DependencyObject sender, DependencyPropertyChangedEventArgs args) { - - var MethodDetails = (MethodDetails)args.NewValue; + //var MethodDetails = (MethodDetails)args.NewValue; //MethodDetails.ExplicitDatas[0]. } + + + public ICommand CommandAddParams { get; } + + public MethodDetailsControl() + { + CommandAddParams = new RelayCommand(ExecuteAddParams); + } + + private void ExecuteAddParams(object parameter) + { + // 方法逻辑 + this.MethodDetails.AddParamsArg(); + } + + + } } diff --git a/Workbench/Node/Junction/JunctionControlBase.cs b/Workbench/Node/Junction/JunctionControlBase.cs index af31965..db4159a 100644 --- a/Workbench/Node/Junction/JunctionControlBase.cs +++ b/Workbench/Node/Junction/JunctionControlBase.cs @@ -9,11 +9,141 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Shapes; +using System.Windows.Media.Media3D; +using System.Windows.Documents; +using System.Threading; namespace Serein.Workbench.Node.View { + public class ParamsArgControl: Shape + { + + + public ParamsArgControl() + { + this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除 + this.MouseMove += ParamsArgControl_MouseMove; + this.MouseLeave += ParamsArgControl_MouseLeave; + AddOrRemoveParamsAction = Add; + + + } + + + + protected readonly StreamGeometry StreamGeometry = new StreamGeometry(); + protected override Geometry DefiningGeometry => StreamGeometry; + + + #region 控件属性,所在的节点 + public static readonly DependencyProperty NodeProperty = + DependencyProperty.Register(nameof(MyNode), typeof(NodeModelBase), typeof(ParamsArgControl), new PropertyMetadata(default(NodeModelBase))); + //public NodeModelBase NodeModel; + + /// + /// 所在的节点 + /// + public NodeModelBase MyNode + { + get { return (NodeModelBase)GetValue(NodeProperty); } + set { SetValue(NodeProperty, value); } + } + #endregion + + #region 控件属性,连接器类型 + public static readonly DependencyProperty ArgIndexProperty = + DependencyProperty.Register(nameof(ArgIndex), typeof(int), typeof(ParamsArgControl), new PropertyMetadata(default(int))); + + /// + /// 参数的索引 + /// + public int ArgIndex + { + get { return (int)GetValue(ArgIndexProperty); } + set { SetValue(ArgIndexProperty, value.ToString()); } + } + #endregion + + + /// + /// 控件重绘事件 + /// + /// + protected override void OnRender(DrawingContext drawingContext) + { + Brush brush = isMouseOver ? Brushes.Red : Brushes.Green; + double height = ActualHeight; + // 定义圆形的大小和位置 + double connectorSize = 10; // 连接器的大小 + double circleCenterX = 8; // 圆心 X 坐标 + double circleCenterY = height / 2; // 圆心 Y 坐标 + var circlePoint = new Point(circleCenterX, circleCenterY); + + // 圆形部分 + var ellipse = new EllipseGeometry(circlePoint, connectorSize / 2, connectorSize / 2); + + drawingContext.DrawGeometry(brush, new Pen(Brushes.Black, 1), ellipse); + } + + + private bool isMouseOver; // 鼠标悬停状态 + + private Action AddOrRemoveParamsAction; // 增加或删除参数 + + public void ParamsArg_OnMouseDown(object sender, MouseButtonEventArgs e) + { + AddOrRemoveParamsAction?.Invoke(); + } + + private void ParamsArgControl_MouseMove(object sender, MouseEventArgs e) + { + isMouseOver = true; + if (cancellationTokenSource.IsCancellationRequested) { + cancellationTokenSource = new CancellationTokenSource(); + Task.Run(async () => + { + await Task.Delay(500); + + }, cancellationTokenSource.Token).ContinueWith((t) => + { + // 如果焦点仍在控件上时,则改变点击事件 + if (isMouseOver) + { + AddOrRemoveParamsAction = Remove; + this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 + + } + }); + } + + } + private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + + private void ParamsArgControl_MouseLeave(object sender, MouseEventArgs e) + { + isMouseOver = false; + AddOrRemoveParamsAction = Add; // 鼠标焦点离开时恢复点击事件 + cancellationTokenSource?.Cancel(); + this.Dispatcher.Invoke(InvalidateVisual);// 触发一次重绘 + + } + + + private void Add() + { + this.MyNode.Env.ChangeParameter(MyNode.Guid, true, ArgIndex); + } + private void Remove() + { + this.MyNode.Env.ChangeParameter(MyNode.Guid, false, ArgIndex); + } + + } + + public abstract class JunctionControlBase : Shape { @@ -141,7 +271,11 @@ namespace Serein.Workbench.Node.View private object lockObj = new object(); - // 控件获得鼠标焦点事件 + /// + /// 控件获得鼠标焦点事件 + /// + /// + /// private void JunctionControlBase_MouseMove(object sender, MouseEventArgs e) { //if (!GlobalJunctionData.MyGlobalConnectingData.IsCreateing) return; @@ -150,10 +284,13 @@ namespace Serein.Workbench.Node.View IsMouseOver = true; //this.InvalidateVisual(); - - } - // 控件失去鼠标焦点事件 + + /// + /// 控件失去鼠标焦点事件 + /// + /// + /// private void JunctionControlBase_MouseLeave(object sender, MouseEventArgs e) { IsMouseOver = false; diff --git a/Workbench/Node/RelayCommand.cs b/Workbench/Node/RelayCommand.cs new file mode 100644 index 0000000..a19b531 --- /dev/null +++ b/Workbench/Node/RelayCommand.cs @@ -0,0 +1,26 @@ +using System.Windows.Input; + +namespace Serein.Workbench.Node +{ + + public class RelayCommand : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute; + _canExecute = canExecute; + } + + public event EventHandler CanExecuteChanged; + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); + + public void Execute(object parameter) => _execute(parameter); + + public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); + } + +}