diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index 9648f51..84b22f6 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -33,26 +33,29 @@ namespace Serein.Library ScaleY = model.ScaleY, ViewX = model.ViewX, ViewY = model.ViewY, - StartNode = model.StartNode, + StartNode = model.StartNode?.Guid, }; } /// /// 从画布信息加载 /// - /// - /// - public static void LoadInfo(this FlowCanvasDetails model, FlowCanvasDetailsInfo info) + /// + /// + public static void LoadInfo(this FlowCanvasDetails canvasModel, FlowCanvasDetailsInfo canvasInfo) { - model.Guid = info.Guid; - model.Height = info.Height; - model.Width = info.Width; - model.Name = info.Name; - model.ScaleX = info.ScaleX; - model.ScaleY = info.ScaleY; - model.ViewX = info.ViewX; - model.ViewY = info.ViewY; - model.StartNode = info.StartNode; + canvasModel.Guid = canvasInfo.Guid; + canvasModel.Height = canvasInfo.Height; + canvasModel.Width = canvasInfo.Width; + canvasModel.Name = canvasInfo.Name; + canvasModel.ScaleX = canvasInfo.ScaleX; + canvasModel.ScaleY = canvasInfo.ScaleY; + canvasModel.ViewX = canvasInfo.ViewX; + canvasModel.ViewY = canvasInfo.ViewY; + if(canvasModel.Env.TryGetNodeModel(canvasInfo.StartNode,out var nodeModel)) + { + canvasModel.StartNode = nodeModel; + } } /// @@ -61,7 +64,7 @@ namespace Serein.Library /// public static ParameterData[] SaveParameterInfo(this NodeModelBase nodeModel) { - if (nodeModel.MethodDetails.ParameterDetailss == null) + if (nodeModel.MethodDetails is null || nodeModel.MethodDetails.ParameterDetailss == null) { return new ParameterData[0]; } diff --git a/Library/FlowNode/FlowCanvasDetails.cs b/Library/FlowNode/FlowCanvasDetails.cs index c8d6e49..6725cfa 100644 --- a/Library/FlowNode/FlowCanvasDetails.cs +++ b/Library/FlowNode/FlowCanvasDetails.cs @@ -24,7 +24,6 @@ namespace Serein.Library Env = env; } - public IFlowEnvironment Env { get; } /// @@ -87,19 +86,17 @@ namespace Serein.Library [PropertyInfo(IsNotification = true)] private double _scaleY = 1; - /// - /// 起始节点私有属性 + /// 起始节点 /// - private string _startNode; + [PropertyInfo] + private NodeModelBase _startNode; } public partial class FlowCanvasDetails { - - } diff --git a/Library/FlowNode/ParameterDetails.cs b/Library/FlowNode/ParameterDetails.cs index 9434293..d864a62 100644 --- a/Library/FlowNode/ParameterDetails.cs +++ b/Library/FlowNode/ParameterDetails.cs @@ -248,7 +248,8 @@ namespace Serein.Library } else { - inputParameter = context.GetFlowData(previousNode).Value; // 当前传递的数据 + var flowData = context.GetFlowData(previousNode); + inputParameter = flowData.Value; // 当前传递的数据 } } else @@ -259,7 +260,16 @@ namespace Serein.Library } if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) { - inputParameter = context.GetFlowData(argSourceNodeModel).Value; + var flowData = context.GetFlowData(argSourceNodeModel); + if(flowData is null) + { + inputParameter = null; + } + else + { + + inputParameter = flowData.Value; + } } else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) { diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index cd7fd46..260eb95 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -8,6 +8,7 @@ using Serein.NodeFlow.Tool; using System; using System.Collections.Specialized; using System.Diagnostics; +using System.Net.Http.Headers; using System.Net.Mime; using System.Reactive; using System.Reflection; @@ -228,7 +229,7 @@ namespace Serein.NodeFlow.Env /// /// UI线程操作类 /// - public UIContextOperation UIContextOperation { get; set; } + public UIContextOperation UIContextOperation { get; private set; } /// /// 节点视图模型管理类 @@ -389,13 +390,12 @@ namespace Serein.NodeFlow.Env } var ft = new FlowTask(); ft.GetNodes = () => NodeModels.Values.Where(node => node.CanvasDetails.Guid.Equals(guid)).ToList(); - var startNodeModel = NodeModels.GetValueOrDefault(canvasModel.StartNode); - if(startNodeModel is null) + if (canvasModel.StartNode.Guid is null) { SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}"); return false; } - ft.GetStartNode = () => startNodeModel; + ft.GetStartNode = () => canvasModel.StartNode; flowTasks.Add(guid, ft); } #endregion @@ -841,7 +841,7 @@ namespace Serein.NodeFlow.Env var model = new FlowCanvasDetails(this); model.LoadInfo(info); FlowCanvass.Add(model.Guid, model); - UIContextOperation.Invoke(() => + UIContextOperation?.Invoke(() => { OnCanvasCreate.Invoke(new CanvasCreateEventArgs(model)); }); @@ -868,7 +868,7 @@ namespace Serein.NodeFlow.Env } if (FlowCanvass.Remove(canvasGuid)) { - UIContextOperation.Invoke(() => + UIContextOperation?.Invoke(() => { OnCanvasRemove.Invoke(new CanvasRemoveEventArgs(canvasGuid)); }); @@ -878,6 +878,60 @@ namespace Serein.NodeFlow.Env return false; } + /// + /// 从节点信息创建节点,并返回状态指示是否创建成功 + /// + /// + /// + private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo) + { + if (!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) + { + return false; + } + + #region 获取方法描述 + MethodDetails? methodDetails; + if (controlType.IsBaseNode()) + { + // 加载基础节点 + methodDetails = new MethodDetails(); + } + else + { + if (string.IsNullOrEmpty(nodeInfo.MethodName)) return false; + // 加载方法节点 + FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息 + } + #endregion + + var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 + if (nodeModel is null) + { + nodeInfo.Guid = string.Empty; + return false; + } + if (FlowCanvass.TryGetValue(nodeInfo.CanvasGuid, out var canvasModel)) + { + + // 节点与画布互相绑定 + // 需要在UI线程上进行添加,否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常 + nodeModel.CanvasDetails = canvasModel; + UIContextOperation?.Invoke(() => canvasModel.Nodes.Add(nodeModel)); + + nodeModel.LoadInfo(nodeInfo); // 创建节点model + TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 + } + else + { + SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在"); + return false; + } + + UIContextOperation?.Invoke(() => + OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上 + return true; + } /// @@ -885,57 +939,37 @@ namespace Serein.NodeFlow.Env /// /// 节点信息 /// + /// public async Task LoadNodeInfosAsync(List nodeInfos) { #region 从NodeInfo创建NodeModel + // 流程接口节点最后才创建 + + List flowCallNodeInfos = []; foreach (NodeInfo? nodeInfo in nodeInfos) { - - if (!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) + if(nodeInfo.Type == nameof(NodeControlType.FlowCall)) { - continue; - } - - #region 获取方法描述 - MethodDetails? methodDetails; - if (controlType.IsBaseNode()) - { - // 加载基础节点 - methodDetails = new MethodDetails(); + flowCallNodeInfos.Add(nodeInfo); } else { - if (string.IsNullOrEmpty(nodeInfo.MethodName)) continue; - // 加载方法节点 - FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息 - } - #endregion + if (!CreateNodeFromNodeInfo(nodeInfo)) + { + SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}"); + continue; + } + } + } - var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 - if (nodeModel is null) + // 创建流程接口节点 + foreach (NodeInfo? nodeInfo in flowCallNodeInfos) + { + if (!CreateNodeFromNodeInfo(nodeInfo)) { - nodeInfo.Guid = string.Empty; + SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}"); continue; } - if(FlowCanvass.TryGetValue(nodeInfo.CanvasGuid, out var canvasModel)) - { - - // 节点与画布互相绑定 - // 需要在UI线程上进行添加,否则会报 “不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改”异常 - nodeModel.CanvasDetails = canvasModel; - UIContextOperation.Invoke(() => canvasModel.Nodes.Add(nodeModel)); - - nodeModel.LoadInfo(nodeInfo); // 创建节点model - TryAddNode(nodeModel); // 加载项目时将节点加载到环境中 - } - else - { - SereinEnv.WriteLine(InfoType.ERROR, $"加载节点[{nodeInfo.Guid}]时发生异常,画布[{nodeInfo.CanvasGuid}]不存在"); - return; - } - - UIContextOperation.Invoke(() => - OnNodeCreate?.Invoke(new NodeCreateEventArgs(nodeInfo.CanvasGuid, nodeModel, nodeInfo.Position))); // 添加到UI上 } #endregion @@ -960,7 +994,7 @@ namespace Serein.NodeFlow.Env var result = nodeContainer.PlaceNode(nodeModel); if (result) { - UIContextOperation.Invoke(() => OnNodePlace?.Invoke( + UIContextOperation?.Invoke(() => OnNodePlace?.Invoke( new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid))); } @@ -1044,7 +1078,7 @@ namespace Serein.NodeFlow.Env #endregion - UIContextOperation.Invoke(() => + UIContextOperation?.Invoke(() => { OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); }); @@ -1136,7 +1170,7 @@ namespace Serein.NodeFlow.Env var result = nodeContainer.PlaceNode(nodeModel); // 放置在容器节点 if (result) { - UIContextOperation.Invoke(() => + UIContextOperation?.Invoke(() => { OnNodePlace?.Invoke(new NodePlaceEventArgs(canvasGuid, nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 }); @@ -1168,7 +1202,7 @@ namespace Serein.NodeFlow.Env var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出 if (result) { - UIContextOperation.Invoke(() => + UIContextOperation?.Invoke(() => { OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(canvasGuid, nodeGuid)); // 重新放置在画布上 }); @@ -1223,17 +1257,26 @@ namespace Serein.NodeFlow.Env } } - // 遍历所有后继节点,从那些后继节点中的前置节点集合移除该节点 - foreach (var snc in remoteNode.SuccessorNodes) + + if(remoteNode.ControlType == NodeControlType.FlowCall) { - var connectionType = snc.Key; // 连接类型 - for (int i = 0; i < snc.Value.Count; i++) + + } + else + { + // 遍历所有后继节点,从那些后继节点中的前置节点集合移除该节点 + foreach (var snc in remoteNode.SuccessorNodes) { - NodeModelBase? toNode = snc.Value[i]; + var connectionType = snc.Key; // 连接类型 + for (int i = 0; i < snc.Value.Count; i++) + { + NodeModelBase? toNode = snc.Value[i]; - await RemoteConnectAsync(canvasGuid, remoteNode, toNode, connectionType); + await RemoteConnectAsync(canvasGuid, remoteNode, toNode, connectionType); + } } + } @@ -1484,10 +1527,10 @@ namespace Serein.NodeFlow.Env { if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(newNodeGuid, out var newStartNodeModel)) { - return Task.FromResult(canvasModel.StartNode ?? string.Empty); + return Task.FromResult(string.Empty); } SetStartNode(canvasModel, newStartNodeModel); - return Task.FromResult(canvasModel.StartNode ?? string.Empty); + return Task.FromResult(canvasModel.StartNode.Guid ?? string.Empty); } /// @@ -1743,7 +1786,7 @@ namespace Serein.NodeFlow.Env if (OperatingSystem.IsWindows()) { - UIContextOperation.Invoke(() => OnNodeConnectChange?.Invoke( + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke( new NodeConnectChangeEventArgs( canvasGuid, fromNode.Guid, @@ -1776,7 +1819,7 @@ namespace Serein.NodeFlow.Env if (OperatingSystem.IsWindows()) { - UIContextOperation.Invoke(() => OnNodeConnectChange?.Invoke( + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke( new NodeConnectChangeEventArgs( canvasGuid, fromNode.Guid, @@ -1887,6 +1930,12 @@ namespace Serein.NodeFlow.Env /// 连接关系 private bool ConnectInvokeOfNode(string canvasGuid, NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType invokeType) { + if (fromNode.ControlType == NodeControlType.FlowCall) + { + SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点不可调用下一个节点。" + + $"{Environment.NewLine}流程节点:{fromNode.Guid}"); + return false; + } if (!FlowCanvass.ContainsKey(canvasGuid)) { return false; @@ -1907,7 +1956,8 @@ namespace Serein.NodeFlow.Env { flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 } - + var isOverwriting = false; + ConnectionInvokeType overwritingCt = ConnectionInvokeType.None; var isPass = false; foreach (ConnectionInvokeType ctType in ct) { @@ -1917,21 +1967,33 @@ namespace Serein.NodeFlow.Env FromExistInTo = ToOnF.Length > 0; if (ToExistOnFrom && FromExistInTo) { - SereinEnv.WriteLine(InfoType.WARN, "起始节点已与目标节点存在连接"); - isPass = false; + if(ctType == invokeType) + { + SereinEnv.WriteLine(InfoType.WARN, $"起始节点已与目标节点存在连接。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); + return false; + } + isOverwriting = true; + overwritingCt = ctType; } else { // 检查是否可能存在异常 if (!ToExistOnFrom && FromExistInTo) { - SereinEnv.WriteLine(InfoType.WARN, "目标节点不是起始节点的子节点,起始节点却是目标节点的父节点"); + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}"); isPass = false; } else if (ToExistOnFrom && !FromExistInTo) { // - SereinEnv.WriteLine(InfoType.WARN, " 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); + SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点。" + + $"{Environment.NewLine}起始节点:{fromNode.Guid}" + + $"{Environment.NewLine}目标节点:{toNode.Guid}" + + $""); isPass = false; } else @@ -1942,7 +2004,11 @@ namespace Serein.NodeFlow.Env } if (isPass) { - + if (isOverwriting) // 需要替换 + { + fromNode.SuccessorNodes[overwritingCt].Remove(toNode); // 从起始节点子分支中移除 + toNode.PreviousNodes[overwritingCt].Remove(fromNode); // 从目标节点父分支中移除 + } fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点的子分支 toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点的父分支 if (OperatingSystem.IsWindows()) @@ -1991,13 +2057,38 @@ namespace Serein.NodeFlow.Env } var toNodeArgSourceGuid = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid; - if (!string.IsNullOrEmpty(toNodeArgSourceGuid)) + var toNodeArgSourceType = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType; + if(fromNode.Guid == toNodeArgSourceGuid && toNodeArgSourceType == connectionArgSourceType) + { + SereinEnv.WriteLine(InfoType.INFO, $"节点之间已建立过连接关系,此次操作将不会执行" + + $"起始节点:{fromNode.Guid}" + + $"目标节点:{toNode.Guid}" + + $"参数索引:{argIndex}" + + $"参数类型:{connectionArgSourceType}"); + UIContextOperation?.Invoke(() => + OnNodeConnectChange?.Invoke( + new NodeConnectChangeEventArgs( + canvasGuid, + fromNode.Guid, // 从哪个节点开始 + toNode.Guid, // 连接到那个节点 + JunctionOfConnectionType.Arg, + argIndex, // 连接线的样式类型 + connectionArgSourceType, + NodeConnectChangeEventArgs.ConnectChangeType.Create // 是创建连接还是删除连接 + ))); // 通知UI + + return true; + } + + if (!string.IsNullOrEmpty(toNodeArgSourceGuid) ) { await RemoteConnectAsync(canvasGuid, fromNode, toNode, argIndex); } + toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = fromNode.Guid; toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = connectionArgSourceType; - UIContextOperation.Invoke(() => + + UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke( new NodeConnectChangeEventArgs( canvasGuid, @@ -2019,15 +2110,14 @@ namespace Serein.NodeFlow.Env /// 起始节点 private void SetStartNode(FlowCanvasDetails cavnasModel, NodeModelBase newStartNode) { - var oldNodeGuid = cavnasModel.StartNode; + var oldNodeGuid = cavnasModel.StartNode?.Guid; /*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel)) { newStartNode.IsStart = false; }*/ - cavnasModel.StartNode = newStartNode.Guid; + cavnasModel.StartNode = newStartNode; //newStartNode.IsStart = true; - - UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode))); + UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode.Guid))); } diff --git a/NodeFlow/Model/SingleFlowCallNode.cs b/NodeFlow/Model/SingleFlowCallNode.cs index a3435da..dcf5618 100644 --- a/NodeFlow/Model/SingleFlowCallNode.cs +++ b/NodeFlow/Model/SingleFlowCallNode.cs @@ -16,14 +16,17 @@ namespace Serein.NodeFlow.Model [NodeProperty(ValuePath = NodeValuePath.Node)] public partial class SingleFlowCallNode { - - + /// + /// 目标公开节点 + /// + [PropertyInfo(IsNotification = true)] + private string targetNodeGuid; /// /// 使用目标节点的参数(如果为true,则使用目标节点的入参,如果为false,则使用节点自定义入参) /// [PropertyInfo(IsNotification = true)] - private bool _isShareParam = true ; + private bool _isShareParam ; } @@ -38,9 +41,13 @@ namespace Serein.NodeFlow.Model /// private NodeModelBase targetNode; /// + /// 缓存的方法信息 + /// + public MethodDetails CacheMethodDetails { get; private set; } + /// /// 接口节点Guid /// - public string? TargetNodeGuid => targetNode?.Guid; + //public string? TargetNodeGuid => targetNode?.Guid; public SingleFlowCallNode(IFlowEnvironment environment) : base(environment) @@ -48,43 +55,70 @@ namespace Serein.NodeFlow.Model } + /// /// 重置接口节点 /// public void ResetTargetNode() { - if(targetNode is not null) + if (targetNode is not null) { // 取消接口 - targetNode.PropertyChanged -= TargetNode_PropertyChanged; - this.MethodDetails = null; - foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) - { - this.SuccessorNodes[ctType] = new List(); - } + TargetNodeGuid = string.Empty; } - - } /// - /// 设置接口节点(如果传入null,则视为取消设置) + /// 设置接口节点 /// - /// - public void SetTargetNode(NodeModelBase? value) + /// + public void SetTargetNode(string? nodeGuid) { - if( value is null) + if (nodeGuid is null || !Env.TryGetNodeModel(nodeGuid, out _)) { return; } - this.targetNode = value; - tmpMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份 - targetNode.PropertyChanged += TargetNode_PropertyChanged; - this.MethodDetails = tmpMethodDetails; - this.SuccessorNodes = targetNode.SuccessorNodes; + TargetNodeGuid = nodeGuid; + } + + partial void OnTargetNodeGuidChanged(string value) + { + if (string.IsNullOrEmpty(value) || !Env.TryGetNodeModel(value, out targetNode)) + { + // 取消设置接口节点 + targetNode.PropertyChanged -= TargetNode_PropertyChanged; + this.MethodDetails = new MethodDetails(); + /*foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + this.SuccessorNodes[ctType] = new List(); + }*/ + } + else + { + //if (this.MethodDetails.ActingInstanceType.FullName.Equals()) + + if(!this.IsShareParam + && CacheMethodDetails is not null + && targetNode.MethodDetails.AssemblyName.Equals(CacheMethodDetails.AssemblyName) + && targetNode.MethodDetails.MethodName.Equals(CacheMethodDetails.MethodName)) + { + this.MethodDetails = CacheMethodDetails; + } + else + { + CacheMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份 + targetNode.PropertyChanged += TargetNode_PropertyChanged; + this.MethodDetails = CacheMethodDetails; + /*foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + this.SuccessorNodes[ctType] = targetNode.SuccessorNodes[ctType]; + }*/ + } + + } + OnPropertyChanged(nameof(MethodDetails)); } - private MethodDetails tmpMethodDetails; partial void OnIsShareParamChanged(bool value) { if (targetNode is null) @@ -93,40 +127,26 @@ namespace Serein.NodeFlow.Model } if (value) { - tmpMethodDetails = this.MethodDetails; + CacheMethodDetails = this.MethodDetails; this.MethodDetails = targetNode.MethodDetails; } else { - this.MethodDetails = tmpMethodDetails; - OnPropertyChanged(nameof(MethodDetails)); + this.MethodDetails = CacheMethodDetails; } + + OnPropertyChanged(nameof(MethodDetails)); } - /* partial void OnTargetNodeGuidChanged(string value) - { - var guid = value; - if (string.IsNullOrEmpty(guid)) - { - targetNode = null; - return; - } - if (!Env.TryGetNodeModel(guid, out targetNode)) - { - SereinEnv.WriteLine(InfoType.ERROR, $"流程接口找不到节点{guid}"); - return; - } - SetTargetNode(targetNode); - //OnIsShareParamChanged(IsShareParam); // 更新参数状态 - }*/ - - private void TargetNode_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { // 如果不再公开 if (sender is NodeModelBase node && !node.IsPublic) { - this.SuccessorNodes = []; + foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes) + { + this.SuccessorNodes[ctType] = []; + } targetNode.PropertyChanged -= TargetNode_PropertyChanged; } } @@ -168,7 +188,12 @@ namespace Serein.NodeFlow.Model { throw new ArgumentNullException(); } - return await base.ExecutingAsync(context, token); + if (IsShareParam) + { + this.MethodDetails = targetNode.MethodDetails; + } + this.SuccessorNodes = targetNode.SuccessorNodes; + return await base.ExecutingAsync(context, token); } @@ -192,19 +217,30 @@ namespace Serein.NodeFlow.Model /// public override void LoadCustomData(NodeInfo nodeInfo) { + CacheMethodDetails = this.MethodDetails; // 缓存 string targetNodeGuid = nodeInfo.CustomData?.TargetNodeGuid ?? ""; this.IsShareParam = nodeInfo.CustomData?.IsShareParam; if (Env.TryGetNodeModel(targetNodeGuid, out var targetNode)) { + TargetNodeGuid = targetNode.Guid; this.targetNode = targetNode; } else { SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点[{this.Guid}]无法找到对应的节点:{targetNodeGuid}"); } + } + public override void Remove() + { + var tmp = this; + targetNode = null; + CacheMethodDetails = null; + + } + } } \ No newline at end of file diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index 4bbcf63..8f9ab04 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -61,6 +61,7 @@ namespace Serein.Workbench { getSyncContext = () => uiContext; } + }); UIContextOperation? uIContextOperation = null; @@ -84,6 +85,12 @@ namespace Serein.Workbench public partial class App : Application { private static IServiceProvider? ServiceProvider; + + /// + /// UI线程 + /// + public static UIContextOperation UIContextOperation => App.GetService() ?? throw new NullReferenceException(); + public static T GetService() where T : class { return ServiceProvider?.GetService() ?? throw new NullReferenceException(); @@ -111,7 +118,7 @@ namespace Serein.Workbench { await Task.Delay(500); #if DEBUG - if (1 ==1) + if (1 == 1) { // 这里是测试代码,可以删除 string filePath; diff --git a/Workbench/Node/Junction/ConnectionLineShape.cs b/Workbench/Node/Junction/ConnectionLineShape.cs index 122adaf..c573dbe 100644 --- a/Workbench/Node/Junction/ConnectionLineShape.cs +++ b/Workbench/Node/Junction/ConnectionLineShape.cs @@ -60,7 +60,15 @@ namespace Serein.Workbench.Node.View endPoint = end; this.strokeThickness = 4; InitElementPoint(isDotted, isTop); - InvalidateVisual(); // 触发重绘 + + _ = Task.Run(async () => + { + await App.UIContextOperation.InvokeAsync(() => + { + InvalidateVisual(); // 触发重绘 + }); + }); + } diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index a4b77fd..f72b9a9 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -1,7 +1,9 @@ -using Serein.Library; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Serein.Library; using Serein.Library.Api; using Serein.Workbench.Api; using Serein.Workbench.Node.ViewModel; +using Serein.Workbench.Themes; using Serein.Workbench.Views; using System.Windows; using System.Windows.Controls; @@ -146,7 +148,7 @@ namespace Serein.Workbench.Node.View /// /// /// - protected T FindVisualChild(DependencyObject parent) where T : DependencyObject + protected static T FindVisualChild(DependencyObject parent) where T : DependencyObject { for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) { @@ -165,13 +167,58 @@ namespace Serein.Workbench.Node.View return null; } + protected static JunctionControlBase[] GetArgJunction(NodeControlBase nodeControl, MethodDetailsControl methodDetailsControl) + { + // 获取 MethodDetailsControl 实例 + try + { + var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl + if (itemsControl != null) + { + var md = nodeControl.ViewModel.NodeModel.MethodDetails; + if (md is null) + { + return []; + } + if(md.ParameterDetailss is null) + { + return []; + } + var argDataJunction = new JunctionControlBase[md.ParameterDetailss.Length]; + var controls = new List(); + + for (int i = 0; i < itemsControl.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + if (container != null) + { + var argControl = FindVisualChild(container); + if (argControl != null) + { + controls.Add(argControl); // 收集 ArgJunctionControl 实例 + } + } + } + return argDataJunction = controls.ToArray(); + } + return []; + } + catch (Exception ex) + { + + SereinEnv.WriteLine(InfoType.ERROR,$"节点获取入参控制点时发生异常{Environment.NewLine}节点:{nodeControl.ViewModel.NodeModel.Guid}"); + SereinEnv.WriteLine(ex); + return []; + } + } + } - + //public class FLowNodeObObservableCollection : ObservableCollection //{ diff --git a/Workbench/Node/View/ActionNodeControl.xaml.cs b/Workbench/Node/View/ActionNodeControl.xaml.cs index ec69362..5cfa4f0 100644 --- a/Workbench/Node/View/ActionNodeControl.xaml.cs +++ b/Workbench/Node/View/ActionNodeControl.xaml.cs @@ -1,5 +1,7 @@ -using Serein.NodeFlow.Model; +using Serein.Library; +using Serein.NodeFlow.Model; using Serein.Workbench.Node.ViewModel; +using Serein.Workbench.Themes; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Controls; @@ -42,35 +44,7 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - - private JunctionControlBase[] GetArgJunction() - { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); - - for (int i = 0; i < itemsControl.Items.Count; i++) - { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) - { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } - } - } - return argDataJunction = controls.ToArray(); - } - return []; - } - + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this, MethodDetailsControl); } } diff --git a/Workbench/Node/View/ConnectionControl.cs b/Workbench/Node/View/ConnectionControl.cs index 5eacfb9..cda5603 100644 --- a/Workbench/Node/View/ConnectionControl.cs +++ b/Workbench/Node/View/ConnectionControl.cs @@ -196,7 +196,7 @@ namespace Serein.Workbench.Node.View { leftCenterOfEndLocation = Start.MyCenterPoint; rightCenterOfStartLocation = End.MyCenterPoint; - + (Point startPoint, Point endPoint) = RefreshPoint(Canvas, Start, End); var connectionType = Start.JunctionType.ToConnectyionType(); bool isDotted; @@ -214,7 +214,7 @@ namespace Serein.Workbench.Node.View BezierLine = new ConnectionLineShape(LineType, startPoint, endPoint, brush, isDotted); Grid.SetZIndex(BezierLine, -9999999); // 置底 Canvas.Children.Add(BezierLine); - + ConfigureLineContextMenu(); //配置右键菜单 } diff --git a/Workbench/Node/View/ExpOpNodeControl.xaml.cs b/Workbench/Node/View/ExpOpNodeControl.xaml.cs index 4a96d69..bfd7369 100644 --- a/Workbench/Node/View/ExpOpNodeControl.xaml.cs +++ b/Workbench/Node/View/ExpOpNodeControl.xaml.cs @@ -43,6 +43,7 @@ namespace Serein.Workbench.Node.View /// 方法入参控制点(可能有,可能没) /// private JunctionControlBase[] argDataJunction; + /// /// 方法入参控制点(可能有,可能没) /// diff --git a/Workbench/Node/View/FlipflopNodeControl.xaml.cs b/Workbench/Node/View/FlipflopNodeControl.xaml.cs index b2d68e8..f9c1e73 100644 --- a/Workbench/Node/View/FlipflopNodeControl.xaml.cs +++ b/Workbench/Node/View/FlipflopNodeControl.xaml.cs @@ -34,34 +34,7 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - - private JunctionControlBase[] GetArgJunction() - { - // 获取 MethodDetailsControl 实例 - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); - - for (int i = 0; i < itemsControl.Items.Count; i++) - { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) - { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } - } - } - return argDataJunction = controls.ToArray(); - } - return []; - } + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this, MethodDetailsControl); } } diff --git a/Workbench/Node/View/FlowCallNodeControl.xaml.cs b/Workbench/Node/View/FlowCallNodeControl.xaml.cs index 33c1923..bf726f2 100644 --- a/Workbench/Node/View/FlowCallNodeControl.xaml.cs +++ b/Workbench/Node/View/FlowCallNodeControl.xaml.cs @@ -55,36 +55,9 @@ namespace Serein.Workbench.Node.View /// /// 方法入参控制点(可能有,可能没) /// - JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); - - private JunctionControlBase[] GetArgJunction() - { - // 获取 MethodDetailsControl 实例 - //var methodDetailsControl = ViewModel.NodeModel.IsShareParam ? this.SelectMethodDetailsControl : this.MyMethodDetailsControl; - var methodDetailsControl = this.MethodDetailsControl; - var itemsControl = FindVisualChild(methodDetailsControl); // 查找 ItemsControl - if (itemsControl != null) - { - var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length]; - var controls = new List(); - - for (int i = 0; i < itemsControl.Items.Count; i++) - { - var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; - if (container != null) - { - var argControl = FindVisualChild(container); - if (argControl != null) - { - controls.Add(argControl); // 收集 ArgJunctionControl 实例 - } - } - } - return argDataJunction = controls.ToArray(); - } - return []; - } + JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this,MethodDetailsControl); + } } diff --git a/Workbench/Node/View/UINodeControl.xaml b/Workbench/Node/View/UINodeControl.xaml index b7b8318..3c3621a 100644 --- a/Workbench/Node/View/UINodeControl.xaml +++ b/Workbench/Node/View/UINodeControl.xaml @@ -32,8 +32,9 @@ - + + + diff --git a/Workbench/Node/View/UINodeControl.xaml.cs b/Workbench/Node/View/UINodeControl.xaml.cs index 4d7beac..a7fa9e1 100644 --- a/Workbench/Node/View/UINodeControl.xaml.cs +++ b/Workbench/Node/View/UINodeControl.xaml.cs @@ -22,6 +22,7 @@ namespace Serein.Workbench.Node.View /// public partial class UINodeControl : NodeControlBase, INodeJunction { + private new UINodeControlViewModel ViewModel { get; } public UINodeControl() { base.ViewModel.IsEnabledOnView = true; @@ -30,6 +31,7 @@ namespace Serein.Workbench.Node.View public UINodeControl(UINodeControlViewModel viewModel) : base(viewModel) { + ViewModel = viewModel; DataContext = viewModel; InitializeComponent(); @@ -49,8 +51,8 @@ namespace Serein.Workbench.Node.View private void NodeControlBase_Loaded(object sender, RoutedEventArgs e) { - UINodeControlViewModel vm = (UINodeControlViewModel)DataContext; - vm.InitAdapter(userControl => { + //ViewModel.InitAdapter(); + ViewModel.InitAdapter(userControl => { EmbedContainer.Child = userControl; }); diff --git a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs index c3c783e..cbb74da 100644 --- a/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/FlowCallNodeControlViewModel.cs @@ -68,10 +68,9 @@ namespace Serein.Workbench.Node.ViewModel { return; } - if (targetNodeControl.FlowCanvas is FlowCanvasView view - && view.DataContext is FlowCanvasViewModel viewModel) + if (targetNodeControl.FlowCanvas is FlowCanvasView view ) { - SelectCanvas = viewModel; + SelectCanvas = view.ViewModel; SelectNode = targetNodeControl.ViewModel.NodeModel; } } @@ -80,8 +79,10 @@ namespace Serein.Workbench.Node.ViewModel { flowEEForwardingService.OnCanvasCreate += (e) => RershCanvass(); // 画布创建了 flowEEForwardingService.OnCanvasRemove += (e) => RershCanvass(); // 画布移除了 + } + partial void OnSelectCanvasChanged(FlowCanvasViewModel value) { FlowCallNode.ResetTargetNode(); @@ -89,7 +90,12 @@ namespace Serein.Workbench.Node.ViewModel partial void OnSelectNodeChanged(NodeModelBase value) { - FlowCallNode.SetTargetNode(value); + if(value is null) + { + FlowCallNode.ResetTargetNode(); + return; + } + FlowCallNode.SetTargetNode(value.Guid); } private void RershCanvass() diff --git a/Workbench/Node/ViewModel/UINodeControlViewModel.cs b/Workbench/Node/ViewModel/UINodeControlViewModel.cs index e7a9b3b..81f7890 100644 --- a/Workbench/Node/ViewModel/UINodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/UINodeControlViewModel.cs @@ -1,4 +1,5 @@ -using Serein.Library; +using CommunityToolkit.Mvvm.ComponentModel; +using Serein.Library; using Serein.Library.Api; using Serein.NodeFlow.Model; using System; @@ -10,17 +11,24 @@ using System.Windows.Controls; namespace Serein.Workbench.Node.ViewModel { - public class UINodeControlViewModel : NodeControlViewModelBase + public partial class UINodeControlViewModel : NodeControlViewModelBase { private SingleUINode NodeModel => (SingleUINode)base.NodeModel; //public IEmbeddedContent Adapter => NodeModel.Adapter; + /// + /// 节点UI的对应内容 + /// + [ObservableProperty] + private UserControl _nodeUIContent; + + public UINodeControlViewModel(NodeModelBase nodeModel) : base(nodeModel) { - //NodeModel.Adapter.GetWindowHandle(); + } - public void InitAdapter(Action setUIDisplayHandle) + public void InitAdapter() { Task.Factory.StartNew(async () => { @@ -32,11 +40,32 @@ namespace Serein.Workbench.Node.ViewModel && NodeModel.Adapter.GetUserControl() is UserControl userControl) { NodeModel.Env.UIContextOperation.Invoke(() => + { + NodeUIContent = userControl; + }); + } + }); + } + + + public void InitAdapter(Action setUIDisplayHandle) + { + Task.Factory.StartNew(async () => + { + var context = new DynamicContext(NodeModel.Env); + var cts = new CancellationTokenSource(); + var result = await NodeModel.ExecutingAsync(context, cts.Token); + cts?.Dispose(); + if (context.NextOrientation == ConnectionInvokeType.IsSucceed + && NodeModel.Adapter.GetUserControl() is UserControl userControl) + { + NodeModel.Env.UIContextOperation.Invoke(() => { setUIDisplayHandle.Invoke(userControl); }); } }); } + } } diff --git a/Workbench/Services/FlowEEForwardingService.cs b/Workbench/Services/FlowEEForwardingService.cs index 13d6118..2bfe3f5 100644 --- a/Workbench/Services/FlowEEForwardingService.cs +++ b/Workbench/Services/FlowEEForwardingService.cs @@ -13,6 +13,7 @@ using Serein.Library; using Serein.Library.Utils; using Serein.Workbench.Avalonia.Api; using Serein.Workbench.Api; +using System.Diagnostics; namespace Serein.Workbench.Services { diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index c3c25dd..362abe6 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -8,7 +8,9 @@ using Serein.Workbench.Node.View; using Serein.Workbench.Node.ViewModel; using Serein.Workbench.ViewModels; using Serein.Workbench.Views; +using System; using System.Text; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -210,7 +212,9 @@ namespace Serein.Workbench.Services (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => () => flow.RemoveArgConnection(fromNode, toNode, e.ArgIndex), // 移除节点之间的参数传递关系 _ => null }; + action?.Invoke(); + return; } diff --git a/Workbench/ViewModels/CanvasNodeTreeViewModel.cs b/Workbench/ViewModels/CanvasNodeTreeViewModel.cs index 26186a1..0155c38 100644 --- a/Workbench/ViewModels/CanvasNodeTreeViewModel.cs +++ b/Workbench/ViewModels/CanvasNodeTreeViewModel.cs @@ -32,10 +32,7 @@ namespace Serein.Workbench.ViewModels /// private void OnViewCanvasChanged(FlowCanvasView flowCanvas) { - if (flowCanvas.DataContext is FlowCanvasViewModel vm) - { - Model = vm.Model; - } + Model = flowCanvas.ViewModel.Model; } } } diff --git a/Workbench/ViewModels/FlowCanvasViewModel.cs b/Workbench/ViewModels/FlowCanvasViewModel.cs index be49fe7..bcaedb4 100644 --- a/Workbench/ViewModels/FlowCanvasViewModel.cs +++ b/Workbench/ViewModels/FlowCanvasViewModel.cs @@ -17,7 +17,6 @@ namespace Serein.Workbench.ViewModels { public partial class FlowCanvasViewModel : ObservableObject { - /// /// 画布当前的节点 /// diff --git a/Workbench/ViewModels/FlowEditViewModel.cs b/Workbench/ViewModels/FlowEditViewModel.cs index f993307..502ea71 100644 --- a/Workbench/ViewModels/FlowEditViewModel.cs +++ b/Workbench/ViewModels/FlowEditViewModel.cs @@ -43,16 +43,16 @@ namespace Serein.Workbench.ViewModels flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 创建了画布 flowNodeService.OnRemoveFlowCanvasView += OnRemoveFlowCanvasView; // 移除了画布 - this.PropertyChanged += OnPropertyChanged; + //this.PropertyChanged += OnPropertyChanged; } - private void OnPropertyChanged(object? value, PropertyChangedEventArgs e) + partial void OnSelectedTabChanged(FlowEditorTabModel value) { - if (this.SelectedTab is null) return; - flowNodeService.CurrentSelectCanvas = this.SelectedTab.Content; + flowNodeService.CurrentSelectCanvas = value.Content; } + #region 响应环境事件 private void OnCreateFlowCanvasView(FlowCanvasView canvas) { diff --git a/Workbench/Views/FlowCanvasView.xaml.cs b/Workbench/Views/FlowCanvasView.xaml.cs index d692678..7deaf60 100644 --- a/Workbench/Views/FlowCanvasView.xaml.cs +++ b/Workbench/Views/FlowCanvasView.xaml.cs @@ -52,187 +52,17 @@ namespace Serein.Workbench.Views private readonly IFlowEnvironment flowEnvironment; private readonly IKeyEventService keyEventService; private readonly FlowNodeService flowNodeService; + private readonly IFlowEEForwardingService flowEEForwardingService; /// /// 存储所有的连接。考虑集成在运行环境中。 /// private List Connections { get; } = []; + /// + /// 画布模型 + /// + public FlowCanvasViewModel ViewModel => this.DataContext as FlowCanvasViewModel ?? throw new ArgumentNullException(); - private FlowCanvasViewModel ViewModel => this.DataContext as FlowCanvasViewModel ?? throw new ArgumentNullException(); - - #region 画布接口实现 - private IFlowCanvas Api => this; - - public string Guid - { - get - { - return ViewModel.Model.Guid; - } - } - - public string Name => ViewModel.Model.Name; - FlowCanvasDetails IFlowCanvas.Model => ViewModel.Model; - void IFlowCanvas.Remove(NodeControlBase nodeControl) - { - ViewModel.NodeControls.Remove(nodeControl.ViewModel.NodeModel.Guid); - FlowChartCanvas.Dispatcher.Invoke(() => - { - FlowChartCanvas.Children.Remove(nodeControl); - }); - } - void IFlowCanvas.Add(NodeControlBase nodeControl) - { - ViewModel.NodeControls.TryAdd(nodeControl.ViewModel.NodeModel.Guid, nodeControl); - FlowChartCanvas.Dispatcher.Invoke(() => - { - FlowChartCanvas.Children.Add(nodeControl); - if(nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.UI) - { - // 需要切换到对应画布,尽可能让UI线程获取到适配器 - var edit = App.GetService().FlowEditViewModel; - var tab = edit.CanvasTabs.First(tab => tab.Content == this); - App.GetService().FlowEditViewModel.SelectedTab = tab; - - } - }); - - ConfigureNodeEvents(nodeControl); // 配置相关事件 - ConfigureContextMenu(nodeControl); // 添加右键菜单 - - } - - void IFlowCanvas.CreateInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionInvokeType type) - { - if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) - { - SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); - return; - } - JunctionControlBase startJunction = IFormJunction.NextStepJunction; - JunctionControlBase endJunction = IToJunction.ExecuteJunction; - var connection = new ConnectionControl( - FlowChartCanvas, - type, - startJunction, - endJunction - ); - - //if (toNodeControl is FlipflopNodeControl flipflopControl - // && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 - //{ - // NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 - //} - Connections.Add(connection); - fromNodeControl.AddCnnection(connection); - toNodeControl.AddCnnection(connection); - EndConnection(); // 环境触发了创建节点连接事件 - - - } - void IFlowCanvas.RemoveInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl) - { - if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) - { - SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); - return; - } - JunctionControlBase startJunction = IFormJunction.NextStepJunction; - JunctionControlBase endJunction = IToJunction.ExecuteJunction; - - var removeConnections = Connections.Where(c => - c.Start.Equals(startJunction) - && c.End.Equals(endJunction) - && (c.Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke - || c.End.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)) - .ToList(); - - - foreach (var connection in removeConnections) - { - Connections.Remove(connection); - fromNodeControl.RemoveConnection(connection); // 移除连接 - toNodeControl.RemoveConnection(connection); // 移除连接 - - //if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) - //{ - // JudgmentFlipFlopNode(control); // 连接关系变更时判断 - //} - } - } - void IFlowCanvas.CreateArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl,ConnectionArgSourceType type, int index) - { - if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) - { - SereinEnv.WriteLine(InfoType.INFO, "非预期的情况"); - return; - } - - JunctionControlBase startJunction = type switch - { - ConnectionArgSourceType.GetPreviousNodeData => IFormJunction.ReturnDataJunction, // 自身节点 - ConnectionArgSourceType.GetOtherNodeData => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 - ConnectionArgSourceType.GetOtherNodeDataOfInvoke => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 - _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 - }; - - if (IToJunction.ArgDataJunction.Length <= index) - { - _ = Task.Run(async () => - { - await Task.Delay(500); - Api.CreateArgConnection(fromNodeControl, toNodeControl, type, index); - }); - return; // // 尝试重新连接 - } - JunctionControlBase endJunction = IToJunction.ArgDataJunction[index]; - LineType lineType = LineType.Bezier; - // 添加连接 - var connection = new ConnectionControl( - lineType, - FlowChartCanvas, - index, - type, - startJunction, - endJunction, - IToJunction - ); - Connections.Add(connection); - fromNodeControl.AddCnnection(connection); - toNodeControl.AddCnnection(connection); - EndConnection(); // 环境触发了创建节点连接事件 - } - void IFlowCanvas.RemoveArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, int index) - { - if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) - { - SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); - return; - } - JunctionControlBase startJunction = IFormJunction.NextStepJunction; - JunctionControlBase endJunction = IToJunction.ExecuteJunction; - - var removeConnections = Connections.Where(c => - c.Start.Equals(startJunction) - && c.End.Equals(endJunction) - && (c.Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke - || c.End.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)) - .ToList(); - - - foreach (var connection in removeConnections) - { - Connections.Remove(connection); - fromNodeControl.RemoveConnection(connection); // 移除连接 - toNodeControl.RemoveConnection(connection); // 移除连接 - //if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) - //{ - // JudgmentFlipFlopNode(control); // 连接关系变更时判断 - //} - } - } - - #endregion #region 与画布相关的字段 @@ -301,8 +131,10 @@ namespace Serein.Workbench.Views flowEnvironment = App.GetService(); flowNodeService = App.GetService(); keyEventService = App.GetService(); + flowEEForwardingService = App.GetService(); flowNodeService.OnCreateNode += OnCreateNode; - keyEventService.OnKeyDown += KeyEventService_OnKeyDown; + keyEventService.OnKeyDown += KeyEventService_OnKeyDown; + //flowEEForwardingService.OnProjectLoaded += FlowEEForwardingService_OnProjectLoaded; // 缩放平移容器 canvasTransformGroup = new TransformGroup(); @@ -313,6 +145,13 @@ namespace Serein.Workbench.Views FlowChartCanvas.RenderTransform = canvasTransformGroup; SetBinding(model); + + + } + + private void FlowEEForwardingService_OnProjectLoaded(ProjectLoadedEventArgs eventArgs) + { + RefreshAllLine(); } @@ -336,16 +175,18 @@ namespace Serein.Workbench.Views } + /// /// 当前画布创建了节点 /// /// private void OnCreateNode(NodeControlBase nodeControl) { - if (!nodeControl.FlowCanvas.Guid.Equals(Guid)) - { - return; - } + if (!nodeControl.FlowCanvas.Guid.Equals(Guid)) + { + // 防止事件传播到其它画布 + return; + } var p = nodeControl.ViewModel.NodeModel.Position; PositionOfUI position = new PositionOfUI(p.X, p.Y); if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 @@ -409,6 +250,188 @@ namespace Serein.Workbench.Views #endregion + #region 画布接口实现 + private IFlowCanvas Api => this; + + public string Guid + { + get + { + return ViewModel.Model.Guid; + } + } + + public string Name => ViewModel.Model.Name; + FlowCanvasDetails IFlowCanvas.Model => ViewModel.Model; + void IFlowCanvas.Remove(NodeControlBase nodeControl) + { + ViewModel.NodeControls.Remove(nodeControl.ViewModel.NodeModel.Guid); + FlowChartCanvas.Dispatcher.Invoke(() => + { + FlowChartCanvas.Children.Remove(nodeControl); + }); + } + void IFlowCanvas.Add(NodeControlBase nodeControl) + { + + ViewModel.NodeControls.TryAdd(nodeControl.ViewModel.NodeModel.Guid, nodeControl); + + FlowChartCanvas.Dispatcher.Invoke(() => + { + + FlowChartCanvas.Children.Add(nodeControl); + + if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.UI) + { + // 需要切换到对应画布,尽可能让UI线程获取到适配器 + var edit = App.GetService().FlowEditViewModel; + var tab = edit.CanvasTabs.First(tab => tab.Content == this); + App.GetService().FlowEditViewModel.SelectedTab = tab; + + } + }); + ConfigureNodeEvents(nodeControl); // 配置相关事件 + ConfigureContextMenu(nodeControl); // 添加右键菜单 + + } + + void IFlowCanvas.CreateInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionInvokeType type) + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); + return; + } + JunctionControlBase startJunction = IFormJunction.NextStepJunction; + JunctionControlBase endJunction = IToJunction.ExecuteJunction; + + + var connection = new ConnectionControl( + FlowChartCanvas, + type, + startJunction, + endJunction + ); + + //if (toNodeControl is FlipflopNodeControl flipflopControl + // && flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器 + //{ + // NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除 + //} + + Connections.Add(connection); + fromNodeControl.AddCnnection(connection); + toNodeControl.AddCnnection(connection); + EndConnection(); // 环境触发了创建节点连接事件 + + + } + void IFlowCanvas.RemoveInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl) + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); + return; + } + JunctionControlBase startJunction = IFormJunction.NextStepJunction; + JunctionControlBase endJunction = IToJunction.ExecuteJunction; + + var removeConnections = Connections.Where(c => + c.Start.Equals(startJunction) + && c.End.Equals(endJunction) + && (c.Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke + || c.End.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)) + .ToList(); + + + foreach (var connection in removeConnections) + { + Connections.Remove(connection); + fromNodeControl.RemoveConnection(connection); // 移除连接 + toNodeControl.RemoveConnection(connection); // 移除连接 + + //if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + //{ + // JudgmentFlipFlopNode(control); // 连接关系变更时判断 + //} + } + } + void IFlowCanvas.CreateArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl,ConnectionArgSourceType type, int index) + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的情况"); + return; + } + + JunctionControlBase startJunction = type switch + { + ConnectionArgSourceType.GetPreviousNodeData => IFormJunction.ReturnDataJunction, // 自身节点 + ConnectionArgSourceType.GetOtherNodeData => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + ConnectionArgSourceType.GetOtherNodeDataOfInvoke => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点 + _ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发 + }; + + if (IToJunction.ArgDataJunction.Length <= index) + { + _ = Task.Run(async () => + { + await Task.Delay(100); + await App.UIContextOperation.InvokeAsync(() => { + Api.CreateArgConnection(fromNodeControl, toNodeControl, type, index); + }); + }); + return; // // 尝试重新连接 + } + JunctionControlBase endJunction = IToJunction.ArgDataJunction[index]; + LineType lineType = LineType.Bezier; + // 添加连接 + var connection = new ConnectionControl( + lineType, + FlowChartCanvas, + index, + type, + startJunction, + endJunction, + IToJunction + ); + Connections.Add(connection); + fromNodeControl.AddCnnection(connection); + toNodeControl.AddCnnection(connection); + EndConnection(); // 环境触发了创建节点连接事件 + } + void IFlowCanvas.RemoveArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, int index) + { + if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction) + { + SereinEnv.WriteLine(InfoType.INFO, "非预期的连接"); + return; + } + JunctionControlBase startJunction = IFormJunction.NextStepJunction; + JunctionControlBase endJunction = IToJunction.ExecuteJunction; + + var removeConnections = Connections.Where(c => + c.Start.Equals(startJunction) + && c.End.Equals(endJunction) + && (c.Start.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke + || c.End.JunctionType.ToConnectyionType() == JunctionOfConnectionType.Invoke)) + .ToList(); + + + foreach (var connection in removeConnections) + { + Connections.Remove(connection); + fromNodeControl.RemoveConnection(connection); // 移除连接 + toNodeControl.RemoveConnection(connection); // 移除连接 + //if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control)) + //{ + // JudgmentFlipFlopNode(control); // 连接关系变更时判断 + //} + } + } + + #endregion + #region 画布键盘操作 /// /// 监听按键事件 @@ -449,7 +472,7 @@ namespace Serein.Workbench.Views IsCanvasDragging = false; SelectionRectangle.Visibility = Visibility.Collapsed; CancelSelectNode(); - EndConnection(); + EndConnection(); // Esc 按键 退出连线状态 return; } @@ -464,7 +487,6 @@ namespace Serein.Workbench.Views } catch { - Clipboard.SetText(text); return; } } @@ -590,10 +612,7 @@ namespace Serein.Workbench.Views startCanvasDragPoint = currentMousePosition; - foreach (var line in Connections) - { - line.RefreshLine(); // 画布移动时刷新所有连接线 - } + RefreshAllLine(); } if (IsSelectControl) // 正在选取节点 @@ -807,7 +826,7 @@ namespace Serein.Workbench.Views } #endregion } - EndConnection(); + EndConnection(); // 完成创建连线请求后取消连线 } } @@ -1012,6 +1031,17 @@ namespace Serein.Workbench.Views #region 私有方法 + /// + /// 刷新画布所有连线 + /// + public void RefreshAllLine() + { + foreach (var line in Connections) + { + line.RefreshLine(); + } + } + /// /// 完成选取操作 /// @@ -1088,9 +1118,9 @@ namespace Serein.Workbench.Views nodeControl.BorderBrush = Brushes.Black; nodeControl.BorderThickness = new Thickness(0); - var startNodeGuid = ViewModel.Model.StartNode; - var nodeGuid = nodeControl.ViewModel.NodeModel.Guid; - if (startNodeGuid.Equals(nodeGuid)) + var startNode = ViewModel.Model.StartNode; + var canvasStartNode = nodeControl.ViewModel.NodeModel; + if (canvasStartNode.Equals(startNode)) { nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); nodeControl.BorderThickness = new Thickness(2); @@ -1300,6 +1330,10 @@ namespace Serein.Workbench.Views /// private void ConfigureContextMenu(NodeControlBase nodeControl) { + /*if(nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.UI) + { + return; + }*/ var canvasGuid = Guid; var contextMenu = new ContextMenu(); var nodeGuid = nodeControl.ViewModel?.NodeModel?.Guid; @@ -1412,21 +1446,7 @@ namespace Serein.Workbench.Views #region 节点对齐 (有些小瑕疵) - //public void UpdateConnectedLines() - //{ - // //foreach (var nodeControl in selectNodeControls) - // //{ - // // UpdateConnections(nodeControl); - // //} - // this.Dispatcher.Invoke(() => - // { - // foreach (var line in Connections) - // { - // line.AddOrRefreshLine(); // 节点完成对齐 - // } - // }); - //} #region Plan A 群组对齐