mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-05-02 05:11:27 +08:00
增加了节点树预览、节点定位,容器对象预览
This commit is contained in:
@@ -345,32 +345,23 @@ namespace Serein.Library.Api
|
|||||||
}
|
}
|
||||||
public string Key { get; private set; }
|
public string Key { get; private set; }
|
||||||
public object Instance { 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
|
public interface IFlowEnvironment
|
||||||
{
|
{
|
||||||
#region 属性
|
#region 属性
|
||||||
@@ -447,6 +438,11 @@ namespace Serein.Library.Api
|
|||||||
event IOCMembersChangedHandler OnIOCMembersChanged;
|
event IOCMembersChangedHandler OnIOCMembersChanged;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点需要定位
|
||||||
|
/// </summary>
|
||||||
|
event NodeLocatedHandler OnNodeLocate;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
@@ -461,7 +457,7 @@ namespace Serein.Library.Api
|
|||||||
|
|
||||||
//bool TryGetNodeData(string methodName, out NodeData node);
|
//bool TryGetNodeData(string methodName, out NodeData node);
|
||||||
|
|
||||||
#region Workbench
|
#region 环境基础接口
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 保存当前项目
|
/// 保存当前项目
|
||||||
@@ -501,8 +497,6 @@ namespace Serein.Library.Api
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
void Exit();
|
void Exit();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置流程起点节点
|
/// 设置流程起点节点
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -549,21 +543,7 @@ namespace Serein.Library.Api
|
|||||||
/// <param name="expression"></param>
|
/// <param name="expression"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
bool AddInterruptExpression(string key, string expression);
|
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>
|
||||||
/// 监视指定对象
|
/// 监视指定对象
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -591,7 +571,7 @@ namespace Serein.Library.Api
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Start
|
#region 启动器调用
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 流程启动器调用,监视数据更新通知
|
/// 流程启动器调用,监视数据更新通知
|
||||||
@@ -609,6 +589,17 @@ namespace Serein.Library.Api
|
|||||||
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
|
void TriggerInterrupt(string nodeGuid, string expression, InterruptTriggerEventArgs.InterruptTriggerType type);
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region UI视觉
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点定位
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid"></param>
|
||||||
|
void NodeLocated(string nodeGuid);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace Net461DllTest.Flow
|
namespace Net461DllTest.Flow
|
||||||
{
|
{
|
||||||
[DynamicFlow] // 标记该类存在节点方法
|
[DynamicFlow]
|
||||||
public class LogicControl
|
public class LogicControl
|
||||||
{
|
{
|
||||||
[AutoInjection] // 标记该属性为依赖项,需要注入
|
[AutoInjection]
|
||||||
public PlcDevice MyPlc { get; set; }
|
public PlcDevice MyPlc { get; set; }
|
||||||
|
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ namespace Net461DllTest.Flow
|
|||||||
#region 触发器
|
#region 触发器
|
||||||
|
|
||||||
[NodeAction(NodeType.Flipflop, "等待信号触发", ReturnType = typeof(int))]
|
[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
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ namespace Net461DllTest.Flow
|
|||||||
{
|
{
|
||||||
|
|
||||||
private List<Form> forms = new List<Form>();
|
private List<Form> forms = new List<Form>();
|
||||||
public void OpenView(Form form)
|
public void OpenView(Form form, bool isTop)
|
||||||
{
|
{
|
||||||
form.FormClosing += (s, e) =>
|
form.FormClosing += (s, e) =>
|
||||||
{
|
{
|
||||||
// 关闭窗体时执行一些关于逻辑层的操作
|
// 关闭窗体时执行一些关于逻辑层的操作
|
||||||
};
|
};
|
||||||
|
form.TopMost = isTop;
|
||||||
form.Show();
|
form.Show();
|
||||||
forms.Add(form);
|
forms.Add(form);
|
||||||
}
|
}
|
||||||
@@ -67,21 +68,20 @@ namespace Net461DllTest.Flow
|
|||||||
|
|
||||||
|
|
||||||
[NodeAction(NodeType.Action, "打开窗体(指定枚举值)")]
|
[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);
|
var fromType = EnumHelper.GetBoundValue<FromId, Type>(fromId, attr => attr.Value);
|
||||||
if (fromType is null) return;
|
if (fromType is null) return;
|
||||||
if (context.Env.IOC.Instantiate(fromType) is Form form)
|
if (context.Env.IOC.Instantiate(fromType) is Form form)
|
||||||
{
|
{
|
||||||
ViewManagement.OpenView(form);
|
ViewManagement.OpenView(form, isTop);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[NodeAction(NodeType.Action, "打开窗体(使用转换器)")]
|
[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
|
public enum OrderSignal
|
||||||
{
|
{
|
||||||
A,
|
View_1,
|
||||||
B,
|
View_2,
|
||||||
C,
|
Command_1,
|
||||||
D,
|
Command_2,
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ namespace Serein.NodeFlow
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event IOCMembersChangedHandler OnIOCMembersChanged;
|
public event IOCMembersChangedHandler OnIOCMembersChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 节点需要定位
|
||||||
|
/// </summary>
|
||||||
|
public event NodeLocatedHandler OnNodeLocate;
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 属性
|
#region 属性
|
||||||
@@ -1037,7 +1041,16 @@ namespace Serein.NodeFlow
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 网络交互
|
#region 视觉效果
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 定位节点
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="nodeGuid"></param>
|
||||||
|
public void NodeLocated(string nodeGuid)
|
||||||
|
{
|
||||||
|
OnNodeLocate?.Invoke(new NodeLocatedEventArgs(nodeGuid));
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -150,10 +150,10 @@ namespace Serein.NodeFlow
|
|||||||
#region 初始化运行环境的Ioc容器
|
#region 初始化运行环境的Ioc容器
|
||||||
// 清除节点使用的对象,筛选出需要初始化的方法描述
|
// 清除节点使用的对象,筛选出需要初始化的方法描述
|
||||||
var thisRuningMds = new List<MethodDetails>();
|
var thisRuningMds = new List<MethodDetails>();
|
||||||
thisRuningMds.AddRange(runNodeMd.Where(md => md is not null));
|
thisRuningMds.AddRange(runNodeMd.Where(md => md?.ActingInstanceType is not null));
|
||||||
thisRuningMds.AddRange(initMethods.Where(md => md is not null));
|
thisRuningMds.AddRange(initMethods.Where(md => md?.ActingInstanceType is not null));
|
||||||
thisRuningMds.AddRange(loadingMethods.Where(md => md is not null));
|
thisRuningMds.AddRange(loadingMethods.Where(md => md?.ActingInstanceType is not null));
|
||||||
thisRuningMds.AddRange(exitMethods.Where(md => md is not null));
|
thisRuningMds.AddRange(exitMethods.Where(md => md?.ActingInstanceType is not null));
|
||||||
|
|
||||||
// .AddRange(initMethods).AddRange(loadingMethods).a
|
// .AddRange(initMethods).AddRange(loadingMethods).a
|
||||||
foreach (var nodeMd in thisRuningMds)
|
foreach (var nodeMd in thisRuningMds)
|
||||||
|
|||||||
@@ -673,6 +673,7 @@ namespace Serein.NodeFlow.Tool
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 暂时不删(已注释)
|
#region 暂时不删(已注释)
|
||||||
/* /// <summary>
|
/* /// <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
|
try
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(expression))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
var parse = ConditionParse(data, expression);
|
var parse = ConditionParse(data, expression);
|
||||||
var result = parse.Evaluate(data);
|
var result = parse.Evaluate(data);
|
||||||
return result;
|
return result;
|
||||||
@@ -104,7 +107,8 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
|||||||
{
|
{
|
||||||
// 如果不需要转为指定类型
|
// 如果不需要转为指定类型
|
||||||
memberPath = operatorStr;
|
memberPath = operatorStr;
|
||||||
targetObj = GetMemberValue(data, operatorStr);
|
targetObj = SerinExpressionEvaluator.Evaluate("@get " + operatorStr, data, out _);
|
||||||
|
//targetObj = GetMemberValue(data, operatorStr);
|
||||||
type = targetObj.GetType();
|
type = targetObj.GetType();
|
||||||
operatorStr = parts[1].ToLower(); //
|
operatorStr = parts[1].ToLower(); //
|
||||||
valueStr = string.Join(' ', parts.Skip(2));
|
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
|
#endregion
|
||||||
#region 解析类型 double
|
#region 解析类型 double
|
||||||
|
|||||||
@@ -121,40 +121,98 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
|||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
private static object GetMember(object target, string memberPath)
|
private static object GetMember(object target, string memberPath)
|
||||||
{
|
{
|
||||||
|
// 分割成员路径,按 '.' 处理多级访问
|
||||||
var members = memberPath.Split('.');
|
var members = memberPath.Split('.');
|
||||||
|
|
||||||
foreach (var member in members)
|
foreach (var member in members)
|
||||||
{
|
{
|
||||||
|
if (target == null) return null;
|
||||||
|
|
||||||
if (target is null) return null;
|
// 检查成员是否包含数组索引,例如 "cars[0]"
|
||||||
|
var arrayIndexStart = member.IndexOf('[');
|
||||||
|
if (arrayIndexStart != -1)
|
||||||
var property = target.GetType().GetProperty(member);
|
|
||||||
if (property != null)
|
|
||||||
{
|
{
|
||||||
|
// 解析数组/集合名与索引部分
|
||||||
target = property.GetValue(target);
|
var arrayName = member.Substring(0, arrayIndexStart);
|
||||||
|
var arrayIndexEnd = member.IndexOf(']');
|
||||||
}
|
if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1)
|
||||||
else
|
|
||||||
{
|
|
||||||
var field = target.GetType().GetField(member);
|
|
||||||
if (field != null)
|
|
||||||
{
|
{
|
||||||
|
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
|
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;
|
return target;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 设置目标的值
|
/// 设置目标的值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -178,26 +236,85 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
|||||||
{
|
{
|
||||||
var member = members[i];
|
var member = members[i];
|
||||||
|
|
||||||
var property = target.GetType().GetProperty(member);
|
// 检查是否包含数组索引
|
||||||
|
var arrayIndexStart = member.IndexOf('[');
|
||||||
if (property != null)
|
if (arrayIndexStart != -1)
|
||||||
{
|
{
|
||||||
|
// 解析数组名和索引
|
||||||
target = property.GetValue(target);
|
var arrayName = member.Substring(0, arrayIndexStart);
|
||||||
|
var arrayIndexEnd = member.IndexOf(']');
|
||||||
}
|
if (arrayIndexEnd == -1 || arrayIndexEnd <= arrayIndexStart + 1)
|
||||||
else
|
|
||||||
{
|
|
||||||
var field = target.GetType().GetField(member);
|
|
||||||
if (field != null)
|
|
||||||
{
|
{
|
||||||
|
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
|
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 lastMember = members.Last();
|
||||||
|
|
||||||
var lastProperty = target.GetType().GetProperty(lastMember);
|
var lastProperty = target.GetType().GetProperty(lastMember);
|
||||||
|
|
||||||
if (lastProperty != null)
|
if (lastProperty != null)
|
||||||
{
|
{
|
||||||
var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType);
|
var convertedValue = Convert.ChangeType(value, lastProperty.PropertyType);
|
||||||
@@ -227,7 +343,6 @@ namespace Serein.NodeFlow.Tool.SereinExpression
|
|||||||
|
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 计算数学简单表达式
|
/// 计算数学简单表达式
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Serein.Library.Entity;
|
using Serein.Library.Entity;
|
||||||
using Serein.NodeFlow;
|
using Serein.NodeFlow;
|
||||||
|
using Serein.NodeFlow.Tool;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
|
||||||
@@ -35,6 +36,9 @@ namespace Serein.WorkBench
|
|||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#if false //测试 操作表达式,条件表达式
|
#if false //测试 操作表达式,条件表达式
|
||||||
#region 测试数据
|
#region 测试数据
|
||||||
string expression = "";
|
string expression = "";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:Serein.WorkBench"
|
xmlns:local="clr-namespace:Serein.WorkBench"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Topmost="False"
|
Topmost="True"
|
||||||
Title="LogWindow" Height="600" Width="400"
|
Title="LogWindow" Height="600" Width="400"
|
||||||
Closing="Window_Closing">
|
Closing="Window_Closing">
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
<KeyBinding Key="Escape" Command="{Binding CancelConnectionCommand}"/>
|
<KeyBinding Key="Escape" Command="{Binding CancelConnectionCommand}"/>
|
||||||
</Window.InputBindings>
|
</Window.InputBindings>
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="300"/>
|
<ColumnDefinition Width="300"/>
|
||||||
<ColumnDefinition Width="5"/>
|
<ColumnDefinition Width="5"/>
|
||||||
<ColumnDefinition Width="3*"/>
|
<ColumnDefinition Width="3*"/>
|
||||||
@@ -31,7 +35,15 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</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>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
@@ -64,31 +76,11 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</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 Grid.Row="1" Grid.Column="2" x:Name="FlowChartStackGrid">
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
<StackPanel x:Name="FlowChartStackPanel"
|
||||||
</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"
|
|
||||||
|
|
||||||
ClipToBounds="True">
|
ClipToBounds="True">
|
||||||
<Canvas
|
<Canvas
|
||||||
x:Name="FlowChartCanvas"
|
x:Name="FlowChartCanvas"
|
||||||
@@ -187,9 +179,9 @@ Canvas.Top="{Binding ActualHeight, ElementName=FlowChartCanvas, Mode=OneWay, Con
|
|||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</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容器属性-->
|
<!--IOC容器属性-->
|
||||||
<Grid Grid.Column="4" >
|
<Grid Grid.Row="1" Grid.Column="4" >
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="auto"/>
|
<RowDefinition Height="auto"/>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using Serein.Library.Utils;
|
|||||||
using Serein.NodeFlow;
|
using Serein.NodeFlow;
|
||||||
using Serein.NodeFlow.Base;
|
using Serein.NodeFlow.Base;
|
||||||
using Serein.NodeFlow.Model;
|
using Serein.NodeFlow.Model;
|
||||||
|
using Serein.NodeFlow.Tool;
|
||||||
|
using Serein.NodeFlow.Tool.SereinExpression;
|
||||||
using Serein.WorkBench.Node;
|
using Serein.WorkBench.Node;
|
||||||
using Serein.WorkBench.Node.View;
|
using Serein.WorkBench.Node.View;
|
||||||
using Serein.WorkBench.Node.ViewModel;
|
using Serein.WorkBench.Node.ViewModel;
|
||||||
@@ -14,6 +16,7 @@ using Serein.WorkBench.Themes;
|
|||||||
using Serein.WorkBench.tool;
|
using Serein.WorkBench.tool;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Controls.Primitives;
|
using System.Windows.Controls.Primitives;
|
||||||
@@ -189,7 +192,12 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged;
|
FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged;
|
||||||
|
|
||||||
|
FlowEnvironment.OnNodeLocate += FlowEnvironment_OnNodeLocate;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private void InitCanvasUI()
|
private void InitCanvasUI()
|
||||||
{
|
{
|
||||||
canvasTransformGroup = new TransformGroup();
|
canvasTransformGroup = new TransformGroup();
|
||||||
@@ -200,7 +208,6 @@ namespace Serein.WorkBench
|
|||||||
canvasTransformGroup.Children.Add(translateTransform);
|
canvasTransformGroup.Children.Add(translateTransform);
|
||||||
|
|
||||||
FlowChartCanvas.RenderTransform = canvasTransformGroup;
|
FlowChartCanvas.RenderTransform = canvasTransformGroup;
|
||||||
//FlowChartCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private LogWindow InitConsoleOut()
|
private LogWindow InitConsoleOut()
|
||||||
@@ -260,11 +267,6 @@ namespace Serein.WorkBench
|
|||||||
/// <param name="eventArgs"></param>
|
/// <param name="eventArgs"></param>
|
||||||
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
//foreach(var connection in Connections)
|
|
||||||
//{
|
|
||||||
// connection.Refresh();
|
|
||||||
//}
|
|
||||||
//Console.WriteLine((FlowChartStackPanel.ActualWidth, FlowChartStackPanel.ActualHeight));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -319,8 +321,16 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
string fromNodeGuid = eventArgs.FromNodeGuid;
|
string fromNodeGuid = eventArgs.FromNodeGuid;
|
||||||
string toNodeGuid = eventArgs.ToNodeGuid;
|
string toNodeGuid = eventArgs.ToNodeGuid;
|
||||||
NodeControlBase fromNode = GuidToControl(fromNodeGuid);
|
NodeControlBase? fromNode = GuidToControl(fromNodeGuid);
|
||||||
NodeControlBase toNode = GuidToControl(toNodeGuid);
|
NodeControlBase? toNode = GuidToControl(toNodeGuid);
|
||||||
|
if(fromNode != null && toNode != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ConnectionType connectionType = eventArgs.ConnectionType;
|
ConnectionType connectionType = eventArgs.ConnectionType;
|
||||||
Action? action = null;
|
Action? action = null;
|
||||||
@@ -387,7 +397,8 @@ namespace Serein.WorkBench
|
|||||||
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
|
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
var nodeGuid = eventArgs.NodeGuid;
|
var nodeGuid = eventArgs.NodeGuid;
|
||||||
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
|
||||||
|
if (nodeControl is null) return;
|
||||||
if (selectNodeControls.Count > 0)
|
if (selectNodeControls.Count > 0)
|
||||||
{
|
{
|
||||||
if (selectNodeControls.Contains(nodeControl))
|
if (selectNodeControls.Contains(nodeControl))
|
||||||
@@ -489,11 +500,12 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
string oldNodeGuid = eventArgs.OldNodeGuid;
|
string oldNodeGuid = eventArgs.OldNodeGuid;
|
||||||
string newNodeGuid = eventArgs.NewNodeGuid;
|
string newNodeGuid = eventArgs.NewNodeGuid;
|
||||||
NodeControlBase newStartNodeControl = GuidToControl(newNodeGuid);
|
NodeControlBase? newStartNodeControl = GuidToControl(newNodeGuid);
|
||||||
|
if (newStartNodeControl is null) return;
|
||||||
if (!string.IsNullOrEmpty(oldNodeGuid))
|
if (!string.IsNullOrEmpty(oldNodeGuid))
|
||||||
{
|
{
|
||||||
NodeControlBase oldStartNodeControl = GuidToControl(oldNodeGuid);
|
NodeControlBase? oldStartNodeControl = GuidToControl(oldNodeGuid);
|
||||||
|
if (oldStartNodeControl is null) return;
|
||||||
oldStartNodeControl.BorderBrush = Brushes.Black;
|
oldStartNodeControl.BorderBrush = Brushes.Black;
|
||||||
oldStartNodeControl.BorderThickness = new Thickness(0);
|
oldStartNodeControl.BorderThickness = new Thickness(0);
|
||||||
}
|
}
|
||||||
@@ -554,7 +566,8 @@ namespace Serein.WorkBench
|
|||||||
private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs)
|
private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
string nodeGuid = eventArgs.NodeGuid;
|
string nodeGuid = eventArgs.NodeGuid;
|
||||||
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
|
||||||
|
if (nodeControl is null) return;
|
||||||
if (eventArgs.Class == InterruptClass.None)
|
if (eventArgs.Class == InterruptClass.None)
|
||||||
{
|
{
|
||||||
nodeControl.ViewModel.IsInterrupt = false;
|
nodeControl.ViewModel.IsInterrupt = false;
|
||||||
@@ -591,7 +604,7 @@ namespace Serein.WorkBench
|
|||||||
private void FlowEnvironment_OnInterruptTrigger(InterruptTriggerEventArgs eventArgs)
|
private void FlowEnvironment_OnInterruptTrigger(InterruptTriggerEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
string nodeGuid = eventArgs.NodeGuid;
|
string nodeGuid = eventArgs.NodeGuid;
|
||||||
NodeControlBase nodeControl = GuidToControl(nodeGuid);
|
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
|
||||||
if (nodeControl is null) return;
|
if (nodeControl is null) return;
|
||||||
if(eventArgs.Type == InterruptTriggerEventArgs.InterruptTriggerType.Exp)
|
if(eventArgs.Type == InterruptTriggerEventArgs.InterruptTriggerType.Exp)
|
||||||
{
|
{
|
||||||
@@ -613,24 +626,118 @@ namespace Serein.WorkBench
|
|||||||
IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance);
|
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>
|
/// <summary>
|
||||||
/// Guid 转 NodeControl
|
/// Guid 转 NodeControl
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="nodeGuid">节点Guid</param>
|
/// <param name="nodeGuid">节点Guid</param>
|
||||||
/// <returns>节点Model</returns>
|
/// <returns>节点Model</returns>
|
||||||
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
|
/// <exception cref="ArgumentNullException">无法获取节点、Guid/节点为null时报错</exception>
|
||||||
private NodeControlBase GuidToControl(string nodeGuid)
|
private NodeControlBase? GuidToControl(string nodeGuid)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(nodeGuid))
|
if (string.IsNullOrEmpty(nodeGuid))
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid));
|
throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid));
|
||||||
}
|
}
|
||||||
if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase? nodeControl) || nodeControl is null)
|
if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase? nodeControl) || nodeControl is null)
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
|
|
||||||
throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid));
|
throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid));
|
||||||
}
|
}
|
||||||
return nodeControl;
|
return nodeControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 加载项目文件后触发事件相关方法
|
#region 加载项目文件后触发事件相关方法
|
||||||
@@ -800,6 +907,7 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
|
||||||
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
|
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
|
||||||
|
|
||||||
@@ -948,7 +1056,7 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 与流程图相关的拖拽操作
|
#region 与流程图与节点相关
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 鼠标在画布移动。
|
/// 鼠标在画布移动。
|
||||||
@@ -1138,7 +1246,7 @@ namespace Serein.WorkBench
|
|||||||
if(sender is NodeControlBase nodeControl)
|
if(sender is NodeControlBase nodeControl)
|
||||||
{
|
{
|
||||||
ChangeViewerObjOfNode(nodeControl);
|
ChangeViewerObjOfNode(nodeControl);
|
||||||
if (nodeControl.ViewModel.Node.MethodDetails.IsProtectionParameter) return;
|
if (nodeControl?.ViewModel?.Node?.MethodDetails?.IsProtectionParameter == true) return;
|
||||||
IsControlDragging = true;
|
IsControlDragging = true;
|
||||||
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
|
||||||
((UIElement)sender).CaptureMouse(); // 捕获鼠标
|
((UIElement)sender).CaptureMouse(); // 捕获鼠标
|
||||||
@@ -1168,7 +1276,7 @@ namespace Serein.WorkBench
|
|||||||
// 获取element控件的旧位置
|
// 获取element控件的旧位置
|
||||||
double oldLeft = Canvas.GetLeft(element);
|
double oldLeft = Canvas.GetLeft(element);
|
||||||
double oldTop = Canvas.GetTop(element);
|
double oldTop = Canvas.GetTop(element);
|
||||||
|
|
||||||
// 计算被选择控件的偏移量
|
// 计算被选择控件的偏移量
|
||||||
double deltaX = (int)(currentPosition.X - startControlDragPoint.X);
|
double deltaX = (int)(currentPosition.X - startControlDragPoint.X);
|
||||||
double deltaY = (int)(currentPosition.Y - startControlDragPoint.Y);
|
double deltaY = (int)(currentPosition.Y - startControlDragPoint.Y);
|
||||||
@@ -1221,12 +1329,12 @@ namespace Serein.WorkBench
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
|
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
|
||||||
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
|
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
|
||||||
|
|
||||||
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
|
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
|
||||||
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
|
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
|
||||||
|
//Console.WriteLine((Canvas.GetLeft(block), Canvas.GetTop(block)));
|
||||||
|
|
||||||
// 限制控件不超出FlowChartCanvas的边界
|
// 限制控件不超出FlowChartCanvas的边界
|
||||||
if (newLeft >= 0 && newLeft + block.ActualWidth <= FlowChartCanvas.ActualWidth)
|
if (newLeft >= 0 && newLeft + block.ActualWidth <= FlowChartCanvas.ActualWidth)
|
||||||
@@ -1245,9 +1353,9 @@ namespace Serein.WorkBench
|
|||||||
}
|
}
|
||||||
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
|
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
|
||||||
{
|
{
|
||||||
|
|
||||||
var node = nodeControl?.ViewModel?.Node;
|
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 key = node.Guid;
|
||||||
var instance = node.GetFlowData();
|
var instance = node.GetFlowData();
|
||||||
@@ -1421,8 +1529,6 @@ namespace Serein.WorkBench
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#region 拖动画布实现缩放平移效果
|
#region 拖动画布实现缩放平移效果
|
||||||
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
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 (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;
|
if (e.Delta > 0 && scaleTransform.ScaleY > 1.5) return;
|
||||||
// 获取鼠标在 Canvas 内的相对位置
|
// 获取鼠标在 Canvas 内的相对位置
|
||||||
var mousePosition = e.GetPosition(FlowChartCanvas);
|
var mousePosition = e.GetPosition(FlowChartCanvas);
|
||||||
|
|
||||||
// 缩放因子,根据滚轮方向调整
|
// 缩放因子,根据滚轮方向调整
|
||||||
double zoomFactor = e.Delta > 0 ? 0.1 : -0.1;
|
//double zoomFactor = e.Delta > 0 ? 0.1 : -0.1;
|
||||||
//double zoomFactor = e.Delta > 0 ? 1.1 : 0.9;
|
double zoomFactor = e.Delta > 0 ? 1.1 : 0.9;
|
||||||
|
|
||||||
// 当前缩放比例
|
// 当前缩放比例
|
||||||
double oldScale = scaleTransform.ScaleX;
|
double oldScale = scaleTransform.ScaleX;
|
||||||
// double newScale = oldScale * zoomFactor;
|
double newScale = oldScale * zoomFactor;
|
||||||
double newScale = oldScale + zoomFactor;
|
//double newScale = oldScale + zoomFactor;
|
||||||
// 更新缩放比例
|
// 更新缩放比例
|
||||||
scaleTransform.ScaleX = newScale;
|
scaleTransform.ScaleX = newScale;
|
||||||
scaleTransform.ScaleY = newScale;
|
scaleTransform.ScaleY = newScale;
|
||||||
@@ -1608,6 +1714,8 @@ namespace Serein.WorkBench
|
|||||||
#endregion
|
#endregion
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 画布中框选节点控件动作
|
#region 画布中框选节点控件动作
|
||||||
@@ -1706,7 +1814,6 @@ namespace Serein.WorkBench
|
|||||||
|
|
||||||
}
|
}
|
||||||
e.Handled = true; // 防止事件传播影响其他控件
|
e.Handled = true; // 防止事件传播影响其他控件
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -2256,6 +2363,8 @@ namespace Serein.WorkBench
|
|||||||
private void UnloadAllButton_Click(object sender, RoutedEventArgs e)
|
private void UnloadAllButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
FlowEnvironment.ClearAll();
|
FlowEnvironment.ClearAll();
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 卸载DLL文件,清空当前项目
|
/// 卸载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层面上显示为 带箭头指向的贝塞尔曲线
|
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Serein.WorkBench.Node.ViewModel
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
isSelect = value;
|
isSelect = value;
|
||||||
// OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,15 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
xmlns:local="clr-namespace:Serein.WorkBench.Themes"
|
||||||
mc:Ignorable="d"
|
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>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="*"/>
|
<RowDefinition Height="*"/>
|
||||||
@@ -21,7 +28,7 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Rectangle Grid.Column="0" Width="1" x:Name="UpstreamTreeRectangle" Grid.Row="0" Fill="#4A82E4" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
<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 Grid.Row="1" x:Name="IsSucceedTreeGuid" Margin="0,0,0,0">
|
<Grid Grid.Row="1" x:Name="IsSucceedTreeGuid" Margin="0,0,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -29,7 +36,7 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsSucceedRectangle" Grid.Row="0" Fill="#04FC10" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
<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 Grid.Row="2" x:Name="IsFailTreeGuid" Margin="0,0,0,0">
|
<Grid Grid.Row="2" x:Name="IsFailTreeGuid" Margin="0,0,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -38,7 +45,7 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsFailRectangle" Grid.Row="0" Fill="#F18905" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
<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 Grid.Row="3" x:Name="IsErrorTreeGuid" Margin="0,0,0,0">
|
<Grid Grid.Row="3" x:Name="IsErrorTreeGuid" Margin="0,0,0,0">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -46,7 +53,7 @@
|
|||||||
<ColumnDefinition Width="*"/>
|
<ColumnDefinition Width="*"/>
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Rectangle Grid.Column="0" Width="1" x:Name="IsErrorRectangle" Grid.Row="0" Fill="#FE1343" Margin="4,1,4,1" IsHitTestVisible="False"/>
|
<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>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -74,9 +74,14 @@ namespace Serein.WorkBench.Themes
|
|||||||
{ConnectionType.IsError, []},
|
{ConnectionType.IsError, []},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
string itemName = rootNodeModel?.MethodDetails?.MethodTips;
|
||||||
|
if (string.IsNullOrEmpty(itemName))
|
||||||
|
{
|
||||||
|
itemName = rootNodeModel.ControlType.ToString();
|
||||||
|
}
|
||||||
var rootNode = new TreeViewItem
|
var rootNode = new TreeViewItem
|
||||||
{
|
{
|
||||||
Header = rootNodeModel.MethodDetails.MethodTips,
|
Header = itemName,
|
||||||
Tag = nodeTreeModel,
|
Tag = nodeTreeModel,
|
||||||
};
|
};
|
||||||
LoadNodeItem(this, nodeTreeModel);
|
LoadNodeItem(this, nodeTreeModel);
|
||||||
@@ -136,19 +141,27 @@ namespace Serein.WorkBench.Themes
|
|||||||
RootNode = child,
|
RootNode = child,
|
||||||
ChildNodes = child.SuccessorNodes,
|
ChildNodes = child.SuccessorNodes,
|
||||||
};
|
};
|
||||||
|
string itemName = child?.MethodDetails?.MethodTips;
|
||||||
|
if (string.IsNullOrEmpty(itemName))
|
||||||
|
{
|
||||||
|
itemName = child.ControlType.ToString();
|
||||||
|
}
|
||||||
TreeViewItem treeViewItem = new TreeViewItem
|
TreeViewItem treeViewItem = new TreeViewItem
|
||||||
{
|
{
|
||||||
Header = child.MethodDetails.MethodTips,
|
Header = itemName,
|
||||||
Tag = tmpNodeTreeModel
|
Tag = tmpNodeTreeModel
|
||||||
};
|
};
|
||||||
treeViewItem.Expanded += TreeViewItem_Expanded;
|
treeViewItem.Expanded += TreeViewItem_Expanded;
|
||||||
ContextMenu contextMenu = new ContextMenu();
|
|
||||||
|
var contextMenu = new ContextMenu();
|
||||||
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", (s, e) =>
|
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", (s, e) =>
|
||||||
{
|
{
|
||||||
flowEnvironment.StartFlowInSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
|
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);
|
treeViewer.Items.Add(treeViewItem);
|
||||||
}
|
}
|
||||||
guid.Visibility = Visibility.Visible;
|
guid.Visibility = Visibility.Visible;
|
||||||
@@ -190,12 +203,18 @@ namespace Serein.WorkBench.Themes
|
|||||||
RootNode = childNodeModel,
|
RootNode = childNodeModel,
|
||||||
ChildNodes = childNodeModel.SuccessorNodes,
|
ChildNodes = childNodeModel.SuccessorNodes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
string itemName = childNodeModel?.MethodDetails?.MethodTips;
|
||||||
|
if (string.IsNullOrEmpty(itemName))
|
||||||
|
{
|
||||||
|
itemName = childNodeModel.ControlType.ToString();
|
||||||
|
}
|
||||||
TreeViewItem treeViewItem = new TreeViewItem
|
TreeViewItem treeViewItem = new TreeViewItem
|
||||||
{
|
{
|
||||||
Header = childNodeModel.MethodDetails.MethodTips,
|
Header = itemName,
|
||||||
Tag = tempNodeTreeModel
|
Tag = tempNodeTreeModel
|
||||||
};
|
};
|
||||||
treeViewItem.Margin = new Thickness(-15, 0, 0, 0);
|
treeViewItem.Margin = new Thickness(-20, 0, 0, 0);
|
||||||
treeViewItem.Visibility = Visibility.Visible;
|
treeViewItem.Visibility = Visibility.Visible;
|
||||||
treeView.Items.Add(treeViewItem);
|
treeView.Items.Add(treeViewItem);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user