mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-20 16:26:34 +08:00
增加了节点树预览、节点定位,容器对象预览
This commit is contained in:
@@ -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
|
||||
// //{
|
||||
// // /// <summary>
|
||||
// // /// 登记了类型
|
||||
// // /// </summary>
|
||||
// // Registered,
|
||||
// // /// <summary>
|
||||
// // /// 构建了类型
|
||||
// // /// </summary>
|
||||
// // 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; }
|
||||
|
||||
//}
|
||||
/// <summary>
|
||||
/// 节点需要定位
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
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;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 节点需要定位
|
||||
/// </summary>
|
||||
event NodeLocatedHandler OnNodeLocate;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -461,7 +457,7 @@ namespace Serein.Library.Api
|
||||
|
||||
//bool TryGetNodeData(string methodName, out NodeData node);
|
||||
|
||||
#region Workbench
|
||||
#region 环境基础接口
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前项目
|
||||
@@ -501,8 +497,6 @@ namespace Serein.Library.Api
|
||||
/// </summary>
|
||||
void Exit();
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 设置流程起点节点
|
||||
/// </summary>
|
||||
@@ -549,21 +543,7 @@ namespace Serein.Library.Api
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
bool AddInterruptExpression(string key, string expression);
|
||||
/// <summary>
|
||||
/// 添加作用于指定节点的中断表达式
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
/// <param name="expression"></param>
|
||||
/// <returns></returns>
|
||||
// bool AddInterruptExpression(string nodeGuid,string expression);
|
||||
|
||||
// <summary>
|
||||
// 设置节点数据监视状态
|
||||
// </summary>
|
||||
// <param name="nodeGuid">需要监视的节点Guid</param>
|
||||
// <param name="isMonitor">是否监视</param>
|
||||
// void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 监视指定对象
|
||||
/// </summary>
|
||||
@@ -591,7 +571,7 @@ namespace Serein.Library.Api
|
||||
|
||||
#endregion
|
||||
|
||||
#region Start
|
||||
#region 启动器调用
|
||||
|
||||
/// <summary>
|
||||
/// 流程启动器调用,监视数据更新通知
|
||||
@@ -609,6 +589,17 @@ namespace Serein.Library.Api
|
||||
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region UI视觉
|
||||
|
||||
/// <summary>
|
||||
/// 节点定位
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
void NodeLocated(string nodeGuid);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IFlipflopContext> WaitTask(OrderSignal order = OrderSignal.A)
|
||||
public async Task<IFlipflopContext> WaitTask(OrderSignal order = OrderSignal.Command_1)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -19,12 +19,13 @@ namespace Net461DllTest.Flow
|
||||
{
|
||||
|
||||
private List<Form> forms = new List<Form>();
|
||||
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, Type>(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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,10 @@ namespace Serein.NodeFlow
|
||||
/// </summary>
|
||||
public event IOCMembersChangedHandler OnIOCMembersChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 节点需要定位
|
||||
/// </summary>
|
||||
public event NodeLocatedHandler OnNodeLocate;
|
||||
#endregion
|
||||
|
||||
#region 属性
|
||||
@@ -1037,7 +1041,16 @@ namespace Serein.NodeFlow
|
||||
|
||||
#endregion
|
||||
|
||||
#region 网络交互
|
||||
#region 视觉效果
|
||||
|
||||
/// <summary>
|
||||
/// 定位节点
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid"></param>
|
||||
public void NodeLocated(string nodeGuid)
|
||||
{
|
||||
OnNodeLocate?.Invoke(new NodeLocatedEventArgs(nodeGuid));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -150,10 +150,10 @@ namespace Serein.NodeFlow
|
||||
#region 初始化运行环境的Ioc容器
|
||||
// 清除节点使用的对象,筛选出需要初始化的方法描述
|
||||
var thisRuningMds = new List<MethodDetails>();
|
||||
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)
|
||||
|
||||
@@ -673,6 +673,7 @@ namespace Serein.NodeFlow.Tool
|
||||
*/
|
||||
|
||||
#endregion
|
||||
|
||||
#region 暂时不删(已注释)
|
||||
/* /// <summary>
|
||||
/// 表达式树构建多个参数,有返回值的方法
|
||||
|
||||
308
NodeFlow/Tool/ObjDynamicCreateHelper.cs
Normal file
308
NodeFlow/Tool/ObjDynamicCreateHelper.cs
Normal file
@@ -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<string, Type> typeCache = new Dictionary<string, Type>();
|
||||
|
||||
public static object Resolve(Dictionary<string, object> properties, string typeName)
|
||||
{
|
||||
var obj = CreateObjectWithProperties(properties, typeName);
|
||||
//SetPropertyValues(obj, properties);
|
||||
return obj;
|
||||
}
|
||||
public static bool TryResolve(Dictionary<string, object> 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<string, object> 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<Dictionary<string, object>>) // 处理数组类型
|
||||
{
|
||||
var nestedPropValue = (propValue as IList<Dictionary<string, object>>)[0];
|
||||
var nestedType = CreateObjectWithProperties(nestedPropValue, $"{propName}Element");
|
||||
propType = nestedType.GetType().MakeArrayType(); // 创建数组类型
|
||||
}
|
||||
else if(propValue is Dictionary<string, object> 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<string, object> properties)
|
||||
{
|
||||
var objType = obj.GetType();
|
||||
|
||||
foreach (var kvp in properties)
|
||||
{
|
||||
var propInfo = objType.GetProperty(kvp.Key);
|
||||
object value = kvp.Value;
|
||||
|
||||
// 如果值是嵌套的字典类型,递归处理嵌套对象
|
||||
if (value is Dictionary<string, object> 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<string, object> 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<string, object> nestedProperties)
|
||||
{
|
||||
var nestedObj = Activator.CreateInstance(propInfo.PropertyType);
|
||||
if (SetPropertyValuesWithValidation(nestedObj, nestedProperties))
|
||||
{
|
||||
propInfo.SetValue(obj, nestedObj);
|
||||
}
|
||||
else
|
||||
{
|
||||
allSuccessful = false; // 嵌套赋值失败
|
||||
}
|
||||
}
|
||||
else if (propValue is IList<Dictionary<string, object>> 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<Dictionary<string, object>>; // 假设值是一个列表,具体处理逻辑在赋值时
|
||||
}
|
||||
|
||||
// 处理嵌套类型的情况
|
||||
if (value is Dictionary<string, object> && targetType.IsClass && !targetType.IsPrimitive)
|
||||
{
|
||||
// 如果目标类型是一个复杂对象,并且值是一个字典,可能是嵌套对象
|
||||
return true; // 假设可以递归处理嵌套对象
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<int>
|
||||
//{
|
||||
// TargetObj = targetObj,
|
||||
// //MemberPath = memberPath,
|
||||
// Op = ParseValueTypeOperator<int>(operatorStr),
|
||||
// Value = value,
|
||||
// ArithmeticExpression = GetArithmeticExpression(parts[0])
|
||||
//};
|
||||
}
|
||||
#endregion
|
||||
#region 解析类型 double
|
||||
|
||||
@@ -121,40 +121,98 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
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<object> 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;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置目标的值
|
||||
/// </summary>
|
||||
@@ -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<object> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算数学简单表达式
|
||||
/// </summary>
|
||||
|
||||
@@ -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 = "";
|
||||
|
||||
@@ -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">
|
||||
<Grid>
|
||||
|
||||
@@ -23,7 +23,11 @@
|
||||
<KeyBinding Key="Escape" Command="{Binding CancelConnectionCommand}"/>
|
||||
</Window.InputBindings>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition Width="5"/>
|
||||
<ColumnDefinition Width="3*"/>
|
||||
@@ -31,7 +35,15 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<DockPanel Grid.Column="0" Background="#F5F5F5">
|
||||
<StackPanel Grid.Row="0" Grid.ColumnSpan="5" Background="#F5F5F5" Orientation="Horizontal" >
|
||||
<Button x:Name="ButtonDebugRun" Content="运行" Width="100" Margin="10" Click="ButtonDebugRun_Click"></Button>
|
||||
<Button x:Name="ButtonDebugFlipflopNode" Content="结束" Width="100" Margin="10" Click="ButtonDebugFlipflopNode_Click"></Button>
|
||||
<Button x:Name="ButtonStartFlowInSelectNode" Content="从选定节点开始" Width="100" Margin="10" Click="ButtonStartFlowInSelectNode_Click"></Button>
|
||||
<Button x:Name="ButtonResetCanvas" Content="重置画布" Width="100" Margin="10" Click="ButtonResetCanvas_Click"></Button>
|
||||
<Button x:Name="ButtonTestExpObj" Content="测试对象表达式" Width="100" Margin="10" Click="ButtonTestExpObj_Click"></Button>
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel Grid.Row="1" Grid.Column="0" Background="#F5F5F5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
@@ -64,31 +76,11 @@
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
|
||||
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
|
||||
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
|
||||
|
||||
<Grid Grid.Column="2" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="60"></RowDefinition>
|
||||
<RowDefinition Height="*"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Background="#F5F5F5" Orientation="Horizontal" >
|
||||
<Button x:Name="ButtonDebugRun" Content="运行" Width="100" Margin="10" Click="ButtonDebugRun_Click"></Button>
|
||||
<Button x:Name="ButtonDebugFlipflopNode" Content="结束" Width="100" Margin="10" Click="ButtonDebugFlipflopNode_Click"></Button>
|
||||
<Button x:Name="ButtonStartFlowInSelectNode" Content="从选定节点开始" Width="100" Margin="10" Click="ButtonStartFlowInSelectNode_Click"></Button>
|
||||
<!--<Button x:Name="ButtonLoadStartNodeTree" Content="加载起始节点树" Width="100" Margin="10" Click="ButtonLoadStartNodeTree_Click"></Button>-->
|
||||
<!--<Button x:Name="ButtonReflushCanvasConfig" Content="重置画布设置" Width="100" Margin="10" Click="ButtonReflushCanvasConfig_Click"></Button>-->
|
||||
<!--<Button x:Name="ButtonLoadCanvasConfig" Content="加载画布设置" Width="100" Margin="10" Click="ButtonLoadCanvasConfig_Click"></Button>-->
|
||||
</StackPanel>
|
||||
|
||||
<!--HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"-->
|
||||
|
||||
<StackPanel Grid.Row="1"
|
||||
x:Name="FlowChartStackPanel"
|
||||
|
||||
<Grid Grid.Row="1" Grid.Column="2" x:Name="FlowChartStackGrid">
|
||||
|
||||
<StackPanel x:Name="FlowChartStackPanel"
|
||||
ClipToBounds="True">
|
||||
<Canvas
|
||||
x:Name="FlowChartCanvas"
|
||||
@@ -187,9 +179,9 @@ Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Con
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<GridSplitter Grid.Column="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
|
||||
<GridSplitter Grid.Row="1" Grid.Column="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="Gray" />
|
||||
<!--IOC容器属性-->
|
||||
<Grid Grid.Column="4" >
|
||||
<Grid Grid.Row="1" Grid.Column="4" >
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
|
||||
@@ -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
|
||||
/// <param name="eventArgs"></param>
|
||||
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
||||
{
|
||||
//foreach(var connection in Connections)
|
||||
//{
|
||||
// connection.Refresh();
|
||||
//}
|
||||
//Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 节点需要定位
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
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);
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 控件抖动
|
||||
/// 来源:https://www.cnblogs.com/RedSky/p/17705411.html
|
||||
/// 作者:HotSky
|
||||
/// (……太好用了)
|
||||
/// </summary>
|
||||
/// <param name="translate"></param>
|
||||
/// <param name="power">抖动第一下偏移量</param>
|
||||
/// <param name="range">减弱幅度(小于等于power,大于0)</param>
|
||||
/// <param name="speed">持续系数(大于0),越大时间越长,</param>
|
||||
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; // 或者重新设置为默认值
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Guid 转 NodeControl
|
||||
/// </summary>
|
||||
/// <param name="nodeGuid">节点Guid</param>
|
||||
/// <returns>节点Model</returns>
|
||||
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
|
||||
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 与流程图与节点相关
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标在画布移动。
|
||||
@@ -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; // 防止事件传播影响其他控件
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -2256,6 +2363,8 @@ namespace Serein.WorkBench
|
||||
private void UnloadAllButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
FlowEnvironment.ClearAll();
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// 卸载DLL文件,清空当前项目
|
||||
@@ -2450,6 +2559,70 @@ namespace Serein.WorkBench
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对象装箱测试
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ButtonTestExpObj_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
string jsonString =
|
||||
"""
|
||||
{
|
||||
"Name": "张三",
|
||||
"Age": 24,
|
||||
"Address": {
|
||||
"City": "北京",
|
||||
"PostalCode": "10000"
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var externalData = new Dictionary<string, object>
|
||||
{
|
||||
{ "Name", "John" },
|
||||
{ "Age", 30 },
|
||||
{ "Addresses", new List<Dictionary<string, object>>
|
||||
{
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "Street", "123 Main St" },
|
||||
{ "City", "New York" }
|
||||
},
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
{ "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}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置画布
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
translateTransform.X = 0;
|
||||
translateTransform.Y = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Serein.WorkBench.Node.ViewModel
|
||||
set
|
||||
{
|
||||
isSelect = value;
|
||||
// OnPropertyChanged();
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="CustomTreeViewItemStyle" TargetType="TreeViewItem">
|
||||
<Setter Property="SnapsToDevicePixels" Value="true" />
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
|
||||
|
||||
</Style>
|
||||
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
@@ -21,7 +28,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Column="0" Width="1" x:Name="UpstreamTreeRectangle" Grid.Row="0" Fill="#4A82E4" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
||||
<TreeView Grid.Column="1" x:Name="UpstreamTreeNodes" BorderThickness="0"/>
|
||||
<TreeView Grid.Column="1" x:Name="UpstreamTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" x:Name="IsSucceedTreeGuid" Margin="0,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -29,7 +36,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsSucceedRectangle" Grid.Row="0" Fill="#04FC10" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
||||
<TreeView Grid.Column="1" x:Name="IsSucceedTreeNodes" BorderThickness="0"/>
|
||||
<TreeView Grid.Column="1" x:Name="IsSucceedTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2" x:Name="IsFailTreeGuid" Margin="0,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -38,7 +45,7 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsFailRectangle" Grid.Row="0" Fill="#F18905" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
||||
|
||||
<TreeView Grid.Column="1" x:Name="IsFailTreeNodes" BorderThickness="0"/>
|
||||
<TreeView Grid.Column="1" x:Name="IsFailTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="3" x:Name="IsErrorTreeGuid" Margin="0,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -46,7 +53,7 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsErrorRectangle" Grid.Row="0" Fill="#FE1343" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
||||
<TreeView Grid.Column="1" x:Name="IsErrorTreeNodes" BorderThickness="0"/>
|
||||
<TreeView Grid.Column="1" x:Name="IsErrorTreeNodes" BorderThickness="0" ItemContainerStyle="{StaticResource CustomTreeViewItemStyle}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user