解决了接口节点的参数共享,UI控件的Menu菜单事件穿透问题,同时优化了工作台画布流程相关事件的部分代码

This commit is contained in:
fengjiayi
2025-05-30 01:02:25 +08:00
parent bbf7f045b0
commit a112d0287f
23 changed files with 635 additions and 457 deletions

View File

@@ -33,26 +33,29 @@ namespace Serein.Library
ScaleY = model.ScaleY, ScaleY = model.ScaleY,
ViewX = model.ViewX, ViewX = model.ViewX,
ViewY = model.ViewY, ViewY = model.ViewY,
StartNode = model.StartNode, StartNode = model.StartNode?.Guid,
}; };
} }
/// <summary> /// <summary>
/// 从画布信息加载 /// 从画布信息加载
/// </summary> /// </summary>
/// <param name="model"></param> /// <param name="canvasModel"></param>
/// <param name="info"></param> /// <param name="canvasInfo"></param>
public static void LoadInfo(this FlowCanvasDetails model, FlowCanvasDetailsInfo info) public static void LoadInfo(this FlowCanvasDetails canvasModel, FlowCanvasDetailsInfo canvasInfo)
{ {
model.Guid = info.Guid; canvasModel.Guid = canvasInfo.Guid;
model.Height = info.Height; canvasModel.Height = canvasInfo.Height;
model.Width = info.Width; canvasModel.Width = canvasInfo.Width;
model.Name = info.Name; canvasModel.Name = canvasInfo.Name;
model.ScaleX = info.ScaleX; canvasModel.ScaleX = canvasInfo.ScaleX;
model.ScaleY = info.ScaleY; canvasModel.ScaleY = canvasInfo.ScaleY;
model.ViewX = info.ViewX; canvasModel.ViewX = canvasInfo.ViewX;
model.ViewY = info.ViewY; canvasModel.ViewY = canvasInfo.ViewY;
model.StartNode = info.StartNode; if(canvasModel.Env.TryGetNodeModel(canvasInfo.StartNode,out var nodeModel))
{
canvasModel.StartNode = nodeModel;
}
} }
/// <summary> /// <summary>
@@ -61,7 +64,7 @@ namespace Serein.Library
/// <returns></returns> /// <returns></returns>
public static ParameterData[] SaveParameterInfo(this NodeModelBase nodeModel) 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]; return new ParameterData[0];
} }

View File

@@ -24,7 +24,6 @@ namespace Serein.Library
Env = env; Env = env;
} }
public IFlowEnvironment Env { get; } public IFlowEnvironment Env { get; }
/// <summary> /// <summary>
@@ -87,19 +86,17 @@ namespace Serein.Library
[PropertyInfo(IsNotification = true)] [PropertyInfo(IsNotification = true)]
private double _scaleY = 1; private double _scaleY = 1;
/// <summary> /// <summary>
/// 起始节点私有属性 /// 起始节点
/// </summary> /// </summary>
private string _startNode; [PropertyInfo]
private NodeModelBase _startNode;
} }
public partial class FlowCanvasDetails public partial class FlowCanvasDetails
{ {
} }

View File

@@ -248,7 +248,8 @@ namespace Serein.Library
} }
else else
{ {
inputParameter = context.GetFlowData(previousNode).Value; // 当前传递的数据 var flowData = context.GetFlowData(previousNode);
inputParameter = flowData.Value; // 当前传递的数据
} }
} }
else else
@@ -259,7 +260,16 @@ namespace Serein.Library
} }
if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData) 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) else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke)
{ {

View File

@@ -8,6 +8,7 @@ using Serein.NodeFlow.Tool;
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Reactive; using System.Reactive;
using System.Reflection; using System.Reflection;
@@ -228,7 +229,7 @@ namespace Serein.NodeFlow.Env
/// <summary> /// <summary>
/// UI线程操作类 /// UI线程操作类
/// </summary> /// </summary>
public UIContextOperation UIContextOperation { get; set; } public UIContextOperation UIContextOperation { get; private set; }
/// <summary> /// <summary>
/// 节点视图模型管理类 /// 节点视图模型管理类
@@ -389,13 +390,12 @@ namespace Serein.NodeFlow.Env
} }
var ft = new FlowTask(); var ft = new FlowTask();
ft.GetNodes = () => NodeModels.Values.Where(node => node.CanvasDetails.Guid.Equals(guid)).ToList(); ft.GetNodes = () => NodeModels.Values.Where(node => node.CanvasDetails.Guid.Equals(guid)).ToList();
var startNodeModel = NodeModels.GetValueOrDefault(canvasModel.StartNode); if (canvasModel.StartNode.Guid is null)
if(startNodeModel is null)
{ {
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}"); SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}");
return false; return false;
} }
ft.GetStartNode = () => startNodeModel; ft.GetStartNode = () => canvasModel.StartNode;
flowTasks.Add(guid, ft); flowTasks.Add(guid, ft);
} }
#endregion #endregion
@@ -841,7 +841,7 @@ namespace Serein.NodeFlow.Env
var model = new FlowCanvasDetails(this); var model = new FlowCanvasDetails(this);
model.LoadInfo(info); model.LoadInfo(info);
FlowCanvass.Add(model.Guid, model); FlowCanvass.Add(model.Guid, model);
UIContextOperation.Invoke(() => UIContextOperation?.Invoke(() =>
{ {
OnCanvasCreate.Invoke(new CanvasCreateEventArgs(model)); OnCanvasCreate.Invoke(new CanvasCreateEventArgs(model));
}); });
@@ -868,7 +868,7 @@ namespace Serein.NodeFlow.Env
} }
if (FlowCanvass.Remove(canvasGuid)) if (FlowCanvass.Remove(canvasGuid))
{ {
UIContextOperation.Invoke(() => UIContextOperation?.Invoke(() =>
{ {
OnCanvasRemove.Invoke(new CanvasRemoveEventArgs(canvasGuid)); OnCanvasRemove.Invoke(new CanvasRemoveEventArgs(canvasGuid));
}); });
@@ -878,6 +878,60 @@ namespace Serein.NodeFlow.Env
return false; return false;
} }
/// <summary>
/// 从节点信息创建节点,并返回状态指示是否创建成功
/// </summary>
/// <param name="nodeInfo"></param>
/// <returns></returns>
private bool CreateNodeFromNodeInfo(NodeInfo nodeInfo)
{
if (!EnumHelper.TryConvertEnum<NodeControlType>(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;
}
/// <summary> /// <summary>
@@ -885,57 +939,37 @@ namespace Serein.NodeFlow.Env
/// </summary> /// </summary>
/// <param name="List<NodeInfo>">节点信息</param> /// <param name="List<NodeInfo>">节点信息</param>
/// <returns></returns> /// <returns></returns>
///
public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos) public async Task LoadNodeInfosAsync(List<NodeInfo> nodeInfos)
{ {
#region NodeInfo创建NodeModel #region NodeInfo创建NodeModel
// 流程接口节点最后才创建
List<NodeInfo> flowCallNodeInfos = [];
foreach (NodeInfo? nodeInfo in nodeInfos) foreach (NodeInfo? nodeInfo in nodeInfos)
{ {
if(nodeInfo.Type == nameof(NodeControlType.FlowCall))
if (!EnumHelper.TryConvertEnum<NodeControlType>(nodeInfo.Type, out var controlType))
{ {
continue; flowCallNodeInfos.Add(nodeInfo);
}
#region
MethodDetails? methodDetails;
if (controlType.IsBaseNode())
{
// 加载基础节点
methodDetails = new MethodDetails();
} }
else else
{ {
if (string.IsNullOrEmpty(nodeInfo.MethodName)) continue; if (!CreateNodeFromNodeInfo(nodeInfo))
// 加载方法节点 {
FlowLibraryManagement.TryGetMethodDetails(nodeInfo.AssemblyName, nodeInfo.MethodName, out methodDetails); // 加载项目时尝试获取方法信息 SereinEnv.WriteLine(InfoType.WARN, $"节点创建失败。{Environment.NewLine}{nodeInfo}");
continue;
}
} }
#endregion }
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; 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 #endregion
@@ -960,7 +994,7 @@ namespace Serein.NodeFlow.Env
var result = nodeContainer.PlaceNode(nodeModel); var result = nodeContainer.PlaceNode(nodeModel);
if (result) if (result)
{ {
UIContextOperation.Invoke(() => OnNodePlace?.Invoke( UIContextOperation?.Invoke(() => OnNodePlace?.Invoke(
new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid))); new NodePlaceEventArgs(nodeInfo.CanvasGuid, nodeModel.Guid, containerNode.Guid)));
} }
@@ -1044,7 +1078,7 @@ namespace Serein.NodeFlow.Env
#endregion #endregion
UIContextOperation.Invoke(() => UIContextOperation?.Invoke(() =>
{ {
OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs());
}); });
@@ -1136,7 +1170,7 @@ namespace Serein.NodeFlow.Env
var result = nodeContainer.PlaceNode(nodeModel); // 放置在容器节点 var result = nodeContainer.PlaceNode(nodeModel); // 放置在容器节点
if (result) if (result)
{ {
UIContextOperation.Invoke(() => UIContextOperation?.Invoke(() =>
{ {
OnNodePlace?.Invoke(new NodePlaceEventArgs(canvasGuid, nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置 OnNodePlace?.Invoke(new NodePlaceEventArgs(canvasGuid, nodeGuid, containerNodeGuid)); // 通知UI更改节点放置位置
}); });
@@ -1168,7 +1202,7 @@ namespace Serein.NodeFlow.Env
var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出 var result = nodeContainer.TakeOutNode(nodeModel); // 从容器节点取出
if (result) if (result)
{ {
UIContextOperation.Invoke(() => UIContextOperation?.Invoke(() =>
{ {
OnNodeTakeOut?.Invoke(new NodeTakeOutEventArgs(canvasGuid, nodeGuid)); // 重新放置在画布上 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)) 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); SetStartNode(canvasModel, newStartNodeModel);
return Task.FromResult(canvasModel.StartNode ?? string.Empty); return Task.FromResult(canvasModel.StartNode.Guid ?? string.Empty);
} }
/// <summary> /// <summary>
@@ -1743,7 +1786,7 @@ namespace Serein.NodeFlow.Env
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
UIContextOperation.Invoke(() => OnNodeConnectChange?.Invoke( UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(
new NodeConnectChangeEventArgs( new NodeConnectChangeEventArgs(
canvasGuid, canvasGuid,
fromNode.Guid, fromNode.Guid,
@@ -1776,7 +1819,7 @@ namespace Serein.NodeFlow.Env
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
{ {
UIContextOperation.Invoke(() => OnNodeConnectChange?.Invoke( UIContextOperation?.Invoke(() => OnNodeConnectChange?.Invoke(
new NodeConnectChangeEventArgs( new NodeConnectChangeEventArgs(
canvasGuid, canvasGuid,
fromNode.Guid, fromNode.Guid,
@@ -1887,6 +1930,12 @@ namespace Serein.NodeFlow.Env
/// <param name="invokeType">连接关系</param> /// <param name="invokeType">连接关系</param>
private bool ConnectInvokeOfNode(string canvasGuid, NodeModelBase fromNode, NodeModelBase toNode, ConnectionInvokeType invokeType) 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)) if (!FlowCanvass.ContainsKey(canvasGuid))
{ {
return false; return false;
@@ -1907,7 +1956,8 @@ namespace Serein.NodeFlow.Env
{ {
flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除
} }
var isOverwriting = false;
ConnectionInvokeType overwritingCt = ConnectionInvokeType.None;
var isPass = false; var isPass = false;
foreach (ConnectionInvokeType ctType in ct) foreach (ConnectionInvokeType ctType in ct)
{ {
@@ -1917,21 +1967,33 @@ namespace Serein.NodeFlow.Env
FromExistInTo = ToOnF.Length > 0; FromExistInTo = ToOnF.Length > 0;
if (ToExistOnFrom && FromExistInTo) if (ToExistOnFrom && FromExistInTo)
{ {
SereinEnv.WriteLine(InfoType.WARN, "起始节点已与目标节点存在连接"); if(ctType == invokeType)
isPass = false; {
SereinEnv.WriteLine(InfoType.WARN, $"起始节点已与目标节点存在连接。" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}");
return false;
}
isOverwriting = true;
overwritingCt = ctType;
} }
else else
{ {
// 检查是否可能存在异常 // 检查是否可能存在异常
if (!ToExistOnFrom && FromExistInTo) if (!ToExistOnFrom && FromExistInTo)
{ {
SereinEnv.WriteLine(InfoType.WARN, "目标节点不是起始节点的节点,起始节点却是目标节点的节点"); SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的节点,目标节点却是起始节点的节点" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}");
isPass = false; isPass = false;
} }
else if (ToExistOnFrom && !FromExistInTo) else if (ToExistOnFrom && !FromExistInTo)
{ {
// //
SereinEnv.WriteLine(InfoType.WARN, " 起始节点不是目标节点的父节点,目标节点却是起始节点的子节点"); SereinEnv.WriteLine(InfoType.ERROR, $"起始节点不是目标节点的父节点,目标节点却是起始节点的子节点" +
$"{Environment.NewLine}起始节点:{fromNode.Guid}" +
$"{Environment.NewLine}目标节点:{toNode.Guid}" +
$"");
isPass = false; isPass = false;
} }
else else
@@ -1942,7 +2004,11 @@ namespace Serein.NodeFlow.Env
} }
if (isPass) if (isPass)
{ {
if (isOverwriting) // 需要替换
{
fromNode.SuccessorNodes[overwritingCt].Remove(toNode); // 从起始节点子分支中移除
toNode.PreviousNodes[overwritingCt].Remove(fromNode); // 从目标节点父分支中移除
}
fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点的子分支 fromNode.SuccessorNodes[invokeType].Add(toNode); // 添加到起始节点的子分支
toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点的父分支 toNode.PreviousNodes[invokeType].Add(fromNode); // 添加到目标节点的父分支
if (OperatingSystem.IsWindows()) if (OperatingSystem.IsWindows())
@@ -1991,13 +2057,38 @@ namespace Serein.NodeFlow.Env
} }
var toNodeArgSourceGuid = toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid; 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); await RemoteConnectAsync(canvasGuid, fromNode, toNode, argIndex);
} }
toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = fromNode.Guid; toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceNodeGuid = fromNode.Guid;
toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = connectionArgSourceType; toNode.MethodDetails.ParameterDetailss[argIndex].ArgDataSourceType = connectionArgSourceType;
UIContextOperation.Invoke(() =>
UIContextOperation?.Invoke(() =>
OnNodeConnectChange?.Invoke( OnNodeConnectChange?.Invoke(
new NodeConnectChangeEventArgs( new NodeConnectChangeEventArgs(
canvasGuid, canvasGuid,
@@ -2019,15 +2110,14 @@ namespace Serein.NodeFlow.Env
/// <param name="newStartNode">起始节点</param> /// <param name="newStartNode">起始节点</param>
private void SetStartNode(FlowCanvasDetails cavnasModel, NodeModelBase newStartNode) private void SetStartNode(FlowCanvasDetails cavnasModel, NodeModelBase newStartNode)
{ {
var oldNodeGuid = cavnasModel.StartNode; var oldNodeGuid = cavnasModel.StartNode?.Guid;
/*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel)) /*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel))
{ {
newStartNode.IsStart = false; newStartNode.IsStart = false;
}*/ }*/
cavnasModel.StartNode = newStartNode.Guid; cavnasModel.StartNode = newStartNode;
//newStartNode.IsStart = true; //newStartNode.IsStart = true;
UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode.Guid)));
UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode)));
} }

View File

@@ -16,14 +16,17 @@ namespace Serein.NodeFlow.Model
[NodeProperty(ValuePath = NodeValuePath.Node)] [NodeProperty(ValuePath = NodeValuePath.Node)]
public partial class SingleFlowCallNode public partial class SingleFlowCallNode
{ {
/// <summary>
/// 目标公开节点
/// </summary>
[PropertyInfo(IsNotification = true)]
private string targetNodeGuid;
/// <summary> /// <summary>
/// 使用目标节点的参数如果为true则使用目标节点的入参如果为false则使用节点自定义入参 /// 使用目标节点的参数如果为true则使用目标节点的入参如果为false则使用节点自定义入参
/// </summary> /// </summary>
[PropertyInfo(IsNotification = true)] [PropertyInfo(IsNotification = true)]
private bool _isShareParam = true ; private bool _isShareParam ;
} }
@@ -38,9 +41,13 @@ namespace Serein.NodeFlow.Model
/// </summary> /// </summary>
private NodeModelBase targetNode; private NodeModelBase targetNode;
/// <summary> /// <summary>
/// 缓存的方法信息
/// </summary>
public MethodDetails CacheMethodDetails { get; private set; }
/// <summary>
/// 接口节点Guid /// 接口节点Guid
/// </summary> /// </summary>
public string? TargetNodeGuid => targetNode?.Guid; //public string? TargetNodeGuid => targetNode?.Guid;
public SingleFlowCallNode(IFlowEnvironment environment) : base(environment) public SingleFlowCallNode(IFlowEnvironment environment) : base(environment)
@@ -48,43 +55,70 @@ namespace Serein.NodeFlow.Model
} }
/// <summary> /// <summary>
/// 重置接口节点 /// 重置接口节点
/// </summary> /// </summary>
public void ResetTargetNode() public void ResetTargetNode()
{ {
if(targetNode is not null) if (targetNode is not null)
{ {
// 取消接口 // 取消接口
targetNode.PropertyChanged -= TargetNode_PropertyChanged; TargetNodeGuid = string.Empty;
this.MethodDetails = null;
foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
this.SuccessorNodes[ctType] = new List<NodeModelBase>();
}
} }
} }
/// <summary> /// <summary>
/// 设置接口节点如果传入null则视为取消设置 /// 设置接口节点
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="nodeGuid"></param>
public void SetTargetNode(NodeModelBase? value) public void SetTargetNode(string? nodeGuid)
{ {
if( value is null) if (nodeGuid is null || !Env.TryGetNodeModel(nodeGuid, out _))
{ {
return; return;
} }
this.targetNode = value; TargetNodeGuid = nodeGuid;
tmpMethodDetails = targetNode.MethodDetails.CloneOfNode(this); // 从目标节点复制一份 }
targetNode.PropertyChanged += TargetNode_PropertyChanged;
this.MethodDetails = tmpMethodDetails; partial void OnTargetNodeGuidChanged(string value)
this.SuccessorNodes = targetNode.SuccessorNodes; {
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<NodeModelBase>();
}*/
}
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) partial void OnIsShareParamChanged(bool value)
{ {
if (targetNode is null) if (targetNode is null)
@@ -93,40 +127,26 @@ namespace Serein.NodeFlow.Model
} }
if (value) if (value)
{ {
tmpMethodDetails = this.MethodDetails; CacheMethodDetails = this.MethodDetails;
this.MethodDetails = targetNode.MethodDetails; this.MethodDetails = targetNode.MethodDetails;
} }
else else
{ {
this.MethodDetails = tmpMethodDetails; this.MethodDetails = CacheMethodDetails;
OnPropertyChanged(nameof(MethodDetails));
} }
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) private void TargetNode_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{ {
// 如果不再公开 // 如果不再公开
if (sender is NodeModelBase node && !node.IsPublic) if (sender is NodeModelBase node && !node.IsPublic)
{ {
this.SuccessorNodes = []; foreach (ConnectionInvokeType ctType in NodeStaticConfig.ConnectionTypes)
{
this.SuccessorNodes[ctType] = [];
}
targetNode.PropertyChanged -= TargetNode_PropertyChanged; targetNode.PropertyChanged -= TargetNode_PropertyChanged;
} }
} }
@@ -168,6 +188,11 @@ namespace Serein.NodeFlow.Model
{ {
throw new ArgumentNullException(); throw new ArgumentNullException();
} }
if (IsShareParam)
{
this.MethodDetails = targetNode.MethodDetails;
}
this.SuccessorNodes = targetNode.SuccessorNodes;
return await base.ExecutingAsync(context, token); return await base.ExecutingAsync(context, token);
} }
@@ -192,19 +217,30 @@ namespace Serein.NodeFlow.Model
/// <param name="nodeInfo"></param> /// <param name="nodeInfo"></param>
public override void LoadCustomData(NodeInfo nodeInfo) public override void LoadCustomData(NodeInfo nodeInfo)
{ {
CacheMethodDetails = this.MethodDetails; // 缓存
string targetNodeGuid = nodeInfo.CustomData?.TargetNodeGuid ?? ""; string targetNodeGuid = nodeInfo.CustomData?.TargetNodeGuid ?? "";
this.IsShareParam = nodeInfo.CustomData?.IsShareParam; this.IsShareParam = nodeInfo.CustomData?.IsShareParam;
if (Env.TryGetNodeModel(targetNodeGuid, out var targetNode)) if (Env.TryGetNodeModel(targetNodeGuid, out var targetNode))
{ {
TargetNodeGuid = targetNode.Guid;
this.targetNode = targetNode; this.targetNode = targetNode;
} }
else else
{ {
SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点[{this.Guid}]无法找到对应的节点:{targetNodeGuid}"); SereinEnv.WriteLine(InfoType.ERROR, $"流程接口节点[{this.Guid}]无法找到对应的节点:{targetNodeGuid}");
} }
} }
public override void Remove()
{
var tmp = this;
targetNode = null;
CacheMethodDetails = null;
}
} }
} }

View File

@@ -61,6 +61,7 @@ namespace Serein.Workbench
{ {
getSyncContext = () => uiContext; getSyncContext = () => uiContext;
} }
}); });
UIContextOperation? uIContextOperation = null; UIContextOperation? uIContextOperation = null;
@@ -84,6 +85,12 @@ namespace Serein.Workbench
public partial class App : Application public partial class App : Application
{ {
private static IServiceProvider? ServiceProvider; private static IServiceProvider? ServiceProvider;
/// <summary>
/// UI线程
/// </summary>
public static UIContextOperation UIContextOperation => App.GetService<UIContextOperation>() ?? throw new NullReferenceException();
public static T GetService<T>() where T : class public static T GetService<T>() where T : class
{ {
return ServiceProvider?.GetService<T>() ?? throw new NullReferenceException(); return ServiceProvider?.GetService<T>() ?? throw new NullReferenceException();
@@ -111,7 +118,7 @@ namespace Serein.Workbench
{ {
await Task.Delay(500); await Task.Delay(500);
#if DEBUG #if DEBUG
if (1 ==1) if (1 == 1)
{ {
// 这里是测试代码,可以删除 // 这里是测试代码,可以删除
string filePath; string filePath;

View File

@@ -60,7 +60,15 @@ namespace Serein.Workbench.Node.View
endPoint = end; endPoint = end;
this.strokeThickness = 4; this.strokeThickness = 4;
InitElementPoint(isDotted, isTop); InitElementPoint(isDotted, isTop);
InvalidateVisual(); // 触发重绘
_ = Task.Run(async () =>
{
await App.UIContextOperation.InvokeAsync(() =>
{
InvalidateVisual(); // 触发重绘
});
});
} }

View File

@@ -1,7 +1,9 @@
using Serein.Library; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Serein.Library;
using Serein.Library.Api; using Serein.Library.Api;
using Serein.Workbench.Api; using Serein.Workbench.Api;
using Serein.Workbench.Node.ViewModel; using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.Themes;
using Serein.Workbench.Views; using Serein.Workbench.Views;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -146,7 +148,7 @@ namespace Serein.Workbench.Node.View
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
/// <param name="parent"></param> /// <param name="parent"></param>
/// <returns></returns> /// <returns></returns>
protected T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject protected static T FindVisualChild<T>(DependencyObject parent) where T : DependencyObject
{ {
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++) for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{ {
@@ -165,6 +167,51 @@ namespace Serein.Workbench.Node.View
return null; return null;
} }
protected static JunctionControlBase[] GetArgJunction(NodeControlBase nodeControl, MethodDetailsControl methodDetailsControl)
{
// 获取 MethodDetailsControl 实例
try
{
var itemsControl = FindVisualChild<ItemsControl>(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<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(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 [];
}
}
} }

View File

@@ -1,5 +1,7 @@
using Serein.NodeFlow.Model; using Serein.Library;
using Serein.NodeFlow.Model;
using Serein.Workbench.Node.ViewModel; using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.Themes;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@@ -42,35 +44,7 @@ namespace Serein.Workbench.Node.View
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this, MethodDetailsControl);
private JunctionControlBase[] GetArgJunction()
{
// 获取 MethodDetailsControl 实例
var methodDetailsControl = this.MethodDetailsControl;
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{
var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var controls = new List<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(container);
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
}
}
return argDataJunction = controls.ToArray();
}
return [];
}
} }
} }

View File

@@ -43,6 +43,7 @@ namespace Serein.Workbench.Node.View
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
private JunctionControlBase[] argDataJunction; private JunctionControlBase[] argDataJunction;
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>

View File

@@ -34,34 +34,7 @@ namespace Serein.Workbench.Node.View
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this, MethodDetailsControl);
private JunctionControlBase[] GetArgJunction()
{
// 获取 MethodDetailsControl 实例
var methodDetailsControl = this.MethodDetailsControl;
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{
var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var controls = new List<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(container);
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
}
}
return argDataJunction = controls.ToArray();
}
return [];
}
} }
} }

View File

@@ -55,35 +55,8 @@ namespace Serein.Workbench.Node.View
/// <summary> /// <summary>
/// 方法入参控制点(可能有,可能没) /// 方法入参控制点(可能有,可能没)
/// </summary> /// </summary>
JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(); JunctionControlBase[] INodeJunction.ArgDataJunction => GetArgJunction(this,MethodDetailsControl);
private JunctionControlBase[] GetArgJunction()
{
// 获取 MethodDetailsControl 实例
//var methodDetailsControl = ViewModel.NodeModel.IsShareParam ? this.SelectMethodDetailsControl : this.MyMethodDetailsControl;
var methodDetailsControl = this.MethodDetailsControl;
var itemsControl = FindVisualChild<ItemsControl>(methodDetailsControl); // 查找 ItemsControl
if (itemsControl != null)
{
var argDataJunction = new JunctionControlBase[base.ViewModel.NodeModel.MethodDetails.ParameterDetailss.Length];
var controls = new List<JunctionControlBase>();
for (int i = 0; i < itemsControl.Items.Count; i++)
{
var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement;
if (container != null)
{
var argControl = FindVisualChild<ArgJunctionControl>(container);
if (argControl != null)
{
controls.Add(argControl); // 收集 ArgJunctionControl 实例
}
}
}
return argDataJunction = controls.ToArray();
}
return [];
}
} }

View File

@@ -32,8 +32,9 @@
</Grid> </Grid>
<Border Grid.Row="1" x:Name="EmbedContainer" BorderBrush="Black" BorderThickness="1" <Border x:Name="EmbedContainer" Width="200" Height="200" Grid.Row="1" BorderBrush="Black" BorderThickness="1">
Width="500" Height="400"/> <!--<ContentControl />-->
</Border>
</Grid> </Grid>

View File

@@ -22,6 +22,7 @@ namespace Serein.Workbench.Node.View
/// </summary> /// </summary>
public partial class UINodeControl : NodeControlBase, INodeJunction public partial class UINodeControl : NodeControlBase, INodeJunction
{ {
private new UINodeControlViewModel ViewModel { get; }
public UINodeControl() public UINodeControl()
{ {
base.ViewModel.IsEnabledOnView = true; base.ViewModel.IsEnabledOnView = true;
@@ -30,6 +31,7 @@ namespace Serein.Workbench.Node.View
public UINodeControl(UINodeControlViewModel viewModel) : base(viewModel) public UINodeControl(UINodeControlViewModel viewModel) : base(viewModel)
{ {
ViewModel = viewModel;
DataContext = viewModel; DataContext = viewModel;
InitializeComponent(); InitializeComponent();
@@ -49,8 +51,8 @@ namespace Serein.Workbench.Node.View
private void NodeControlBase_Loaded(object sender, RoutedEventArgs e) private void NodeControlBase_Loaded(object sender, RoutedEventArgs e)
{ {
UINodeControlViewModel vm = (UINodeControlViewModel)DataContext; //ViewModel.InitAdapter();
vm.InitAdapter(userControl => { ViewModel.InitAdapter(userControl => {
EmbedContainer.Child = userControl; EmbedContainer.Child = userControl;
}); });

View File

@@ -68,10 +68,9 @@ namespace Serein.Workbench.Node.ViewModel
{ {
return; return;
} }
if (targetNodeControl.FlowCanvas is FlowCanvasView view if (targetNodeControl.FlowCanvas is FlowCanvasView view )
&& view.DataContext is FlowCanvasViewModel viewModel)
{ {
SelectCanvas = viewModel; SelectCanvas = view.ViewModel;
SelectNode = targetNodeControl.ViewModel.NodeModel; SelectNode = targetNodeControl.ViewModel.NodeModel;
} }
} }
@@ -80,8 +79,10 @@ namespace Serein.Workbench.Node.ViewModel
{ {
flowEEForwardingService.OnCanvasCreate += (e) => RershCanvass(); // 画布创建了 flowEEForwardingService.OnCanvasCreate += (e) => RershCanvass(); // 画布创建了
flowEEForwardingService.OnCanvasRemove += (e) => RershCanvass(); // 画布移除了 flowEEForwardingService.OnCanvasRemove += (e) => RershCanvass(); // 画布移除了
} }
partial void OnSelectCanvasChanged(FlowCanvasViewModel value) partial void OnSelectCanvasChanged(FlowCanvasViewModel value)
{ {
FlowCallNode.ResetTargetNode(); FlowCallNode.ResetTargetNode();
@@ -89,7 +90,12 @@ namespace Serein.Workbench.Node.ViewModel
partial void OnSelectNodeChanged(NodeModelBase value) partial void OnSelectNodeChanged(NodeModelBase value)
{ {
FlowCallNode.SetTargetNode(value); if(value is null)
{
FlowCallNode.ResetTargetNode();
return;
}
FlowCallNode.SetTargetNode(value.Guid);
} }
private void RershCanvass() private void RershCanvass()

View File

@@ -1,4 +1,5 @@
using Serein.Library; using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
using Serein.Library.Api; using Serein.Library.Api;
using Serein.NodeFlow.Model; using Serein.NodeFlow.Model;
using System; using System;
@@ -10,16 +11,43 @@ using System.Windows.Controls;
namespace Serein.Workbench.Node.ViewModel namespace Serein.Workbench.Node.ViewModel
{ {
public class UINodeControlViewModel : NodeControlViewModelBase public partial class UINodeControlViewModel : NodeControlViewModelBase
{ {
private SingleUINode NodeModel => (SingleUINode)base.NodeModel; private SingleUINode NodeModel => (SingleUINode)base.NodeModel;
//public IEmbeddedContent Adapter => NodeModel.Adapter; //public IEmbeddedContent Adapter => NodeModel.Adapter;
/// <summary>
/// 节点UI的对应内容
/// </summary>
[ObservableProperty]
private UserControl _nodeUIContent;
public UINodeControlViewModel(NodeModelBase nodeModel) : base(nodeModel) public UINodeControlViewModel(NodeModelBase nodeModel) : base(nodeModel)
{ {
//NodeModel.Adapter.GetWindowHandle();
} }
public void InitAdapter()
{
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(() =>
{
NodeUIContent = userControl;
});
}
});
}
public void InitAdapter(Action<UserControl> setUIDisplayHandle) public void InitAdapter(Action<UserControl> setUIDisplayHandle)
{ {
Task.Factory.StartNew(async () => Task.Factory.StartNew(async () =>
@@ -38,5 +66,6 @@ namespace Serein.Workbench.Node.ViewModel
} }
}); });
} }
} }
} }

View File

@@ -13,6 +13,7 @@ using Serein.Library;
using Serein.Library.Utils; using Serein.Library.Utils;
using Serein.Workbench.Avalonia.Api; using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Api; using Serein.Workbench.Api;
using System.Diagnostics;
namespace Serein.Workbench.Services namespace Serein.Workbench.Services
{ {

View File

@@ -8,7 +8,9 @@ using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel; using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.ViewModels; using Serein.Workbench.ViewModels;
using Serein.Workbench.Views; using Serein.Workbench.Views;
using System;
using System.Text; using System.Text;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media; using System.Windows.Media;
@@ -210,7 +212,9 @@ namespace Serein.Workbench.Services
(JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => () => flow.RemoveArgConnection(fromNode, toNode, e.ArgIndex), // 移除节点之间的参数传递关系 (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => () => flow.RemoveArgConnection(fromNode, toNode, e.ArgIndex), // 移除节点之间的参数传递关系
_ => null _ => null
}; };
action?.Invoke(); action?.Invoke();
return; return;
} }

View File

@@ -32,10 +32,7 @@ namespace Serein.Workbench.ViewModels
/// <param name="flowCanvas"></param> /// <param name="flowCanvas"></param>
private void OnViewCanvasChanged(FlowCanvasView flowCanvas) private void OnViewCanvasChanged(FlowCanvasView flowCanvas)
{ {
if (flowCanvas.DataContext is FlowCanvasViewModel vm) Model = flowCanvas.ViewModel.Model;
{
Model = vm.Model;
}
} }
} }
} }

View File

@@ -17,7 +17,6 @@ namespace Serein.Workbench.ViewModels
{ {
public partial class FlowCanvasViewModel : ObservableObject public partial class FlowCanvasViewModel : ObservableObject
{ {
/// <summary> /// <summary>
/// 画布当前的节点 /// 画布当前的节点
/// </summary> /// </summary>

View File

@@ -43,16 +43,16 @@ namespace Serein.Workbench.ViewModels
flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 创建了画布 flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 创建了画布
flowNodeService.OnRemoveFlowCanvasView += OnRemoveFlowCanvasView; // 移除了画布 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 = value.Content;
flowNodeService.CurrentSelectCanvas = this.SelectedTab.Content;
} }
#region #region
private void OnCreateFlowCanvasView(FlowCanvasView canvas) private void OnCreateFlowCanvasView(FlowCanvasView canvas)
{ {

View File

@@ -52,187 +52,17 @@ namespace Serein.Workbench.Views
private readonly IFlowEnvironment flowEnvironment; private readonly IFlowEnvironment flowEnvironment;
private readonly IKeyEventService keyEventService; private readonly IKeyEventService keyEventService;
private readonly FlowNodeService flowNodeService; private readonly FlowNodeService flowNodeService;
private readonly IFlowEEForwardingService flowEEForwardingService;
/// <summary> /// <summary>
/// 存储所有的连接。考虑集成在运行环境中。 /// 存储所有的连接。考虑集成在运行环境中。
/// </summary> /// </summary>
private List<ConnectionControl> Connections { get; } = []; private List<ConnectionControl> Connections { get; } = [];
/// <summary>
/// 画布模型
/// </summary>
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<Locator>().FlowEditViewModel;
var tab = edit.CanvasTabs.First(tab => tab.Content == this);
App.GetService<Locator>().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 #region
@@ -301,8 +131,10 @@ namespace Serein.Workbench.Views
flowEnvironment = App.GetService<IFlowEnvironment>(); flowEnvironment = App.GetService<IFlowEnvironment>();
flowNodeService = App.GetService<FlowNodeService>(); flowNodeService = App.GetService<FlowNodeService>();
keyEventService = App.GetService<IKeyEventService>(); keyEventService = App.GetService<IKeyEventService>();
flowEEForwardingService = App.GetService<IFlowEEForwardingService>();
flowNodeService.OnCreateNode += OnCreateNode; flowNodeService.OnCreateNode += OnCreateNode;
keyEventService.OnKeyDown += KeyEventService_OnKeyDown; keyEventService.OnKeyDown += KeyEventService_OnKeyDown;
//flowEEForwardingService.OnProjectLoaded += FlowEEForwardingService_OnProjectLoaded;
// 缩放平移容器 // 缩放平移容器
canvasTransformGroup = new TransformGroup(); canvasTransformGroup = new TransformGroup();
@@ -313,6 +145,13 @@ namespace Serein.Workbench.Views
FlowChartCanvas.RenderTransform = canvasTransformGroup; FlowChartCanvas.RenderTransform = canvasTransformGroup;
SetBinding(model); SetBinding(model);
}
private void FlowEEForwardingService_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
{
RefreshAllLine();
} }
@@ -336,16 +175,18 @@ namespace Serein.Workbench.Views
} }
/// <summary> /// <summary>
/// 当前画布创建了节点 /// 当前画布创建了节点
/// </summary> /// </summary>
/// <param name="nodeControl"></param> /// <param name="nodeControl"></param>
private void OnCreateNode(NodeControlBase nodeControl) private void OnCreateNode(NodeControlBase nodeControl)
{ {
if (!nodeControl.FlowCanvas.Guid.Equals(Guid)) if (!nodeControl.FlowCanvas.Guid.Equals(Guid))
{ {
return; // 防止事件传播到其它画布
} return;
}
var p = nodeControl.ViewModel.NodeModel.Position; var p = nodeControl.ViewModel.NodeModel.Position;
PositionOfUI position = new PositionOfUI(p.X, p.Y); PositionOfUI position = new PositionOfUI(p.X, p.Y);
if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器 if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器
@@ -409,6 +250,188 @@ namespace Serein.Workbench.Views
#endregion #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<Locator>().FlowEditViewModel;
var tab = edit.CanvasTabs.First(tab => tab.Content == this);
App.GetService<Locator>().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 #region
/// <summary> /// <summary>
/// 监听按键事件 /// 监听按键事件
@@ -449,7 +472,7 @@ namespace Serein.Workbench.Views
IsCanvasDragging = false; IsCanvasDragging = false;
SelectionRectangle.Visibility = Visibility.Collapsed; SelectionRectangle.Visibility = Visibility.Collapsed;
CancelSelectNode(); CancelSelectNode();
EndConnection(); EndConnection(); // Esc 按键 退出连线状态
return; return;
} }
@@ -464,7 +487,6 @@ namespace Serein.Workbench.Views
} }
catch catch
{ {
Clipboard.SetText(text);
return; return;
} }
} }
@@ -590,10 +612,7 @@ namespace Serein.Workbench.Views
startCanvasDragPoint = currentMousePosition; startCanvasDragPoint = currentMousePosition;
foreach (var line in Connections) RefreshAllLine();
{
line.RefreshLine(); // 画布移动时刷新所有连接线
}
} }
if (IsSelectControl) // 正在选取节点 if (IsSelectControl) // 正在选取节点
@@ -807,7 +826,7 @@ namespace Serein.Workbench.Views
} }
#endregion #endregion
} }
EndConnection(); EndConnection(); // 完成创建连线请求后取消连线
} }
} }
@@ -1012,6 +1031,17 @@ namespace Serein.Workbench.Views
#region #region
/// <summary>
/// 刷新画布所有连线
/// </summary>
public void RefreshAllLine()
{
foreach (var line in Connections)
{
line.RefreshLine();
}
}
/// <summary> /// <summary>
/// 完成选取操作 /// 完成选取操作
/// </summary> /// </summary>
@@ -1088,9 +1118,9 @@ namespace Serein.Workbench.Views
nodeControl.BorderBrush = Brushes.Black; nodeControl.BorderBrush = Brushes.Black;
nodeControl.BorderThickness = new Thickness(0); nodeControl.BorderThickness = new Thickness(0);
var startNodeGuid = ViewModel.Model.StartNode; var startNode = ViewModel.Model.StartNode;
var nodeGuid = nodeControl.ViewModel.NodeModel.Guid; var canvasStartNode = nodeControl.ViewModel.NodeModel;
if (startNodeGuid.Equals(nodeGuid)) if (canvasStartNode.Equals(startNode))
{ {
nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")); nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
nodeControl.BorderThickness = new Thickness(2); nodeControl.BorderThickness = new Thickness(2);
@@ -1300,6 +1330,10 @@ namespace Serein.Workbench.Views
/// </param> /// </param>
private void ConfigureContextMenu(NodeControlBase nodeControl) private void ConfigureContextMenu(NodeControlBase nodeControl)
{ {
/*if(nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.UI)
{
return;
}*/
var canvasGuid = Guid; var canvasGuid = Guid;
var contextMenu = new ContextMenu(); var contextMenu = new ContextMenu();
var nodeGuid = nodeControl.ViewModel?.NodeModel?.Guid; var nodeGuid = nodeControl.ViewModel?.NodeModel?.Guid;
@@ -1412,21 +1446,7 @@ namespace Serein.Workbench.Views
#region #region
//public void UpdateConnectedLines()
//{
// //foreach (var nodeControl in selectNodeControls)
// //{
// // UpdateConnections(nodeControl);
// //}
// this.Dispatcher.Invoke(() =>
// {
// foreach (var line in Connections)
// {
// line.AddOrRefreshLine(); // 节点完成对齐
// }
// });
//}
#region Plan A #region Plan A