新增了对NodeaAction特性标记方法中,对params可变参数的支持

This commit is contained in:
fengjiayi
2024-11-02 22:11:38 +08:00
parent cd1642dcf7
commit 8b4ec31d83
15 changed files with 472 additions and 169 deletions

View File

@@ -844,6 +844,15 @@ namespace Serein.Library.Api
/// <returns></returns>
Task NotificationNodeValueChangeAsync(string nodeGuid, string path, object value);
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
Task<bool> ChangeParameter(string nodeGuid, bool isAdd, int paramIndex);
/// <summary>
/// 获取方法描述信息

View File

@@ -76,7 +76,7 @@ namespace Serein.Library
/// <para>0表示第一个参数是可选参数</para>
/// </summary>
[PropertyInfo]
private int _isParamsArgIndex = -1;
private int _paramsArgIndex = -1;
/// <summary>
/// 出参类型
@@ -89,42 +89,72 @@ namespace Serein.Library
public partial class MethodDetails
{
#region
#region
/// <summary>
/// 新增可选参数
/// 是否存在可变参数(-1表示不存在
/// </summary>
public bool HasParamsArg => _paramsArgIndex >= 0;
/// <summary>
/// 新增可变参数
/// </summary>
/// <param name="index"></param>
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;
}
}
/// <summary>
/// 移除可参数
/// 移除可参数
/// </summary>
/// <param name="index"></param>
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;
}
}
/// <summary>
/// 更新参数的索引
/// </summary>
/// <param name="parameterDetails"></param>
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>(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;
}
/// <summary>
@@ -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($"返回值信息:");

View File

@@ -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
{

View File

@@ -133,6 +133,7 @@ namespace Serein.Library
ExplicitType = Type.GetType(info.ExplicitTypeFullName);
ExplicitTypeName = info.ExplicitTypeName;
Items = info.Items;
IsParams = info.IsParams;
}
/// <summary>
@@ -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;

View File

@@ -17,6 +17,11 @@ namespace Serein.Library
/// </summary>
public int Index { get; set; }
/// <summary>
/// 是否为可变参数
/// </summary>
public bool IsParams { get; set; }
/// <summary>
/// 方法需要的类型
/// </summary>

View File

@@ -29,6 +29,7 @@ namespace Serein.Library.Utils
/// </summary>
HasResultTask,
}
public static bool IsGenericTask(Type returnType, out Type taskResult)
{
// 判断是否为 Task 类型或泛型 Task<T>
@@ -51,6 +52,8 @@ namespace Serein.Library.Utils
}
}
/// <summary>
/// 根据方法信息创建动态调用的委托,返回方法类型,以及传出一个委托
/// </summary>
@@ -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);
}
}
// 调用方法

View File

@@ -1316,7 +1316,28 @@ namespace Serein.NodeFlow.Env
}
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
public async Task<bool> 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;
}
/// <summary>

View File

@@ -425,7 +425,17 @@ namespace Serein.NodeFlow.Env
}
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
public async Task<bool> ChangeParameter(string nodeGuid, bool isAdd, int paramIndex)
{
return await currentFlowEnvironment.ChangeParameter(nodeGuid, isAdd, paramIndex);
}
#region

View File

@@ -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();

View File

@@ -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
}
/// <summary>
/// 改变可选参数的数目
/// </summary>
/// <param name="nodeGuid">对应的节点Guid</param>
/// <param name="isAdd">true增加参数false减少参数</param>
/// <param name="paramIndex">以哪个参数为模板进行拷贝,或删去某个参数(该参数必须为可选参数)</param>
/// <returns></returns>
public async Task<bool> ChangeParameter(string nodeGuid, bool isAdd, int paramIndex)
{
Console.WriteLine("远程环境尚未实现的接口ChangeParameter");
return false;
}
#region

View File

@@ -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<EnumTypeConvertorAttribute>() is EnumTypeConvertorAttribute attribute1 && attribute1 is not null)
#region =>
if (it.GetCustomAttribute<EnumTypeConvertorAttribute>() 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<BindConvertorAttribute>() 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"; // 布尔值 转为 可选类型

View File

@@ -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">
<ResourceDictionary.MergedDictionaries>
</ResourceDictionary.MergedDictionaries>
<converters:InvertableBooleanToVisibilityConverter x:Key="InvertedBoolConverter"/>
<Style TargetType="{x:Type local:MethodDetailsControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MethodDetailsControl}">
<!--根据方法入参数量生成相应的控件-->
<ItemsControl ItemsSource="{Binding MethodDetails.ParameterDetailss, RelativeSource={RelativeSource TemplatedParent}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="#E3FDFD" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<!--连接控制器-->
<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />
<ContentControl Content="{Binding}" Grid.Column="1">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--无须指定参数-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<!--参数索引提示-->
<TextBlock Grid.Column="1" Text="{Binding Index,StringFormat=agr{0}}" Margin="2,0,2,0" VerticalAlignment="Center"/>
<!--是否设置为显式参数-->
<CheckBox Grid.Column="2" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="2,0,2,0" VerticalContentAlignment="Center"/>
<!--入参参数名称-->
<TextBlock Grid.Column="3" MinWidth="50" Text="{Binding Name}" Margin="2,0,2,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<!--增加可选参数(如果有)-->
<view:ParamsArgControl
x:Name="ParamsArgControl"
ArgIndex="{Binding Index}"
MyNode="{Binding NodeModel}"
Width="12"
Grid.Column="5" Margin="2,0,2,0" HorizontalAlignment="Right" VerticalAlignment="Center"
Visibility="{Binding IsParams, Mode=OneWay,
Converter={StaticResource InvertedBoolConverter},ConverterParameter=Normal}"
/>
<ContentControl Content="{Binding}" Grid.Column="4" VerticalAlignment="Center">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--无须指定参数-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBlock Grid.Column="3" MinWidth="50" Text="无须指定参数"/>
<TextBlock Grid.Column="0" MinWidth="50" Text="无须指定参数"/>
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:选项类型-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Select" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />-->
<!--指定参数:选项类型-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Select" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<ComboBox Grid.Column="3"
MinWidth="50"
ItemsSource="{Binding Items}"
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox Grid.Column="0"
MinWidth="50"
ItemsSource="{Binding Items}"
SelectedItem="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:文本类型(可输入)-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Value" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<view:ArgJunctionControl x:Name="ArgJunctionControl" Grid.Column="0" ArgIndex="{Binding Index}" MyNode="{Binding NodeModel}" />-->
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
<!--指定参数:文本类型(可输入)-->
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsExplicitData}" Value="true" />
<Condition Binding="{Binding ExplicitTypeName}" Value="Value" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Index,StringFormat=agr{0}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<CheckBox Grid.Column="1" IsChecked="{Binding IsExplicitData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"/>
<TextBlock Grid.Column="2" MinWidth="50" Text="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<TextBox Grid.Column="3" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Column="0" MinWidth="50" Text="{Binding DataValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</DataTemplate>
</Setter.Value>
</Setter>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>

View File

@@ -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();
}
}
}

View File

@@ -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;
/// <summary>
/// 所在的节点
/// </summary>
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)));
/// <summary>
/// 参数的索引
/// </summary>
public int ArgIndex
{
get { return (int)GetValue(ArgIndexProperty); }
set { SetValue(ArgIndexProperty, value.ToString()); }
}
#endregion
/// <summary>
/// 控件重绘事件
/// </summary>
/// <param name="drawingContext"></param>
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();
// 控件获得鼠标焦点事件
/// <summary>
/// 控件获得鼠标焦点事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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();
}
// 控件失去鼠标焦点事件
/// <summary>
/// 控件失去鼠标焦点事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void JunctionControlBase_MouseLeave(object sender, MouseEventArgs e)
{
IsMouseOver = false;

View File

@@ -0,0 +1,26 @@
using System.Windows.Input;
namespace Serein.Workbench.Node
{
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> 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);
}
}