diff --git a/.claudiaideconfig b/.claudiaideconfig new file mode 100644 index 0000000..4c84480 --- /dev/null +++ b/.claudiaideconfig @@ -0,0 +1,49 @@ +{ + "BackgroundImageAbsolutePath": "C:\\Users\\Az\\Pictures\\anime_girl_brunette1.png", + "BackgroundImagesDirectoryAbsolutePath": "c:\\users\\az\\appdata\\local\\microsoft\\visualstudio\\17.0_a4315dfe\\extensions\\mtri5ge5.xs4\\Images", + "BlurMethod": 0, + "BlurRadius": 0, + "EditorBackgroundColor": "", + "EditorBackgroundColorObject": null, + "ExpandToIDE": false, + "Extensions": ".png, .jpg, .gif, .bmp", + "ImageBackgroundType": 0, + "ImageFadeAnimationInterval": "PT5S", + "ImageStretch": 0, + "IncludeSubdirectories": false, + "IsLimitToMainlyEditorWindow": false, + "IsTransparentToContentMargin": false, + "IsTransparentToStickyScroll": false, + "LoopSlideshow": true, + "MaxHeight": 0, + "MaxWidth": 0, + "Opacity": 0.1, + "PositionHorizon": 1, + "PositionVertical": 1, + "ShuffleSlideshow": false, + "SoftEdgeX": 0, + "SoftEdgeY": 0, + "StickyScrollColor": "#00000000", + "StickyScrollColorObject": { + "A": 0, + "B": 0, + "G": 0, + "R": 0, + "ScA": 0, + "ScB": 0, + "ScG": 0, + "ScR": 0 + }, + "TileMode": 0, + "UpdateImageInterval": "PT1M", + "ViewBoxPointX": 0, + "ViewBoxPointY": 0, + "ViewPortHeight": 1, + "ViewPortPointX": 0, + "ViewPortPointY": 0, + "ViewPortWidth": 1, + "WebApiDownloadInterval": "PT5M", + "WebApiEndpoint": "", + "WebApiJsonKey": "", + "WebSingleUrl": "" +} \ No newline at end of file diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 872601e..75e6bb6 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -146,11 +146,11 @@ namespace Serein.Library.Api /// /// 是否完成 /// - public bool IsSucceed { get; protected set; } = true; + public bool IsSucceed { get;} = true; /// /// 错误提示 /// - public string ErrorTips { get; protected set; } = string.Empty; + public string ErrorTips { get;} = string.Empty; } /// @@ -168,9 +168,15 @@ namespace Serein.Library.Api /// public class ProjectSavingEventArgs : FlowEventArgs { - public ProjectSavingEventArgs() + public ProjectSavingEventArgs(SereinProjectData projectData) { + ProjectData = projectData; } + + /// + /// 项目数据 + /// + public SereinProjectData ProjectData { get; } } /// @@ -186,11 +192,11 @@ namespace Serein.Library.Api /// /// 已加载了的程序集 /// - public NodeLibraryInfo NodeLibraryInfo { get; protected set; } + public NodeLibraryInfo NodeLibraryInfo { get;} /// /// dll文件中有效的流程方法描述 /// - public List MethodDetailss { get; protected set; } + public List MethodDetailss { get;} } /// @@ -279,31 +285,31 @@ namespace Serein.Library.Api /// /// 连接关系中始节点的Guid /// - public string FromNodeGuid { get; protected set; } + public string FromNodeGuid { get;} /// /// 连接关系中目标节点的Guid /// - public string ToNodeGuid { get; protected set; } + public string ToNodeGuid { get;} /// /// 连接类型 /// - public ConnectionInvokeType ConnectionInvokeType { get; protected set; } + public ConnectionInvokeType ConnectionInvokeType { get;} /// /// 表示此次需要在两个节点之间创建连接关系,或是移除连接关系 /// - public ConnectChangeType ChangeType { get; protected set; } + public ConnectChangeType ChangeType { get;} /// /// 指示需要创建什么类型的连接线 /// - public JunctionOfConnectionType JunctionOfConnectionType { get; protected set; } + public JunctionOfConnectionType JunctionOfConnectionType { get;} /// /// 节点对应的方法入参所需参数来源 /// - public ConnectionArgSourceType ConnectionArgSourceType { get; protected set; } + public ConnectionArgSourceType ConnectionArgSourceType { get;} /// /// 第几个参数 /// - public int ArgIndex { get; protected set; } + public int ArgIndex { get;} } @@ -313,12 +319,12 @@ namespace Serein.Library.Api /// public class CanvasCreateEventArgs : FlowEventArgs { - public CanvasCreateEventArgs(FlowCanvasModel model) + public CanvasCreateEventArgs(FlowCanvasDetails model) { Model = model; } - public FlowCanvasModel Model { get; } + public FlowCanvasDetails Model { get; } } /// @@ -342,6 +348,7 @@ namespace Serein.Library.Api /// /// 节点添加事件参数 /// + /// 画布 /// 节点对象 /// 位置 public NodeCreateEventArgs(string canvasGuid, NodeModelBase nodeModel, PositionOfUI position) @@ -350,15 +357,23 @@ namespace Serein.Library.Api this.NodeModel = nodeModel; this.Position = position; } - + /// + /// 所处画布Guid + /// public string CanvasGuid { get; } /// /// 节点Model对象 /// public NodeModelBase NodeModel { get; private set; } + /// + /// 在UI上的位置 + /// public PositionOfUI Position { get; private set; } - public string RegeionGuid { get; private set; } + /// + /// 容器 + /// + //public string RegeionGuid { get; private set; } } /// @@ -487,16 +502,16 @@ namespace Serein.Library.Api /// /// 中断的节点Guid /// - public string NodeGuid { get; protected set; } + public string NodeGuid { get;} /// /// 监听对象类别 /// - public ObjSourceType ObjSource { get; protected set; } + public ObjSourceType ObjSource { get;} /// /// 新的数据 /// - public object NewData { get; protected set; } + public object NewData { get;} } /// @@ -514,9 +529,9 @@ namespace Serein.Library.Api /// /// 中断的节点Guid /// - public string NodeGuid { get; protected set; } - public bool IsInterrupt { get; protected set; } - // public InterruptClass Class { get; protected set; } + public string NodeGuid { get;} + public bool IsInterrupt { get;} + // public InterruptClass Class { get;} } /// /// 节点触发了中断事件参数 @@ -549,9 +564,9 @@ namespace Serein.Library.Api /// /// 中断的节点Guid /// - public string NodeGuid { get; protected set; } - public string Expression { get; protected set; } - public InterruptTriggerType Type { get; protected set; } + public string NodeGuid { get;} + public string Expression { get;} + public InterruptTriggerType Type { get;} } @@ -880,7 +895,7 @@ namespace Serein.Library.Api /// 宽度 /// 高度 /// - Task CreateCanvasAsync(string canvasName, int width , int height); + Task CreateCanvasAsync(string canvasName, int width , int height); /// /// 删除画布 diff --git a/Library/Extension/FlowModelExtension.cs b/Library/Extension/FlowModelExtension.cs index 9e6e652..df53097 100644 --- a/Library/Extension/FlowModelExtension.cs +++ b/Library/Extension/FlowModelExtension.cs @@ -21,9 +21,9 @@ namespace Serein.Library /// /// /// - public static FlowCanvasInfo ToInfo(this FlowCanvasModel model) + public static FlowCanvasDetailsInfo ToInfo(this FlowCanvasDetails model) { - return new FlowCanvasInfo + return new FlowCanvasDetailsInfo { Guid = model.Guid, Height = model.Height, @@ -33,6 +33,7 @@ namespace Serein.Library ScaleY = model.ScaleY, ViewX = model.ViewX, ViewY = model.ViewY, + StartNode = model.StartNode, }; } @@ -41,7 +42,7 @@ namespace Serein.Library /// /// /// - public static void LoadInfo(this FlowCanvasModel model, FlowCanvasInfo info) + public static void LoadInfo(this FlowCanvasDetails model, FlowCanvasDetailsInfo info) { model.Guid = info.Guid; model.Height = info.Height; @@ -51,6 +52,7 @@ namespace Serein.Library model.ScaleY = info.ScaleY; model.ViewX = info.ViewX; model.ViewY = info.ViewY; + model.StartNode = info.StartNode; } /// @@ -102,6 +104,7 @@ namespace Serein.Library NodeInfo nodeInfo = new NodeInfo { Guid = nodeModel.Guid, + CanvasGuid = nodeModel.CanvasGuid, AssemblyName = nodeModel.MethodDetails.AssemblyName, MethodName = nodeModel.MethodDetails?.MethodName, Label = nodeModel.MethodDetails?.MethodAnotherName, diff --git a/Library/FlowNode/FlowCanvasModel.cs b/Library/FlowNode/FlowCanvasDetails.cs similarity index 51% rename from Library/FlowNode/FlowCanvasModel.cs rename to Library/FlowNode/FlowCanvasDetails.cs index 98d05e6..4f56a64 100644 --- a/Library/FlowNode/FlowCanvasModel.cs +++ b/Library/FlowNode/FlowCanvasDetails.cs @@ -1,4 +1,5 @@ using Serein.Library.Api; +using Serein.Library.FlowNode; using System; using System.Collections.Generic; using System.Linq; @@ -8,50 +9,86 @@ using System.Threading.Tasks; namespace Serein.Library { + + + + /// + /// 流程画布 + /// [NodeProperty(ValuePath = NodeValuePath.Node)] - public partial class FlowCanvasModel + public partial class FlowCanvasDetails { - public FlowCanvasModel(IFlowEnvironment env) + public FlowCanvasDetails(IFlowEnvironment env) { Env = env; } + public IFlowEnvironment Env { get; } + /// + /// 标识画布ID + /// [PropertyInfo(IsProtection = true)] private string _guid; + /// + /// 画布名称 + /// [PropertyInfo(IsNotification = true)] private string _name; + /// + /// 画布宽度 + /// [PropertyInfo(IsNotification = true)] private double _width; + /// + /// 画布高度 + /// [PropertyInfo(IsNotification = true)] private double _height; /// /// 预览位置X /// - [PropertyInfo] + [PropertyInfo(IsNotification = true)] private double _viewX ; /// /// 预览位置Y /// - [PropertyInfo] + [PropertyInfo(IsNotification = true)] private double _viewY ; /// /// 缩放比例X /// - [PropertyInfo] - private double _scaleX ; + [PropertyInfo(IsNotification = true)] + private double _scaleX = 1; /// /// 缩放比例Y /// - [PropertyInfo] - private double _scaleY ; + [PropertyInfo(IsNotification = true)] + private double _scaleY = 1; + + + /// + /// 起始节点私有属性 + /// + private string _startNode; + + } + + + public partial class FlowCanvasDetails + { + + + } + + } diff --git a/Library/FlowNode/FlowCanvasDetailsInfo.cs b/Library/FlowNode/FlowCanvasDetailsInfo.cs new file mode 100644 index 0000000..ee55778 --- /dev/null +++ b/Library/FlowNode/FlowCanvasDetailsInfo.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Library +{ + + /// + /// 画布信息 + /// + public class FlowCanvasDetailsInfo + { + /// + /// 起始节点Guid + /// + public string StartNode; + + /// + /// 标识画布ID + /// + public string Guid; + + /// + /// 画布名称 + /// + public string Name; + + /// + /// 画布宽度 + /// + public double Width; + + /// + /// 画布高度 + /// + public double Height; + + /// + /// 预览位置X + /// + public double ViewX; + + /// + /// 预览位置Y + /// + public double ViewY; + + /// + /// 缩放比例X + /// + public double ScaleX; + + /// + /// 缩放比例Y + /// + public double ScaleY; + } +} diff --git a/Library/FlowNode/SereinProjectData.cs b/Library/FlowNode/SereinProjectData.cs index 778fd3b..8ccc9fa 100644 --- a/Library/FlowNode/SereinProjectData.cs +++ b/Library/FlowNode/SereinProjectData.cs @@ -48,7 +48,7 @@ namespace Serein.Library /// - /// 项目保存文件 + /// 项目数据 /// public class SereinProjectData { @@ -64,16 +64,20 @@ namespace Serein.Library public NodeLibraryInfo[] Librarys { get; set; } - /// - /// 起始节点GUID - /// + ///// + ///// 起始节点GUID + ///// - public string StartNode { get; set; } + //public string StartNode { get; set; } + + /// + /// 画布集合 + /// + public FlowCanvasDetailsInfo[] Canvass { get; set; } /// /// 节点集合 /// - public NodeInfo[] Nodes { get; set; } } @@ -83,11 +87,10 @@ namespace Serein.Library /// public class Basic { - /// - /// 画布 - /// - - public FlowCanvasInfo Canvas { get; set; } + ///// + ///// 画布 + ///// + //public FlowCanvasInfo Canvas { get; set; } /// /// 版本 @@ -95,7 +98,9 @@ namespace Serein.Library public string Versions { get; set; } } - /// + + + /* /// /// 画布信息,项目文件相关 /// public class FlowCanvasInfo @@ -132,7 +137,7 @@ namespace Serein.Library /// 缩放比例Y /// public double ScaleY { get; set; } - } + }*/ /// /// 项目依赖的程序集,项目文件相关 @@ -187,7 +192,6 @@ namespace Serein.Library /// public string CanvasGuid { get; set; } - /// /// 节点的GUID /// diff --git a/Library/Utils/UIContextOperation.cs b/Library/Utils/UIContextOperation.cs index 53d4b28..d377792 100644 --- a/Library/Utils/UIContextOperation.cs +++ b/Library/Utils/UIContextOperation.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -91,6 +92,7 @@ namespace Serein.Library.Utils catch (Exception ex) { tcs.SetException(ex); + Debug.WriteLine(ex); } }, null); diff --git a/NodeFlow/Env/EnvMsgTheme.cs b/NodeFlow/Env/EnvMsgTheme.cs index b69cc04..146645f 100644 --- a/NodeFlow/Env/EnvMsgTheme.cs +++ b/NodeFlow/Env/EnvMsgTheme.cs @@ -5,6 +5,10 @@ /// public static class EnvMsgTheme { + /// + /// 尝试保存项目 + /// + public const string SaveProject = nameof(SaveProject); /// /// 获取远程环境信息 /// diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 7caa234..79265f3 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -301,7 +301,7 @@ namespace Serein.NodeFlow.Env /// /// 运行环境加载的画布集合 /// - private Dictionary FlowCanvass { get; } = []; + private Dictionary FlowCanvass { get; } = []; /// /// 存放触发器节点(运行时全部调用) @@ -309,35 +309,35 @@ namespace Serein.NodeFlow.Env private List FlipflopNodes { get; } = []; + /* + /// + /// 起始节点私有属性 + /// + private NodeModelBase? _startNode = null; - /// - /// 起始节点私有属性 - /// - private NodeModelBase? _startNode = null; - - /// - /// 起始节点 - /// - private NodeModelBase? StartNode - { - get - { - return _startNode; - } - set - { - if (value is null) - { - return; - } - if (_startNode is not null) - { - _startNode.IsStart = false; - } - value.IsStart = true; - _startNode = value; - } - } + /// + /// 起始节点 + /// + private NodeModelBase? StartNode + { + get + { + return _startNode; + } + set + { + if (value is null) + { + return; + } + if (_startNode is not null) + { + _startNode.IsStart = false; + } + value.IsStart = true; + _startNode = value; + } + }*/ /// /// 流程任务管理 @@ -373,12 +373,11 @@ namespace Serein.NodeFlow.Env IOC.Reset(); IOC.Register(); // 注册脚本接口 - var flowTaskOptions = new FlowWorkLibrary + var flowTaskOptions = new FlowWorkOptions { - Environment = this, FlowContextPool = new ObjectPool(() => new DynamicContext(this)), - Nodes = NodeModels.Values.ToList(), + //Nodes = NodeModels.Values.ToList(), AutoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(), InitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init), LoadMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Loading), @@ -534,7 +533,8 @@ namespace Serein.NodeFlow.Env /// public void SaveProject() { - OnProjectSaving?.Invoke(new ProjectSavingEventArgs()); + var project = GetProjectInfoAsync().GetAwaiter().GetResult(); + OnProjectSaving?.Invoke(new ProjectSavingEventArgs(project)); } /// @@ -562,7 +562,7 @@ namespace Serein.NodeFlow.Env _ = Task.Run( async () => { await LoadNodeInfosAsync(projectData.Nodes.ToList()); // 加载节点信息 - await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点 + //await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点 }); } @@ -615,16 +615,17 @@ namespace Serein.NodeFlow.Env /// 序列化当前项目的依赖信息、节点信息 /// /// - public Task GetProjectInfoAsync() + public async Task GetProjectInfoAsync() { var projectData = new SereinProjectData() { Librarys = this.FlowLibraryManagement.GetAllLibraryInfo().ToArray(), Nodes = NodeModels.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), - StartNode = NodeModels.Values.FirstOrDefault(it => it.IsStart)?.Guid, + Canvass = FlowCanvass.Values.Select(canvas => canvas.ToInfo()).ToArray(), + //StartNode = NodeModels.Values.FirstOrDefault(it => it.IsStart)?.Guid, }; - return Task.FromResult(projectData); + return projectData; } @@ -776,22 +777,22 @@ namespace Serein.NodeFlow.Env /// 宽度 /// 高度 /// - public Task CreateCanvasAsync(string canvasName, int width, int height) + public async Task CreateCanvasAsync(string canvasName, int width, int height) { - var model = new FlowCanvasModel(this) + var model = new FlowCanvasDetails(this) { Guid = Guid.NewGuid().ToString(), Height = height, - Name = !string.IsNullOrWhiteSpace(canvasName) ? canvasName : $"流程图 {_addCanvasCount++}", - Width = height, + Width = width, + Name = !string.IsNullOrWhiteSpace(canvasName) ? canvasName : $"流程图{_addCanvasCount++}", }; FlowCanvass.Add(model.Guid, model); - UIContextOperation.Invoke(() => + await UIContextOperation.InvokeAsync(() => { OnCanvasCreate.Invoke(new CanvasCreateEventArgs(model)); }); var info = model.ToInfo(); - return Task.FromResult(info); + return info; } /// @@ -799,15 +800,29 @@ namespace Serein.NodeFlow.Env /// /// 画布Guid /// - public Task RemoveCanvasAsync(string canvasGuid) + public async Task RemoveCanvasAsync(string canvasGuid) { if (!FlowCanvass.TryGetValue(canvasGuid, out var model)) { - return Task.FromResult(false); + return false; } var count = NodeModels.Values.Count(node => node.CanvasGuid.Equals(canvasGuid)); - return Task.FromResult(count == 0); + if(count > 0) + { + SereinEnv.WriteLine(InfoType.WARN, "无法删除具有节点的画布"); + return false; + } + if (FlowCanvass.Remove(canvasGuid)) + { + await UIContextOperation.InvokeAsync(() => + { + OnCanvasRemove.Invoke(new CanvasRemoveEventArgs(canvasGuid)); + }); + return true; + } + + return false; } @@ -981,7 +996,7 @@ namespace Serein.NodeFlow.Env PositionOfUI position, MethodDetailsInfo? methodDetailsInfo = null) { - if (!FlowCanvass.ContainsKey(canvasGuid)) + if (!TryGetCanvasModel(canvasGuid,out var cavnasModel)) { return Task.FromResult(null); } @@ -1005,16 +1020,17 @@ namespace Serein.NodeFlow.Env } TryAddNode(nodeModel); - nodeModel.Position = position; + nodeModel.CanvasGuid = canvasGuid; // 设置所属于的画布 + nodeModel.Position = position; // 设置位置 // 通知UI更改 UIContextOperation?.Invoke(() => OnNodeCreate?.Invoke(new NodeCreateEventArgs(canvasGuid, nodeModel, position))); // 因为需要UI先布置了元素,才能通知UI变更特效 // 如果不存在流程起始控件,默认设置为流程起始控件 - if (StartNode is null) + if (cavnasModel.StartNode is null) { - SetStartNode(canvasGuid, nodeModel); + SetStartNode(cavnasModel, nodeModel); } var nodeInfo = nodeModel.ToInfo(); return Task.FromResult(nodeInfo); @@ -1392,15 +1408,16 @@ namespace Serein.NodeFlow.Env /// /// 设置起点控件 /// - /// + /// 画布 + /// 节点Guid public Task SetStartNodeAsync(string canvasGuid, string newNodeGuid) { - if (!FlowCanvass.ContainsKey(canvasGuid) || !TryGetNodeModel(newNodeGuid, out var newStartNodeModel)) + if (!TryGetCanvasModel(canvasGuid, out var canvasModel) || !TryGetNodeModel(newNodeGuid, out var newStartNodeModel)) { - return Task.FromResult(StartNode?.Guid ?? string.Empty); + return Task.FromResult(canvasModel.StartNode ?? string.Empty); } - SetStartNode(canvasGuid, newStartNodeModel); - return Task.FromResult(StartNode?.Guid ?? string.Empty); + SetStartNode(canvasModel, newStartNodeModel); + return Task.FromResult(canvasModel.StartNode ?? string.Empty); } /// @@ -1512,9 +1529,25 @@ namespace Serein.NodeFlow.Env } + /// + /// 从Guid获取画布 + /// + /// 节点Guid + /// 节点Model + /// 无法获取节点、Guid/节点为null时报错 + public bool TryGetCanvasModel(string nodeGuid, out FlowCanvasDetails canvasDetails) + { + if (string.IsNullOrEmpty(nodeGuid)) + { + canvasDetails = null; + return false; + } + return FlowCanvass.TryGetValue(nodeGuid, out canvasDetails) && canvasDetails is not null; + + } /// - /// Guid 转 NodeModel + /// 从Guid获取节点 /// /// 节点Guid /// 节点Model @@ -1913,15 +1946,11 @@ namespace Serein.NodeFlow.Env /// /// /// - private void SetStartNode(string canvasGuid, NodeModelBase newStartNode) + private void SetStartNode(FlowCanvasDetails cavnasModel, NodeModelBase newStartNode) { - if (!FlowCanvass.ContainsKey(canvasGuid)) - { - return; - } - var oldNodeGuid = StartNode?.Guid; - StartNode = newStartNode; - UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(canvasGuid, oldNodeGuid, StartNode.Guid))); + var oldNodeGuid = cavnasModel.StartNode; + cavnasModel.StartNode = newStartNode.Guid; + UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode))); //if (OperatingSystem.IsWindows()) //{ diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 8d310a0..5456d7c 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -238,7 +238,7 @@ namespace Serein.NodeFlow.Env /// 宽度 /// 高度 /// - public async Task CreateCanvasAsync(string canvasName, int width, int height) + public async Task CreateCanvasAsync(string canvasName, int width, int height) { return await currentFlowEnvironment.CreateCanvasAsync(canvasName, width, height); } diff --git a/NodeFlow/Env/MsgControllerOfClient.cs b/NodeFlow/Env/MsgControllerOfClient.cs index 767cd8c..030136c 100644 --- a/NodeFlow/Env/MsgControllerOfClient.cs +++ b/NodeFlow/Env/MsgControllerOfClient.cs @@ -164,7 +164,7 @@ namespace Serein.NodeFlow.Env } [AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateCanvas, IsReturnValue = false)] - public void CreateCanvas([UseMsgId] string msgId, [UseData] FlowCanvasInfo canvasInfo) + public void CreateCanvas([UseMsgId] string msgId, [UseData] FlowCanvasDetailsInfo canvasInfo) { _ = remoteFlowEnvironment.InvokeTriggerAsync(msgId, canvasInfo); } diff --git a/NodeFlow/Env/MsgControllerOfServer.cs b/NodeFlow/Env/MsgControllerOfServer.cs index b896383..5902b98 100644 --- a/NodeFlow/Env/MsgControllerOfServer.cs +++ b/NodeFlow/Env/MsgControllerOfServer.cs @@ -312,7 +312,7 @@ namespace Serein.NodeFlow.Env [AutoSocketHandle(ThemeValue = EnvMsgTheme.CreateCanvas, IsReturnValue = false)] - public async Task CreateCanvas(string canvasName, int width, int height) + public async Task CreateCanvas(string canvasName, int width, int height) { var canvasInfo = await environment.CreateCanvasAsync(canvasName, width, height); // 监听到客户端创建节点的请求 return canvasInfo; diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index 95ad133..c4cd54b 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -131,11 +131,28 @@ namespace Serein.NodeFlow.Env } /// - /// 保存项目 + /// 远程环境下保存项目 /// public void SaveProject() { - OnProjectSaving?.Invoke(new ProjectSavingEventArgs()); + _ = Task.Run(async () => + { + // 保存项目 + var result = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.SaveProject); + if (result is not null) + { + OnProjectSaving?.Invoke(new ProjectSavingEventArgs(result)); + } + }); + + // 获取远程环境 + //var projectData = new SereinProjectData() + //{ + // Librarys = this.FlowLibraryManagement.GetAllLibraryInfo().ToArray(), + // Nodes = NodeModels.Values.Select(node => node.ToInfo()).Where(info => info is not null).ToArray(), + // StartNode = NodeModels.Values.FirstOrDefault(it => it.IsStart)?.Guid, + //}; + } @@ -167,14 +184,26 @@ namespace Serein.NodeFlow.Env } #endregion - var nodeInfos = flowEnvInfo.Project.Nodes.ToList(); - LoadNodeInfos(nodeInfos); // 加载节点 - - var canvasGuid = nodeInfos.FirstOrDefault(item => item.Guid == flowEnvInfo.Project.StartNode)?.CanvasGuid; - if (!string.IsNullOrEmpty(canvasGuid)) + // 加载画布 + foreach (var info in flowEnvInfo.Project.Canvass) { - _ = SetStartNodeAsync(canvasGuid, flowEnvInfo.Project.StartNode); // 设置流程起点 + var canvasModel = new FlowCanvasDetails(this); + canvasModel.LoadInfo(info); + var e = new CanvasCreateEventArgs(canvasModel); + OnCanvasCreate?.Invoke(e); } + + // 加载节点 + var nodeInfos = flowEnvInfo.Project.Nodes.ToList(); + LoadNodeInfos(nodeInfos); + + // 设置每个画布的起始节点 + foreach (var info in flowEnvInfo.Project.Canvass) + { + _ = SetStartNodeAsync(info.Guid, info.StartNode); // 设置流程起点 + + } + UIContextOperation?.Invoke(() => { OnProjectLoaded?.Invoke(new ProjectLoadedEventArgs()); // 加载完成 @@ -427,9 +456,9 @@ namespace Serein.NodeFlow.Env /// 宽度 /// 高度 /// - public async Task CreateCanvasAsync(string canvasName, int width, int height) + public async Task CreateCanvasAsync(string canvasName, int width, int height) { - var info = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.CreateCanvas, new + var info = await msgClient.SendAndWaitDataAsync(EnvMsgTheme.CreateCanvas, new { canvasName, width, @@ -437,7 +466,7 @@ namespace Serein.NodeFlow.Env }); if (info is not null) { - var model = new FlowCanvasModel(this) + var model = new FlowCanvasDetails(this) { Guid = info.Guid, Height = info.Height, @@ -506,6 +535,7 @@ namespace Serein.NodeFlow.Env /// /// 设置远程环境的流程起点节点 /// + /// 节点画布 /// 尝试设置为起始节点的节点Guid /// 被设置为起始节点的Guid public async Task SetStartNodeAsync(string canvasGuid, string nodeGuid) diff --git a/NodeFlow/FlowWorkManagement.cs b/NodeFlow/FlowWorkManagement.cs index 3d697ed..9b4a4d1 100644 --- a/NodeFlow/FlowWorkManagement.cs +++ b/NodeFlow/FlowWorkManagement.cs @@ -6,6 +6,7 @@ using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; using System; using System.Collections.Concurrent; +using System.Threading.Tasks.Dataflow; using System.Xml.Linq; namespace Serein.NodeFlow @@ -20,25 +21,24 @@ namespace Serein.NodeFlow /// private ConcurrentDictionary dictGlobalFlipflop = []; - - /// /// 结束运行时需要执行的方法 /// private Func? ExitAction { get; set; } + /// /// 初始化选项 /// - public FlowWorkLibrary WorkLibrary { get; } + public FlowWorkOptions WorkOptions { get; } /// /// 流程任务管理 /// - /// - public FlowWorkManagement(FlowWorkLibrary library) + /// + public FlowWorkManagement(FlowWorkOptions options) { - WorkLibrary = library; + WorkOptions = options; } @@ -48,43 +48,83 @@ namespace Serein.NodeFlow /// public async Task RunAsync(CancellationToken token) { - NodeModelBase? startNode = WorkLibrary.Nodes.FirstOrDefault(node => node.IsStart); - if (startNode is null) + #region 注册所有节点所属的类的类型,如果注册失败则退出 + List nodes = new List(); + foreach (var item in WorkOptions.Flows.Values) + { + var temp = item.GetNodes(); + nodes.AddRange(temp); + } + if (!RegisterAllType(nodes)) { return false; } + #endregion + + #region 调用所有流程类的Init、Load事件 - if (!RegisterAllType()) - { - return false; - }; var initState = await TryInit(); if (!initState) { return false; - }; + } + ; var loadState = await TryLoadAsync(); if (!loadState) { return false; - }; - var task = CallFlipflopNode(); - await CallStartNode(startNode); - await task; + } + ; + #endregion + + // 开始调用流程 + foreach (var kvp in WorkOptions.Flows) + { + var guid = kvp.Key; + var flow = kvp.Value; + var flowNodes = flow.GetNodes(); + + // 找到流程的起始节点,开始运行 + NodeModelBase? startNode = flowNodes.FirstOrDefault(node => node.IsStart); + if (startNode is null) + { + return false; + } + // 是否后台运行当前画布流程 + if (flow.IsTaskAsync) + { + _ = Task.Run(async () => await CallStartNode(startNode)); + } + else + { + await CallStartNode(startNode); + } + var flipflopTasks = CallFlipflopNode(flow); // 获取所有触发器异步任务 + _ = Task.Run(async () => await flipflopTasks); // 后台调用流程中的触发器 + } + + // 等待流程运行完成 await CallExit(); return true; } #region 初始化 - private bool RegisterAllType() + /// + /// 初始化节点所需的所有类型 + /// + /// + private bool RegisterAllType(List nodes) { - var env = WorkLibrary.Environment; - var nodeMds = WorkLibrary.Nodes.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息 + var env = WorkOptions.Environment; + + + + var nodeMds = nodes.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息 var allMds = new List(); allMds.AddRange(nodeMds.Where(md => md?.ActingInstanceType is not null)); - allMds.AddRange(WorkLibrary.InitMds.Where(md => md?.ActingInstanceType is not null)); - allMds.AddRange(WorkLibrary.LoadMds.Where(md => md?.ActingInstanceType is not null)); - allMds.AddRange(WorkLibrary.ExitMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkOptions.InitMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkOptions.LoadMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkOptions.ExitMds.Where(md => md?.ActingInstanceType is not null)); var isSuccessful = true; foreach (var md in allMds) { @@ -114,10 +154,10 @@ namespace Serein.NodeFlow private async Task TryInit() { - var env = WorkLibrary.Environment; - var initMds = WorkLibrary.InitMds; - var pool = WorkLibrary.FlowContextPool; - var ioc = WorkLibrary.Environment.IOC; + var env = WorkOptions.Environment; + var initMds = WorkOptions.InitMds; + var pool = WorkOptions.FlowContextPool; + var ioc = WorkOptions.Environment.IOC; foreach (var md in initMds) // 初始化 { if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 @@ -136,10 +176,10 @@ namespace Serein.NodeFlow } private async Task TryLoadAsync() { - var env = WorkLibrary.Environment; - var loadMds = WorkLibrary.LoadMds; - var pool = WorkLibrary.FlowContextPool; - var ioc = WorkLibrary.Environment.IOC; + var env = WorkOptions.Environment; + var loadMds = WorkOptions.LoadMds; + var pool = WorkOptions.FlowContextPool; + var ioc = WorkOptions.Environment.IOC; foreach (var md in loadMds) // 加载时 { if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 @@ -159,10 +199,10 @@ namespace Serein.NodeFlow } private async Task CallExit() { - var env = WorkLibrary.Environment; - var mds = WorkLibrary.ExitMds; - var pool = WorkLibrary.FlowContextPool; - var ioc = WorkLibrary.Environment.IOC; + var env = WorkOptions.Environment; + var mds = WorkOptions.ExitMds; + var pool = WorkOptions.FlowContextPool; + var ioc = WorkOptions.Environment.IOC; ioc.Run(fit => fit.CancelAllTrigger());// 取消所有中断 foreach (var md in mds) // 结束时 @@ -186,10 +226,10 @@ namespace Serein.NodeFlow return isSuccessful; } - private Task CallFlipflopNode() + private Task CallFlipflopNode(FlowTask flow) { - var env = WorkLibrary.Environment; - var flipflopNodes = WorkLibrary.Nodes.Where(item => item is SingleFlipflopNode node + var env = WorkOptions.Environment; + var flipflopNodes = flow.GetNodes().Where(item => item is SingleFlipflopNode node && !node.IsStart && node.DebugSetting.IsEnable && node.NotExitPreviousNode()) @@ -206,10 +246,16 @@ namespace Serein.NodeFlow } return Task.CompletedTask; } + + /// + /// 从某一个节点开始执行 + /// + /// + /// private async Task CallStartNode(NodeModelBase startNode) { - var pool = WorkLibrary.FlowContextPool; - var token = WorkLibrary.CancellationTokenSource.Token; + var pool = WorkOptions.FlowContextPool; + var token = WorkOptions.CancellationTokenSource.Token; var context = pool.Allocate(); await startNode.StartFlowAsync(context, token); context.Exit(); @@ -227,9 +273,9 @@ namespace Serein.NodeFlow /// public async Task StartFlowInSelectNodeAsync(IFlowEnvironment env, NodeModelBase startNode) { - var pool = WorkLibrary.FlowContextPool; + var pool = WorkOptions.FlowContextPool; var context = pool.Allocate(); - var token = WorkLibrary.CancellationTokenSource.Token; + var token = WorkOptions.CancellationTokenSource.Token; await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行 context.Reset(); pool.Free(context); @@ -294,7 +340,7 @@ namespace Serein.NodeFlow CancellationToken singleToken) { - var pool = WorkLibrary.FlowContextPool; + var pool = WorkOptions.FlowContextPool; while (!singleToken.IsCancellationRequested && !singleToken.IsCancellationRequested) { try diff --git a/NodeFlow/FlowWorkLibrary.cs b/NodeFlow/FlowWorkOptions.cs similarity index 68% rename from NodeFlow/FlowWorkLibrary.cs rename to NodeFlow/FlowWorkOptions.cs index 2e0ec1f..bce319a 100644 --- a/NodeFlow/FlowWorkLibrary.cs +++ b/NodeFlow/FlowWorkOptions.cs @@ -9,10 +9,29 @@ using System.Threading.Tasks; namespace Serein.NodeFlow { + + public class FlowTask + { + /// + /// 是否异步启动流程 + /// + public bool IsTaskAsync { get; set; } + + /// + /// 流程起始节点 + /// + public Func GetStartNode { get; set; } + + /// + /// 获取当前画布流程的所有节点 + /// + public Func> GetNodes { get; set; } + } + /// /// 节点任务执行依赖 /// - public class FlowWorkLibrary() + public class FlowWorkOptions() { /// /// 流程运行环境 @@ -29,14 +48,21 @@ namespace Serein.NodeFlow /// public Serein.Library.Utils.ObjectPool FlowContextPool { get; set; } + /// + /// 每个画布需要启用的节点 + /// + public Dictionary Flows { get; set; } + /// /// 当前任务加载的所有节点 /// - public List Nodes { get; set; }// = nodes; + //public List Nodes { get; set; }// = nodes; + /// /// 需要注册的类型 /// public Dictionary> AutoRegisterTypes { get; set; } //= autoRegisterTypes; + /// /// 初始化时需要的方法 /// diff --git a/Serein.Library.MyGenerator/Attribute.cs b/Serein.Library.MyGenerator/Attribute.cs index bca2463..ba8c22a 100644 --- a/Serein.Library.MyGenerator/Attribute.cs +++ b/Serein.Library.MyGenerator/Attribute.cs @@ -59,10 +59,12 @@ namespace Serein.Library /// 是否通知UI /// public bool IsNotification = false; + /// /// 是否使用Console.WriteLine打印 /// public bool IsPrint = false; + /// /// 是否禁止参数进行修改(初始化后不能再通过 Setter 修改) /// diff --git a/Workbench/Api/IFlowCanvas.cs b/Workbench/Api/IFlowCanvas.cs new file mode 100644 index 0000000..bd965cb --- /dev/null +++ b/Workbench/Api/IFlowCanvas.cs @@ -0,0 +1,68 @@ +using Serein.Library; +using Serein.Workbench.Node.View; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.Workbench.Api +{ + /// + /// 流程画布 + /// + public interface IFlowCanvas + { + /// + /// 画布标识 + /// + string Guid { get; } + + /// + /// 画布名称 + /// + string Name { get; } + + /// + /// 移除节点 + /// + void Remove(NodeControlBase nodeControl); + + /// + /// 添加节点 + /// + void Add(NodeControlBase nodeControl); + + /// + /// 创建节点之间方法调用关系 + /// + /// 调用顺序中的起始节点 + /// 下一节点 + /// 调用类型 + void CreateInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionInvokeType type); + + /// + /// 移除节点之间的调用关系 + /// + /// 调用顺序中的起始节点 + /// 下一节点 + void RemoveInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl); + + /// + /// 创建节点之间的参数传递关系 + /// + /// 参数来源节点 + /// 获取参数的节点 + /// 指示参数是如何获取的 + /// 作用在节点的第几个入参 + void CreateArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionArgSourceType type, int index); + + /// + /// 移除节点之间的参数传递关系 + /// + /// 参数来源节点 + /// 获取参数的节点 + /// 移除节点第几个入参 + void RemoveArgConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, int index); + } +} diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index b200c20..d13581f 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -32,7 +32,7 @@ namespace Serein.Workbench collection.AddSingleton(); collection.AddSingleton(); - collection.AddTransient(); // 依赖信息 + collection.AddTransient(); // 画布 } public static void AddWorkbenchServices(this IServiceCollection collection) @@ -40,8 +40,8 @@ namespace Serein.Workbench collection.AddSingleton(); // 流程事件管理 collection.AddSingleton(); // 流程事件管理 collection.AddSingleton(); // 节点操作管理 - // collection.AddSingleton(); // 按键事件管理 - //collection.AddSingleton(); // 流程节点控件管理 + // collection.AddSingleton(); // 按键事件管理 + //collection.AddSingleton(); // 流程节点控件管理 } @@ -97,21 +97,17 @@ namespace Serein.Workbench collection.AddViewModelServices(); var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口 App.ServiceProvider = services; - _ = Task.Run(async () => - { - await Task.Delay(500); - await this.LoadLocalProjectAsync(); - App.GetService().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); - - }); +#if DEBUG + _ = this.LoadLocalProjectAsync(); +#endif } private async Task LoadLocalProjectAsync() { - + await Task.Delay(500); #if DEBUG - if (1 == 1) + if (1 == 10) { // 这里是测试代码,可以删除 string filePath; @@ -124,7 +120,7 @@ namespace Serein.Workbench App.FlowProjectData = JsonConvert.DeserializeObject(content); App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// var dir = Path.GetDirectoryName(filePath); - + App.GetService().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); } #endif } diff --git a/Workbench/MainWindow.xaml.cs b/Workbench/MainWindow.xaml.cs index 91a0a20..765ac80 100644 --- a/Workbench/MainWindow.xaml.cs +++ b/Workbench/MainWindow.xaml.cs @@ -7,6 +7,7 @@ using Serein.Library.Utils; using Serein.NodeFlow; using Serein.NodeFlow.Env; using Serein.NodeFlow.Tool; +using Serein.Workbench.Api; using Serein.Workbench.Extension; using Serein.Workbench.Node; using Serein.Workbench.Node.View; @@ -292,14 +293,14 @@ namespace Serein.Workbench { return; } - InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Height);// 设置画布大小 + //InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Height);// 设置画布大小 //foreach (var connection in Connections) //{ // connection.RefreshLine(); // 窗体完成加载后试图刷新所有连接线 //} SereinEnv.WriteLine(InfoType.INFO, $"运行环境当前工作目录:{System.IO.Directory.GetCurrentDirectory()}"); - var canvasData = project.Basic.Canvas; + /*var canvasData = project.Basic.Canvas; if (canvasData is not null) { scaleTransform.ScaleX = 1; @@ -312,7 +313,7 @@ namespace Serein.Workbench translateTransform.Y += canvasData.ViewY; // 应用变换组 FlowChartCanvas.RenderTransform = canvasTransformGroup; - } + }*/ } @@ -356,15 +357,15 @@ namespace Serein.Workbench projectData.Basic = new Basic { - Canvas = new FlowCanvasInfo - { - Height = FlowChartCanvas.Height, - Width = FlowChartCanvas.Width, - ViewX = translateTransform.X, - ViewY = translateTransform.Y, - ScaleX = scaleTransform.ScaleX, - ScaleY = scaleTransform.ScaleY, - }, + //Canvas = new FlowCanvasInfo + //{ + // Height = FlowChartCanvas.Height, + // Width = FlowChartCanvas.Width, + // ViewX = translateTransform.X, + // ViewY = translateTransform.Y, + // ScaleX = scaleTransform.ScaleX, + // ScaleY = scaleTransform.ScaleY, + //}, Versions = "1", }; @@ -527,7 +528,7 @@ namespace Serein.Workbench /// 节点连接关系变更 /// /// - private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) + private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs) { string fromNodeGuid = eventArgs.FromNodeGuid; string toNodeGuid = eventArgs.ToNodeGuid; @@ -2444,7 +2445,7 @@ namespace Serein.Workbench var controlObj = Activator.CreateInstance(controlType, [viewModel]); if (controlObj is NodeControlBase nodeControl) { - nodeControl.NodeCanvas = nodeCanvas; + nodeControl.FlowCanvas = (Api.IFlowCanvas)nodeCanvas; return nodeControl; } else @@ -2731,6 +2732,7 @@ public class FlowLibrary } } #endregion + #region 顶部菜单栏 - 远程管理 private async void ButtonStartRemoteServer_Click(object sender, RoutedEventArgs e) { diff --git a/Workbench/Models/TabModel.cs b/Workbench/Models/TabModel.cs index 3f67122..c1fad2b 100644 --- a/Workbench/Models/TabModel.cs +++ b/Workbench/Models/TabModel.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reflection.Metadata; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -13,35 +14,50 @@ using System.Xml.Linq; namespace Serein.Workbench.Models { - public partial class FlowCanvasModel : ObservableObject + public partial class FlowEditorTabModel : ObservableObject { + /// + /// tab 名称 + /// public string Name { get { - var vm = (FlowCanvasViewModel)content.DataContext; - return vm.Name; + var vm = (FlowCanvasViewModel)Content.DataContext; + return vm.Model.Name ?? "null"; } set { - var vm = (FlowCanvasViewModel)content.DataContext; - vm.Name = value; + var vm = (FlowCanvasViewModel)Content.DataContext; + vm.Model.Name = value; OnPropertyChanged(nameof(Name)); } } + + /// + /// 正在选中 + /// [ObservableProperty] private bool _isSelected; + + /// + /// 正在编辑标题 + /// [ObservableProperty] private bool _isEditing; + + /// + /// tab对应的控件 + /// [ObservableProperty] private FlowCanvasView content; - public FlowCanvasModel() + public FlowEditorTabModel(FlowCanvasView content) { - + this.Content = content; } } diff --git a/Workbench/Node/NodeControlBase.cs b/Workbench/Node/NodeControlBase.cs index 2487cfb..93950d6 100644 --- a/Workbench/Node/NodeControlBase.cs +++ b/Workbench/Node/NodeControlBase.cs @@ -1,6 +1,8 @@ using Serein.Library; using Serein.Library.Api; +using Serein.Workbench.Api; using Serein.Workbench.Node.ViewModel; +using Serein.Workbench.Views; using System.Windows; using System.Windows.Controls; using System.Windows.Data; @@ -18,13 +20,14 @@ namespace Serein.Workbench.Node.View /// /// 节点所在的画布(以后需要将画布封装出来,实现多画布的功能) /// - public Canvas NodeCanvas { get; set; } - - private INodeContainerControl nodeContainerControl; + public IFlowCanvas FlowCanvas { get; set; } + /// /// 如果该节点放置在了某个容器节点,就会记录这个容器节点 /// - private INodeContainerControl NodeContainerControl { get; } + + private INodeContainerControl nodeContainerControl; + /// /// 记录与该节点控件有关的所有连接 @@ -53,11 +56,11 @@ namespace Serein.Workbench.Node.View public void PlaceToContainer(INodeContainerControl nodeContainerControl) { this.nodeContainerControl = nodeContainerControl; - NodeCanvas.Children.Remove(this); // 临时从画布上移除 + FlowCanvas.Remove(this); // 临时从画布上移除 var result = nodeContainerControl.PlaceNode(this); if (!result) // 检查是否放置成功,如果不成功,需要重新添加回来 { - NodeCanvas.Children.Add(this); // 从画布上移除 + FlowCanvas.Add(this); // 从画布上移除 } } @@ -70,7 +73,7 @@ namespace Serein.Workbench.Node.View var result = nodeContainerControl.TakeOutNode(this); // 从控件取出 if (result) // 移除成功时才添加到画布上 { - NodeCanvas.Children.Add(this); // 重新添加到画布上 + FlowCanvas.Add(this); // 重新添加到画布上 if (nodeContainerControl is NodeControlBase containerControl) { this.ViewModel.NodeModel.Position.X = containerControl.ViewModel.NodeModel.Position.X + containerControl.Width + 10; @@ -128,20 +131,12 @@ namespace Serein.Workbench.Node.View /// public void SetBinding() { - // 绑定 Canvas.Left - Binding leftBinding = new Binding("X") - { - Source = ViewModel.NodeModel.Position, // 如果 X 属性在当前 DataContext 中 - Mode = BindingMode.TwoWay - }; + var p = ViewModel.NodeModel.Position; + + Binding leftBinding = new(nameof(p.X)) { Source = p, Mode = BindingMode.TwoWay }; BindingOperations.SetBinding(this, Canvas.LeftProperty, leftBinding); - // 绑定 Canvas.Top - Binding topBinding = new Binding("Y") - { - Source = ViewModel.NodeModel.Position, // 如果 Y 属性在当前 DataContext 中 - Mode = BindingMode.TwoWay - }; + Binding topBinding = new(nameof(p.Y)) { Source = p, Mode = BindingMode.TwoWay }; BindingOperations.SetBinding(this, Canvas.TopProperty, topBinding); } diff --git a/Workbench/Node/View/GlobalDataControl.xaml.cs b/Workbench/Node/View/GlobalDataControl.xaml.cs index 08f4abf..070066b 100644 --- a/Workbench/Node/View/GlobalDataControl.xaml.cs +++ b/Workbench/Node/View/GlobalDataControl.xaml.cs @@ -1,4 +1,5 @@ using Serein.NodeFlow.Model; +using Serein.Workbench.Api; using Serein.Workbench.Node.ViewModel; namespace Serein.Workbench.Node.View diff --git a/Workbench/Serein.WorkBench.csproj b/Workbench/Serein.WorkBench.csproj index 644a844..16613c4 100644 --- a/Workbench/Serein.WorkBench.csproj +++ b/Workbench/Serein.WorkBench.csproj @@ -25,9 +25,11 @@ + + diff --git a/Workbench/Services/FlowEEForwardingService.cs b/Workbench/Services/FlowEEForwardingService.cs index 697bacc..efcebc2 100644 --- a/Workbench/Services/FlowEEForwardingService.cs +++ b/Workbench/Services/FlowEEForwardingService.cs @@ -31,7 +31,7 @@ namespace Serein.Workbench.Services /// 转发流程运行环境各个事件的实现类 /// /// - /// + /// public FlowEEForwardingService(IFlowEnvironment flowEnvironment, IFlowEnvironmentEvent flowEnvironmentEvent) { diff --git a/Workbench/Services/FlowNodeService.cs b/Workbench/Services/FlowNodeService.cs index 1c32e62..cdcb120 100644 --- a/Workbench/Services/FlowNodeService.cs +++ b/Workbench/Services/FlowNodeService.cs @@ -1,9 +1,12 @@ using Serein.Library; using Serein.Library.Api; using Serein.Workbench.Api; +using Serein.Workbench.Node; using Serein.Workbench.Node.View; using Serein.Workbench.Node.ViewModel; +using Serein.Workbench.ViewModels; using Serein.Workbench.Views; +using System.Windows.Controls; namespace Serein.Workbench.Services { @@ -12,9 +15,23 @@ namespace Serein.Workbench.Services /// public class FlowNodeService { + + #region 流程节点操作的相关事件 - public Action OnCreateFlowCanvasView { get; set; } - public Action OnRemoveFlowCanvasView { get; set; } + /// + /// 添加了画布 + /// + public Action OnCreateFlowCanvasView { get; set; } + /// + /// 移除了画布 + /// + public Action OnRemoveFlowCanvasView { get; set; } + + /// + /// 添加了节点 + /// + public Action OnCreateNode { get; set; } + #endregion @@ -32,7 +49,7 @@ namespace Serein.Workbench.Services /// /// 当前需要创建的节点类型 /// - public NodeControlType? CurrentNodeControlType { get; set; } + public NodeControlType CurrentNodeControlType { get; set; } = NodeControlType.None; /// @@ -55,23 +72,27 @@ namespace Serein.Workbench.Services /// public NodeControlBase? ConnectionEndNode { get; set; } - - /* - -*/ - /// /// 记录流程画布 /// - private readonly Dictionary FlowCanvasViews = []; + private readonly Dictionary Canvass = []; + /// /// 记录加载的节点 /// - private readonly Dictionary NodeControls = []; + private readonly Dictionary NodeControls = []; + /// + /// 运行环境接口 + /// private readonly IFlowEnvironment flowEnvironment; + + /// + /// 运行环境事件转发器 + /// private readonly IFlowEEForwardingService flowEEForwardingService; + #region 初始化 public FlowNodeService(IFlowEnvironment flowEnvironment, IFlowEEForwardingService flowEEForwardingService) @@ -79,44 +100,292 @@ namespace Serein.Workbench.Services this.flowEnvironment = flowEnvironment; this.flowEEForwardingService = flowEEForwardingService; InitFlowEvent(); + InitNodeType(); } - public void InitFlowEvent() + + /// + /// 注册节点类型 + /// + private void InitNodeType() { - flowEEForwardingService.OnCanvasCreate += FlowEEForwardingService_OnCanvasCreate; - flowEEForwardingService.OnCanvasRemove += FlowEEForwardingService_OnCanvasRemove; - flowEEForwardingService.OnNodeCreate += FlowEEForwardingService_OnNodeCreate; - flowEEForwardingService.OnNodeRemove += FlowEEForwardingService_OnNodeRemove; + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.UI, typeof(UINodeControl), typeof(UINodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.ConditionRegion, typeof(ConditionRegionControl), typeof(ConditionRegionNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel)); + flowEnvironment.NodeMVVMManagement.RegisterUI(NodeControlType.NetScript, typeof(NetScriptNodeControl), typeof(NetScriptNodeControlViewModel)); } + /// + /// 注册节点事件 + /// + private void InitFlowEvent() + { + flowEEForwardingService.OnCanvasCreate += FlowEEForwardingService_OnCanvasCreate; // 创建了画布 + flowEEForwardingService.OnCanvasRemove += FlowEEForwardingService_OnCanvasRemove; // 移除了画布 + flowEEForwardingService.OnNodeCreate += FlowEEForwardingService_OnNodeCreate; // 创建了节点 + flowEEForwardingService.OnNodeRemove += FlowEEForwardingService_OnNodeRemove; // 移除了节点 + + flowEEForwardingService.OnNodePlace += FlowEEForwardingService_OnNodePlace; // 节点放置在容器中 + flowEEForwardingService.OnNodeTakeOut += FlowEEForwardingService_OnNodeTakeOut; ; // 节点从容器中取出 + + flowEEForwardingService.OnNodeConnectChange += FlowEEForwardingService_OnNodeConnectChange; // 节点连接状态改变事件 + + } + + private void FlowEEForwardingService_OnNodeConnectChange(NodeConnectChangeEventArgs e) + { + var canvasGuid = e.CanvasGuid; + string fromNodeGuid = e.FromNodeGuid; + string toNodeGuid = e.ToNodeGuid; + if (!TryGetCanvas(canvasGuid, out var flowCanvas) + || flowCanvas is not IFlowCanvas flow + || !TryGetControl(fromNodeGuid, out var fromNode) + || !TryGetControl(toNodeGuid, out var toNode)) + { + return; + } + + Action? action = (e.JunctionOfConnectionType, e.ChangeType) switch + { + (JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Create) => () => flow.CreateInvokeConnection(fromNode, toNode, e.ConnectionInvokeType), // 创建节点之间的调用关系 + (JunctionOfConnectionType.Invoke, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => () => flow.RemoveInvokeConnection(fromNode, toNode), // 移除节点之间的调用关系 + (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Create) => () => flow.CreateArgConnection(fromNode, toNode, e.ConnectionArgSourceType, e.ArgIndex), // 创建节点之间的参数传递关系 + (JunctionOfConnectionType.Arg, NodeConnectChangeEventArgs.ConnectChangeType.Remove) => () => flow.RemoveArgConnection(fromNode, toNode, e.ArgIndex), // 移除节点之间的参数传递关系 + _ => null + }; + action?.Invoke(); + return; + + } + + private void FlowEEForwardingService_OnNodeTakeOut(NodeTakeOutEventArgs eventArgs) + { + string nodeGuid = eventArgs.NodeGuid; + if (!TryGetControl(nodeGuid, out var nodeControl)) + { + return; + } + nodeControl.TakeOutContainer(); // 从容器节点中取出 + } + + private void FlowEEForwardingService_OnNodePlace(NodePlaceEventArgs eventArgs) + { + string nodeGuid = eventArgs.NodeGuid; + string containerNodeGuid = eventArgs.ContainerNodeGuid; + if (!TryGetControl(nodeGuid, out var nodeControl) + || !TryGetControl(containerNodeGuid, out var containerNodeControl)) + { + return; + } + if (containerNodeControl is not INodeContainerControl containerControl) + { + SereinEnv.WriteLine(InfoType.WARN, + $"节点[{nodeGuid}]无法放置于节点[{containerNodeGuid}]," + + $"因为后者并不实现 INodeContainerControl 接口"); + return; + } + nodeControl.PlaceToContainer(containerControl); // 放置在容器节点中 + } + #endregion + + #region 节点、画布相关的事件 + + /// + /// 节点移除 + /// + /// private void FlowEEForwardingService_OnNodeRemove(NodeRemoveEventArgs eventArgs) { - throw new NotImplementedException(); + if (!TryGetCanvas(eventArgs.CanvasGuid, out var nodeCanvas) || nodeCanvas is not IFlowCanvas api) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法移除节点,画布不存在。"); + return; + } + if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法移除节点,节点不存在。"); + return; + } + api.Remove(nodeControl); + } + /// + /// 节点创建 + /// + /// + private void FlowEEForwardingService_OnNodeCreate(NodeCreateEventArgs eventArgs) { - throw new NotImplementedException(); + #region 校验事件传入值 + var position = eventArgs.Position; + var cavnasGuid = eventArgs.CanvasGuid; + var nodeModel = eventArgs.NodeModel; + if (NodeControls.ContainsKey(nodeModel.Guid)) + { + SereinEnv.WriteLine(InfoType.WARN, $"创建节点时发生意外:节点Guid重复 - {nodeModel.Guid}"); + return; + } + + if (!flowEnvironment.NodeMVVMManagement.TryGetType(nodeModel.ControlType, out var nodeMVVM)) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,节点类型尚未注册。"); + return; + } + if (nodeMVVM.ControlType == null|| nodeMVVM.ViewModelType == null) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,UI类型尚未注册(请通过 NodeMVVMManagement.RegisterUI() 方法进行注册)。"); + return; + } + + if (!TryGetCanvas(cavnasGuid, out var nodeCanvas)) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法创建{nodeModel.ControlType}节点,不存在画布【{cavnasGuid}】。"); + return; + } + #endregion + + #region 创建控件 + + NodeControlBase nodeControl; + try + { + nodeControl = CreateNodeControl(nodeMVVM.ControlType, // 控件UI类型 + nodeMVVM.ViewModelType, // 控件VIewModel类型 + nodeModel, // 控件数据实体 + nodeCanvas); // 所在画布 + OnCreateNode.Invoke(nodeControl); // 创建节点 + } + catch (Exception ex) + { + SereinEnv.WriteLine(ex); + return; + } + + NodeControls.TryAdd(nodeControl.ViewModel.NodeModel.Guid, nodeControl); // 记录创建了的节点控件 + + #endregion + } + /// + /// 画布移除 + /// + /// private void FlowEEForwardingService_OnCanvasRemove(CanvasRemoveEventArgs eventArgs) { - OnRemoveFlowCanvasView.Invoke(eventArgs.CanvasGuid); + if (!TryGetCanvas(eventArgs.CanvasGuid, out var nodeCanvas)) + { + SereinEnv.WriteLine(InfoType.INFO, $"无法移除画布,画布不存在。"); + return; + } + OnRemoveFlowCanvasView.Invoke(nodeCanvas); } + /// + /// 画布创建 + /// + /// private void FlowEEForwardingService_OnCanvasCreate(CanvasCreateEventArgs eventArgs) { - var info = eventArgs.Model; var model = eventArgs.Model; - FlowCanvasView canvasView = new FlowCanvasView(); - canvasView.ViewModel.CanvasGuid = info.Guid; - canvasView.ViewModel.Model = model; - FlowCanvasViews.Add(info.Guid, canvasView); + var guid = model.Guid; + if (Canvass.ContainsKey(guid)) + { + SereinEnv.WriteLine(InfoType.WARN, $"创建画布时发生意外:节点Guid重复 - {guid}"); + return; + } + + FlowCanvasView canvasView = new FlowCanvasView(model); + //canvasView.ViewModel.Model = model; + //canvasView.ViewModel.CanvasGuid = model.Guid; + //canvasView.ViewModel.Name = model.Name; + //canvasView.SetBinding(model); + Canvass.Add(model.Guid, canvasView); OnCreateFlowCanvasView.Invoke(canvasView); // 传递给订阅者 } + + #endregion + /// + /// 创建节点控件 + /// + /// 节点控件视图控件类型 + /// 节点控件ViewModel类型 + /// 节点Model实例 + /// 节点所在画布 + /// + /// 无法创建节点控件 + private static NodeControlBase CreateNodeControl(Type controlType, Type viewModelType, NodeModelBase model, IFlowCanvas nodeCanvas) + { + if ((controlType is null) + || viewModelType is null + || model is null) + { + throw new Exception("无法创建节点控件"); + } + if (typeof(NodeControlBase).IsSubclassOf(controlType) || typeof(NodeControlViewModelBase).IsSubclassOf(viewModelType)) + { + throw new Exception("无法创建节点控件"); + } + + if (string.IsNullOrEmpty(model.Guid)) + { + model.Guid = Guid.NewGuid().ToString(); + } + + var viewModel = Activator.CreateInstance(viewModelType, [model]); + var controlObj = Activator.CreateInstance(controlType, [viewModel]); + if (controlObj is NodeControlBase nodeControl) + { + nodeControl.FlowCanvas = nodeCanvas; + return nodeControl; + } + else + { + throw new Exception("无法创建节点控件"); + } + } + + /// + /// 从Guid获取节点控件 + /// + /// + /// + /// + private bool TryGetControl(string nodeGuid, out NodeControlBase nodeControl) + { + nodeControl = null; + if (string.IsNullOrEmpty(nodeGuid)) + { + return false; + } + return NodeControls.TryGetValue(nodeGuid, out nodeControl); + } + + + /// + /// 从Guid获取画布视图 + /// + /// + /// + /// + private bool TryGetCanvas(string nodeGuid, out FlowCanvasView flowCanvas) + { + flowCanvas = null; + if (string.IsNullOrEmpty(nodeGuid)) + { + return false; + } + return Canvass.TryGetValue(nodeGuid, out flowCanvas); + } @@ -128,8 +397,8 @@ namespace Serein.Workbench.Services /// public void CreateFlowCanvas() { - int height = 1000; - int width = 600; + int width = 1200; + int height = 780; _ = Task.Run(async () => { var result = await flowEnvironment.CreateCanvasAsync("", width, height); @@ -146,7 +415,8 @@ namespace Serein.Workbench.Services { return; } - _ = flowEnvironment.RemoveCanvasAsync(CurrentSelectCanvas.ViewModel.CanvasGuid); + var model = ((FlowCanvasViewModel)CurrentSelectCanvas.DataContext).Model; + _ = flowEnvironment.RemoveCanvasAsync(model.Guid); } /// @@ -154,32 +424,31 @@ namespace Serein.Workbench.Services /// public void CreateNode() { - string canvasGuid = CurrentSelectCanvas.ViewModel.CanvasGuid; - NodeControlType? nodeType = CurrentNodeControlType; + var model = ((FlowCanvasViewModel)CurrentSelectCanvas.DataContext).Model; + + string canvasGuid = model.Guid; + NodeControlType nodeType = CurrentNodeControlType; PositionOfUI? position = CurrentMouseLocation; MethodDetailsInfo? methodDetailsInfo = CurrentDragMdInfo; - if (nodeType is null) - { - return; - } + if (position is null) { return; } - _ = flowEnvironment.CreateNodeAsync(canvasGuid, (NodeControlType)nodeType, position, methodDetailsInfo); + _ = flowEnvironment.CreateNodeAsync(canvasGuid, nodeType, position, methodDetailsInfo); } /// /// 向运行环境发出请求:移除节点 /// - public void RemoteNode() + public void RemoteNode(NodeControlBase nodeControl) { - NodeControlBase? node = CurrentSelectNodeControl; - if (node is null) + //NodeControlBase? node = CurrentSelectNodeControl; + if (nodeControl is null) { return; } - var model = node.ViewModel.NodeModel; + var model = nodeControl.ViewModel.NodeModel; if (model is null) { return; diff --git a/Workbench/Services/KeyEventService.cs b/Workbench/Services/KeyEventService.cs index ac6069f..a21718c 100644 --- a/Workbench/Services/KeyEventService.cs +++ b/Workbench/Services/KeyEventService.cs @@ -1,10 +1,11 @@ -/* + using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; namespace Serein.Workbench.Services { @@ -38,7 +39,6 @@ namespace Serein.Workbench.Services /// internal class KeyEventService : IKeyEventService { - /// /// 按键按下 /// @@ -77,4 +77,3 @@ namespace Serein.Workbench.Services } } } -*/ \ No newline at end of file diff --git a/Workbench/Services/NodeControlService.cs b/Workbench/Services/NodeControlService.cs index 23064ad..7654e57 100644 --- a/Workbench/Services/NodeControlService.cs +++ b/Workbench/Services/NodeControlService.cs @@ -18,14 +18,6 @@ using System.Threading.Tasks; using System.Windows.Controls; - - - -namespace Serein.Workbench.Api -{ - -} - namespace Serein.Workbench.Services { /// diff --git a/Workbench/Services/WorkbenchEventService.cs b/Workbench/Services/WorkbenchEventService.cs index 78ede6b..4469620 100644 --- a/Workbench/Services/WorkbenchEventService.cs +++ b/Workbench/Services/WorkbenchEventService.cs @@ -1,7 +1,11 @@ -using Serein.Library; +using Microsoft.Win32; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; +using Serein.Workbench.Api; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -50,25 +54,32 @@ namespace Serein.Workbench.Services { private readonly IFlowEnvironment flowEnvironment; + private readonly IFlowEEForwardingService flowEEForwardingService; + /// /// 管理工作台的事件 /// /// - public WorkbenchEventService(IFlowEnvironment flowEnvironment) + /// + public WorkbenchEventService(IFlowEnvironment flowEnvironment, IFlowEEForwardingService flowEEForwardingService) { this.flowEnvironment = flowEnvironment; - + this.flowEEForwardingService = flowEEForwardingService; + InitEvents(); } - private void SubscribeEvents() + private void InitEvents() { - + flowEEForwardingService.OnProjectSaving += SaveProjectToLocalFile; } + + /// /// 预览了某个方法信息(待创建) /// public event PreviewlMethodInfoHandler? OnPreviewlMethodInfo; + /// /// 预览依赖方法信息 /// @@ -84,6 +95,96 @@ namespace Serein.Workbench.Services { } + + /// + /// 保存项目数据到本地文件 + /// + /// + private void SaveProjectToLocalFile(ProjectSavingEventArgs e) + { + var project = e.ProjectData; + // 创建一个新的保存文件对话框 + SaveFileDialog saveFileDialog = new() + { + Filter = "DynamicNodeFlow Files (*.dnf)|*.dnf", + DefaultExt = "dnf", + FileName = "project.dnf" + // FileName = System.IO.Path.GetFileName(App.FileDataPath) + }; + + // 显示保存文件对话框 + bool? result = saveFileDialog.ShowDialog(); + // 如果用户选择了文件并点击了保存按钮 + if (result == false) + { + SereinEnv.WriteLine(InfoType.ERROR, "取消保存文件"); + return; + } + + var savePath = saveFileDialog.FileName; + string? librarySavePath = System.IO.Path.GetDirectoryName(savePath); + if (string.IsNullOrEmpty(librarySavePath)) + { + SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径"); + return; + } + + + Uri saveProjectFileUri = new Uri(savePath); + SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath); + for (int index = 0; index < project.Librarys.Length; index++) + { + NodeLibraryInfo? library = project.Librarys[index]; + string sourceFilePath = new Uri(library.FilePath).LocalPath; // 源文件夹 + string targetDir = System.IO.Path.Combine(librarySavePath, library.AssemblyName); // 目标文件夹 + if (!Path.Exists(targetDir)) + { + Directory.CreateDirectory(targetDir); + } + string targetFilePath = System.IO.Path.Combine(targetDir, library.FileName); // 目标文件夹 + + try + { + if (File.Exists(sourceFilePath)) + { + if (!File.Exists(targetFilePath)) + { + SereinEnv.WriteLine(InfoType.INFO, $"源文件路径 : {sourceFilePath}"); + SereinEnv.WriteLine(InfoType.INFO, $"目标路径 : {targetFilePath}"); + File.Copy(sourceFilePath, targetFilePath, true); + + } + else + { + SereinEnv.WriteLine(InfoType.WARN, $"目标路径已有类库文件: {targetFilePath}"); + } + } + else + { + SereinEnv.WriteLine(InfoType.WARN, $"源文件不存在 : {targetFilePath}"); + } + } + catch (IOException ex) + { + + SereinEnv.WriteLine(InfoType.ERROR, ex.Message); + } + var dirName = System.IO.Path.GetDirectoryName(targetFilePath); + if (!string.IsNullOrEmpty(dirName)) + { + var tmpUri2 = new Uri(targetFilePath); + var relativePath = saveProjectFileUri.MakeRelativeUri(tmpUri2).ToString(); // 转为类库的相对文件路径 + + //string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath); + project.Librarys[index].FilePath = relativePath; + } + + } + + JObject projectJsonData = JObject.FromObject(project); + File.WriteAllText(savePath, projectJsonData.ToString()); + + } } } diff --git a/Workbench/ViewModels/FlowCanvasViewModel.cs b/Workbench/ViewModels/FlowCanvasViewModel.cs index fb9ea9f..b8dc077 100644 --- a/Workbench/ViewModels/FlowCanvasViewModel.cs +++ b/Workbench/ViewModels/FlowCanvasViewModel.cs @@ -1,21 +1,27 @@ using CommunityToolkit.Mvvm.ComponentModel; using Serein.Library; +using Serein.Library.Api; +using Serein.Workbench.Api; using Serein.Workbench.Node.View; +using Serein.Workbench.Services; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows; +using System.Windows.Controls; namespace Serein.Workbench.ViewModels { public partial class FlowCanvasViewModel : ObservableObject { + /// /// 画布当前选中的节点 /// - public NodeControlBase CurrentSelectNodeControl { get; set; } - + public NodeControlBase CurrentSelectNode { get; set; } /// /// 正在创建节点方法调用关系 @@ -29,29 +35,13 @@ namespace Serein.Workbench.ViewModels [ObservableProperty] private bool _isConnectionArgSourceNode; - /// - /// 画布显示名称 - /// - [ObservableProperty] - private string _name; - - /// - /// 画布ID - /// - [ObservableProperty] - private string _canvasGuid; - /// /// 画布数据实体 /// [ObservableProperty] - private FlowCanvasModel _model; + private FlowCanvasDetails _model; - public FlowCanvasViewModel() - { - - } } } diff --git a/Workbench/ViewModels/FlowEditViewModel.cs b/Workbench/ViewModels/FlowEditViewModel.cs index 45fab07..029b23e 100644 --- a/Workbench/ViewModels/FlowEditViewModel.cs +++ b/Workbench/ViewModels/FlowEditViewModel.cs @@ -1,6 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using Serein.Workbench.Api; using Serein.Workbench.Models; +using Serein.Workbench.Node.View; using Serein.Workbench.Services; using Serein.Workbench.Views; using System; @@ -20,13 +22,17 @@ namespace Serein.Workbench.ViewModels /// public partial class FlowEditViewModel : ObservableObject { - public ObservableCollection Tabs { get; set; } = []; + public ObservableCollection CanvasTabs { get; set; } = []; public ICommand AddTabCommand { get; set; } public ICommand RemoveTabCommand { get; set; } public ICommand RenameTabCommand { get; set; } + + /// + /// 当前选择的画布 + /// [ObservableProperty] - private FlowCanvasModel _selectedTab; + private FlowEditorTabModel _selectedTab; private readonly FlowNodeService flowNodeService; @@ -34,39 +40,42 @@ namespace Serein.Workbench.ViewModels { this.flowNodeService = flowNodeService; AddTabCommand = new RelayCommand(AddTab); - RemoveTabCommand = new RelayCommand(RemoveTab, CanRemoveTab); + RemoveTabCommand = new RelayCommand(RemoveTab); - flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 运行环境创建了画布 - flowNodeService.OnRemoveFlowCanvasView += OnRemoveFlowCanvasView; // 运行环境移除了画布 + flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 创建了画布 + flowNodeService.OnRemoveFlowCanvasView += OnRemoveFlowCanvasView; // 移除了画布 this.PropertyChanged += OnPropertyChanged; + + } + + private void OnPropertyChanged(object? value, PropertyChangedEventArgs e) { - if (nameof(SelectedTab).Equals(e.PropertyName) && value is FlowCanvasModel model) - { - flowNodeService.CurrentSelectCanvas = model.Content; // 选中的视图发生改变 - } + if (this.SelectedTab is null) return; + flowNodeService.CurrentSelectCanvas = this.SelectedTab.Content; } #region 响应环境事件 - private void OnCreateFlowCanvasView(FlowCanvasView FlowCanvasView) + private void OnCreateFlowCanvasView(FlowCanvasView canvas) { - var model = new FlowCanvasModel { Content = FlowCanvasView, Name = FlowCanvasView.ViewModel.Name }; - Tabs.Add(model); + var model = new FlowEditorTabModel(canvas); + CanvasTabs.Add(model); + SelectedTab = model; } - private void OnRemoveFlowCanvasView(string canvasGuid) + private void OnRemoveFlowCanvasView(FlowCanvasView canvas) { - var tab = Tabs.FirstOrDefault(t => t.Content.ViewModel.Model.Guid.Equals(canvasGuid, StringComparison.OrdinalIgnoreCase)); + var tab = CanvasTabs.FirstOrDefault(c => c.Content.Guid.Equals(canvas.Guid)); if (tab is null) { return; } - Tabs.Remove(tab); - Tabs.Remove(SelectedTab); - if(Tabs.Count > 0 && Tabs[^1] is FlowCanvasModel view ) + CanvasTabs.Remove(tab); + + if (CanvasTabs.Count > 0 && CanvasTabs[^1] is FlowEditorTabModel c) { - SelectedTab = view; + SelectedTab = c; } } #endregion @@ -75,10 +84,9 @@ namespace Serein.Workbench.ViewModels private void AddTab() => flowNodeService.CreateFlowCanvas(); private void RemoveTab() { - if (Tabs.Count > 0 && SelectedTab != null) flowNodeService.RemoveFlowCanvas(); + if (CanvasTabs.Count > 0 && SelectedTab != null) flowNodeService.RemoveFlowCanvas(); } - private bool CanRemoveTab() => SelectedTab != null; @@ -86,12 +94,12 @@ namespace Serein.Workbench.ViewModels /// 进入编辑模式 /// /// - public void StartEditingTab(FlowCanvasModel tab) + public void StartEditingTab(FlowEditorTabModel tab) { if (tab != null) { tab.IsEditing = true; - OnPropertyChanged(nameof(Tabs)); // 刷新Tabs集合,以便更新UI + OnPropertyChanged(nameof(CanvasTabs)); // 刷新Tabs集合,以便更新UI } } @@ -100,13 +108,13 @@ namespace Serein.Workbench.ViewModels /// /// /// - public void EndEditingTab(FlowCanvasModel tab, string newName) + public void EndEditingTab(FlowEditorTabModel tab, string? newName = null) { if (tab != null) { tab.IsEditing = false; - tab.Name = newName; // 设置新名称 - OnPropertyChanged(nameof(Tabs)); // 刷新Tabs集合 + if(tab.Name != newName && !string.IsNullOrWhiteSpace(newName)) tab.Name = newName; // 名称合法时设置新名称 + OnPropertyChanged(nameof(CanvasTabs)); // 刷新Tabs集合 } } diff --git a/Workbench/ViewModels/FlowWorkbenchViewModel.cs b/Workbench/ViewModels/FlowWorkbenchViewModel.cs index 387e870..47fb4dd 100644 --- a/Workbench/ViewModels/FlowWorkbenchViewModel.cs +++ b/Workbench/ViewModels/FlowWorkbenchViewModel.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Serein.Workbench.Api; using Serein.Workbench.Models; +using Serein.Workbench.Services; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -13,10 +14,12 @@ namespace Serein.Workbench.ViewModels internal partial class FlowWorkbenchViewModel : ObservableObject { private readonly IFlowEEForwardingService flowEEForwardingService; + private readonly IWorkbenchEventService workbenchEventService; - public FlowWorkbenchViewModel(IFlowEEForwardingService flowEEForwardingService) + public FlowWorkbenchViewModel(IFlowEEForwardingService flowEEForwardingService, IWorkbenchEventService workbenchEventService) { this.flowEEForwardingService = flowEEForwardingService; + this.workbenchEventService = workbenchEventService; //flowEEForwardingService.OnDllLoad += FlowEEForwardingService_OnDllLoad; } } diff --git a/Workbench/ViewModels/MainMenuBarViewModel.cs b/Workbench/ViewModels/MainMenuBarViewModel.cs index 05f1d0e..6bf28ff 100644 --- a/Workbench/ViewModels/MainMenuBarViewModel.cs +++ b/Workbench/ViewModels/MainMenuBarViewModel.cs @@ -21,23 +21,33 @@ namespace Serein.Workbench.ViewModels /// 加载远程项目 /// public ICommand LoadRemoteProjectCommand { get; private set; } - + /// /// 增加流程图 /// public ICommand CreateFlowCanvasCommand { get; private set; } - /// - /// 增加流程图 + /// 移除流程图 /// public ICommand RemoteFlowCanvasCommand { get; private set; } - + /// + /// 运行当前画布流程 + /// + public ICommand StartCurrentCanvasFlowCommand { get; private set; } + + /// + /// 停止当前画布流程 + /// + public ICommand StopCurrentCanvasFlowCommand { get; private set; } + + + /// /// 打开环境输出窗口 /// public ICommand OpenEnvOutWindowCommand { get; private set; } - + /// /// 打开动态编译窗口 /// @@ -49,13 +59,33 @@ namespace Serein.Workbench.ViewModels { this.environment = environment; - SaveProjectCommand = new RelayCommand(SaveProject); + SaveProjectCommand = new RelayCommand(SaveProject); // 保存项目 + LoadLocalProjectCommand = new RelayCommand(LoadLocalProject); // 加载本地项目 + LoadRemoteProjectCommand = new RelayCommand(LoadRemoteProject); // 加载远程项目 + + CreateFlowCanvasCommand = new RelayCommand(CreateFlowCanvas); // 增加画布 + RemoteFlowCanvasCommand = new RelayCommand(RemoteFlowCanvas); // 移除画布 + + StartCurrentCanvasFlowCommand = new RelayCommand(StartCurrentCanvasFlow); // 运行当前流程 + StopCurrentCanvasFlowCommand = new RelayCommand(StopCurrentCanvasFlow); // 停止当前流程 + + OpenEnvOutWindowCommand = new RelayCommand(OpenEnvOutWindow); // 打开运行输出窗口 + OpenDynamicCompilerCommand = new RelayCommand(OpenDynamicCompiler); // 打开动态编译仓库窗口 } - public void SaveProject() - { - + private void SaveProject() { + environment.SaveProject(); // 保存项目 } + private void LoadLocalProject() { + //environment.LoadProject(); // 加载项目 + } + private void LoadRemoteProject() { } + private void CreateFlowCanvas() { } + private void RemoteFlowCanvas() { } + private void StartCurrentCanvasFlow() { } + private void StopCurrentCanvasFlow() { } + private void OpenDynamicCompiler() { } + private void OpenEnvOutWindow() { } } } diff --git a/Workbench/Views/FlowCanvasView.xaml b/Workbench/Views/FlowCanvasView.xaml index 152f67a..f92bd40 100644 --- a/Workbench/Views/FlowCanvasView.xaml +++ b/Workbench/Views/FlowCanvasView.xaml @@ -5,8 +5,11 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Views" xmlns:tool="clr-namespace:Serein.Workbench.Tool.Converters" + xmlns:vm="clr-namespace:Serein.Workbench.ViewModels" mc:Ignorable="d" - d:DesignHeight="450" d:DesignWidth="800"> + d:DesignHeight="450" + d:DesignWidth="800" + d:DataContext="{d:DesignInstance vm:FlowCanvasViewModel}"> @@ -16,22 +19,23 @@ + ClipToBounds="True"> + + x:Name="FlowChartCanvas" + Background="#E1FBEA" + AllowDrop="True" + Width="{Binding Model.Width, Mode=TwoWay}" + Height="{Binding Model.Height, Mode=TwoWay}" + MouseLeftButtonDown ="FlowChartCanvas_MouseLeftButtonDown" + MouseLeftButtonUp="FlowChartCanvas_MouseLeftButtonUp" + MouseDown="FlowChartCanvas_MouseDown" + MouseUp="FlowChartCanvas_MouseUp" + MouseMove="FlowChartCanvas_MouseMove" + MouseWheel="FlowChartCanvas_MouseWheel" + Drop="FlowChartCanvas_Drop" + DragOver="FlowChartCanvas_DragOver" + > /// FlowCanvasView.xaml 的交互逻辑 /// - public partial class FlowCanvasView : UserControl + public partial class FlowCanvasView : UserControl, IFlowCanvas { - public FlowCanvasViewModel ViewModel => this.DataContext as FlowCanvasViewModel; + + private readonly IFlowEnvironment flowEnvironment; + private readonly FlowNodeService flowNodeService; /// /// 存储所有的连接。考虑集成在运行环境中。 /// private List Connections { get; } = []; + 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; + + void IFlowCanvas.Remove(NodeControlBase nodeControl) + { + FlowChartCanvas.Dispatcher.Invoke(() => + { + FlowChartCanvas.Children.Remove(nodeControl); + }); + } + void IFlowCanvas.Add(NodeControlBase nodeControl) + { + FlowChartCanvas.Dispatcher.Invoke(() => + { + FlowChartCanvas.Children.Add(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 与画布相关的字段 /// @@ -70,8 +244,6 @@ namespace Serein.Workbench.Views /// private Point startSelectControolPoint; - - /// /// 组合变换容器 /// @@ -86,24 +258,125 @@ namespace Serein.Workbench.Views private readonly TranslateTransform translateTransform; #endregion - private IFlowEnvironment EnvDecorator; - public FlowCanvasView() + + #region 初始化 + + public FlowCanvasView(FlowCanvasDetails model) { var vm = App.GetService().FlowCanvasViewModel; + vm.Model = model; this.DataContext = vm; - EnvDecorator = App.GetService(); InitializeComponent(); - #region 缩放平移容器 + flowEnvironment = App.GetService(); + flowNodeService = App.GetService(); + + flowNodeService.OnCreateNode += OnCreateNode; + + + // 缩放平移容器 canvasTransformGroup = new TransformGroup(); scaleTransform = new ScaleTransform(); translateTransform = new TranslateTransform(); canvasTransformGroup.Children.Add(scaleTransform); canvasTransformGroup.Children.Add(translateTransform); FlowChartCanvas.RenderTransform = canvasTransformGroup; - #endregion + SetBinding(model); + } + + private void SetBinding(FlowCanvasDetails canvasModel) + { + Binding bindingScaleX = new(nameof(canvasModel.ScaleX)) { Source = canvasModel, Mode = BindingMode.TwoWay }; + BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleXProperty, bindingScaleX); + + Binding bindingScaleY = new(nameof(canvasModel.ScaleY)){ Source = canvasModel, Mode = BindingMode.TwoWay }; + BindingOperations.SetBinding(scaleTransform, ScaleTransform.ScaleYProperty, bindingScaleY); + + Binding bindingX = new(nameof(canvasModel.ViewX)) { Source = canvasModel, Mode = BindingMode.TwoWay }; + BindingOperations.SetBinding(translateTransform, TranslateTransform.XProperty, bindingX); + + Binding bindingY = new(nameof(canvasModel.ViewY)) { Source = canvasModel, Mode = BindingMode.TwoWay }; + BindingOperations.SetBinding(translateTransform, TranslateTransform.YProperty, bindingY); + + } + + /// + /// 当前画布创建了节点 + /// + /// + private void OnCreateNode(NodeControlBase nodeControl) + { + 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)) // 判断添加到区域容器 + { + // 通知运行环境调用加载节点子项的方法 + _ = flowEnvironment.PlaceNodeToContainerAsync(Guid, + nodeControl.ViewModel.NodeModel.Guid, // 待移动的节点 + regionControl.ViewModel.NodeModel.Guid); // 目标的容器节点 + } + else + { + // 并非添加在容器中,直接放置节点 + Api.Add(nodeControl); // 添加到对应的画布上 + ConfigureNodeEvents(nodeControl); // 添加了节点 + ConfigureContextMenu(nodeControl); // 添加右键菜单 + } + + } + + /// + /// 尝试判断是否为区域,如果是,将节点放置在区域中 + /// + /// + /// + /// 目标节点控件 + /// + private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, + PositionOfUI position, + out NodeControlBase targetNodeControl) + { + var point = new Point(position.X, position.Y); + HitTestResult hitTestResult = VisualTreeHelper.HitTest(FlowChartCanvas, point); + if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement) + { + // 准备放置条件表达式控件 + if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition) + { + ConditionRegionControl? conditionRegion = GetParentOfType(hitElement); + if (conditionRegion is not null) + { + targetNodeControl = conditionRegion; + //// 如果存在条件区域容器 + //conditionRegion.AddCondition(nodeControl); + return true; + } + } + + else + { + // 准备放置全局数据控件 + GlobalDataControl? globalDataControl = GetParentOfType(hitElement); + if (globalDataControl is not null) + { + targetNodeControl = globalDataControl; + return true; + } + } + } + targetNodeControl = null; + return false; + } + + #endregion + + /// /// 鼠标在画布移动。 /// 选择控件状态下,调整选择框大小 @@ -133,7 +406,6 @@ namespace Serein.Workbench.Views } - if (IsCanvasDragging && e.MiddleButton == MouseButtonState.Pressed) // 正在移动画布(按住中键) { Point currentMousePosition = e.GetPosition(this); @@ -186,11 +458,10 @@ namespace Serein.Workbench.Views { if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData) { - var canvasGuid = this.ViewModel.CanvasGuid; - Task.Run(async () => - { - await EnvDecorator.CreateNodeAsync(canvasGuid, nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象 - }); + flowNodeService.CurrentNodeControlType = nodeData.NodeControlType; // 设置基础节点类型 + flowNodeService.CurrentDragMdInfo = nodeData.MethodDetailsInfo; // 基础节点不需要参数信息 + flowNodeService.CurrentMouseLocation = position; // 设置当前鼠标为止 + flowNodeService.CreateNode(); // 创建来自DLL加载的方法节点 } } else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas)) @@ -209,12 +480,14 @@ namespace Serein.Workbench.Views }; if (nodeControlType != NodeControlType.None) { - var canvasGuid = this.ViewModel.CanvasGuid; - Task.Run(async () => - { - await EnvDecorator.CreateNodeAsync(canvasGuid, nodeControlType, position); // 创建基础节点对象 - }); + flowNodeService.CurrentNodeControlType = nodeControlType; // 设置基础节点类型 + flowNodeService.CurrentDragMdInfo = null; // 基础节点不需要参数信息 + flowNodeService.CurrentMouseLocation = position; // 设置当前鼠标为止 + flowNodeService.CreateNode(); // 创建基础节点 + } + + } } e.Handled = true; @@ -322,9 +595,9 @@ namespace Serein.Workbench.Views #region 方法调用关系创建 if (myData.Type == JunctionOfConnectionType.Invoke) { - var canvasGuid = this.ViewModel.CanvasGuid; + var canvasGuid = this.Guid; - await EnvDecorator.ConnectInvokeNodeAsync( + await flowEnvironment.ConnectInvokeNodeAsync( canvasGuid, myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid, @@ -346,9 +619,9 @@ namespace Serein.Workbench.Views { argIndex = argJunction2.ArgIndex; } - var canvasGuid = this.ViewModel.CanvasGuid; + var canvasGuid = this.Guid; - await EnvDecorator.ConnectArgSourceNodeAsync( + await flowEnvironment.ConnectArgSourceNodeAsync( canvasGuid, myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid, @@ -545,17 +818,10 @@ namespace Serein.Workbench.Views } - private void Test(double deltaX, double deltaY) - { - //Console.WriteLine((translateTransform.X, translateTransform.Y)); - //translateTransform.X += deltaX; - //translateTransform.Y += deltaY; - } - #endregion #endregion - + /// /// 完成选取操作 /// private void CompleteSelection() @@ -645,7 +911,6 @@ namespace Serein.Workbench.Views return menuItem; } - /// /// 选择范围配置 /// @@ -662,8 +927,8 @@ namespace Serein.Workbench.Views var guid = node?.ViewModel?.NodeModel?.Guid; if (!string.IsNullOrEmpty(guid)) { - var canvasGuid = this.ViewModel.CanvasGuid; - EnvDecorator.RemoveNodeAsync(canvasGuid, guid); + var canvasGuid = this.Guid; + flowEnvironment.RemoveNodeAsync(canvasGuid, guid); } } } @@ -673,6 +938,561 @@ namespace Serein.Workbench.Views // nodeControl.ContextMenu = contextMenu; } + #region 节点控件相关事件 + /// + /// 配置节点事件(移动,点击相关) + /// + /// + private void ConfigureNodeEvents(NodeControlBase nodeControl) + { + + nodeControl.MouseLeftButtonDown += Block_MouseLeftButtonDown; + nodeControl.MouseMove += Block_MouseMove; + nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp; + + } + private void EmptyNodeEvents(NodeControlBase nodeControl) + { + + nodeControl.MouseLeftButtonDown -= Block_MouseLeftButtonDown; + nodeControl.MouseMove -= Block_MouseMove; + nodeControl.MouseLeftButtonUp -= Block_MouseLeftButtonUp; + + } + + /// + /// 控件的鼠标左键按下事件,启动拖动操作。同时显示当前正在传递的数据。 + /// + private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (sender is NodeControlBase nodeControl) + { + //ChangeViewerObjOfNode(nodeControl); // 对象树 + if (nodeControl?.ViewModel?.NodeModel?.MethodDetails?.IsProtectionParameter == true) return; + IsControlDragging = true; + startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置 + ((UIElement)sender).CaptureMouse(); // 捕获鼠标 + e.Handled = true; // 防止事件传播影响其他控件 + } + } + + /// + /// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。 + /// + private void Block_MouseMove(object sender, MouseEventArgs e) + { + if (IsCanvasDragging) + return; + if (IsSelectControl) + return; + + if (IsControlDragging) // 如果正在拖动控件 + { + Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置 + + if (selectNodeControls.Count > 0 && sender is NodeControlBase nodeControlMain && selectNodeControls.Contains(nodeControlMain)) + { + // 进行批量移动 + // 获取旧位置 + var oldLeft = Canvas.GetLeft(nodeControlMain); + var oldTop = Canvas.GetTop(nodeControlMain); + + // 计算被选择控件的偏移量 + var deltaX = /*(int)*/(currentPosition.X - startControlDragPoint.X); + var deltaY = /*(int)*/(currentPosition.Y - startControlDragPoint.Y); + + // 移动被选择的控件 + var newLeft = oldLeft + deltaX; + var newTop = oldTop + deltaY; + + this.flowEnvironment.MoveNode(Guid, nodeControlMain.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点 + + // 计算控件实际移动的距离 + var actualDeltaX = newLeft - oldLeft; + var actualDeltaY = newTop - oldTop; + + // 移动其它选中的控件 + foreach (var nodeControl in selectNodeControls) + { + if (nodeControl != nodeControlMain) // 跳过已经移动的控件 + { + var otherNewLeft = Canvas.GetLeft(nodeControl) + actualDeltaX; + var otherNewTop = Canvas.GetTop(nodeControl) + actualDeltaY; + this.flowEnvironment.MoveNode(Guid, nodeControl.ViewModel.NodeModel.Guid, otherNewLeft, otherNewTop); // 移动节点 + } + } + + // 更新节点之间线的连接位置 + foreach (var nodeControl in selectNodeControls) + { + nodeControl.UpdateLocationConnections(); + } + } + else + { // 单个节点移动 + if (sender is not NodeControlBase nodeControl) + { + return; + } + double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量 + double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量 + double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距 + double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距 + this.flowEnvironment.MoveNode(Guid, nodeControl.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点 + nodeControl.UpdateLocationConnections(); + } + startControlDragPoint = currentPosition; // 更新起始点位置 + } + + } + + /// + /// 控件的鼠标左键松开事件,结束拖动操作 + /// + private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (IsControlDragging) + { + IsControlDragging = false; + ((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获 + + } + + //if (IsConnecting) + //{ + // var formNodeGuid = startConnectNodeControl?.ViewModel.NodeModel.Guid; + // var toNodeGuid = (sender as NodeControlBase)?.ViewModel.NodeModel.Guid; + // if (string.IsNullOrEmpty(formNodeGuid) || string.IsNullOrEmpty(toNodeGuid)) + // { + // return; + // } + // env.ConnectNodeAsync(formNodeGuid, toNodeGuid,0,0, currentConnectionType); + //} + //GlobalJunctionData.OK(); + } + + + + #endregion + + #region 配置节点右键菜单 + + /// + /// 配置节点右键菜单 + /// + /// + /// 任何情景下都尽量避免直接修改 ViewModel 中的 NodeModel 节点实体相关数据。 + /// 而是应该调用 FlowEnvironment 提供接口进行操作。 + /// 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。 + /// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。 + /// + private void ConfigureContextMenu(NodeControlBase nodeControl) + { + var canvasGuid = Guid; + var contextMenu = new ContextMenu(); + var nodeGuid = nodeControl.ViewModel?.NodeModel?.Guid; + #region 触发器节点 + + if (nodeControl.ViewModel?.NodeModel.ControlType == NodeControlType.Flipflop) + { + contextMenu.Items.Add(CreateMenuItem("启动触发器", (s, e) => + { + if (s is MenuItem menuItem) + { + if (menuItem.Header.ToString() == "启动触发器") + { + flowEnvironment.ActivateFlipflopNode(nodeGuid); + + menuItem.Header = "终结触发器"; + } + else + { + flowEnvironment.TerminateFlipflopNode(nodeGuid); + menuItem.Header = "启动触发器"; + + } + } + })); + } + + #endregion + + if (nodeControl.ViewModel?.NodeModel?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void)) + { + contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) => + { + DisplayReturnTypeTreeViewer(returnType); + })); + } + + + + contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid))); + contextMenu.Items.Add(CreateMenuItem("删除", async (s, e) => + { + var result = await flowEnvironment.RemoveNodeAsync(canvasGuid, nodeGuid); + })); + + #region 右键菜单功能 - 控件对齐 + + var AvoidMenu = new MenuItem(); + AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.Grouping); + })); + AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.Planning); + })); + AvoidMenu.Items.Add(CreateMenuItem("水平中心对齐", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.HorizontalCenter); + })); + AvoidMenu.Items.Add(CreateMenuItem("垂直中心对齐 ", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.VerticalCenter); + })); + + AvoidMenu.Items.Add(CreateMenuItem("垂直对齐时水平斜分布", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.Vertical); + })); + AvoidMenu.Items.Add(CreateMenuItem("水平对齐时垂直斜分布", (s, e) => + { + AlignControlsWithGrouping(selectNodeControls, AlignMode.Horizontal); + })); + + AvoidMenu.Header = "对齐"; + contextMenu.Items.Add(AvoidMenu); + + + #endregion + + nodeControl.ContextMenu = contextMenu; + } + + private void EmptyContextMenu(NodeControlBase nodeControl) + { + nodeControl.ContextMenu.Items.Clear(); + } + + /// + /// 查看返回类型(树形结构展开类型的成员) + /// + /// + private void DisplayReturnTypeTreeViewer(Type type) + { + try + { + var typeViewerWindow = new TypeViewerWindow + { + Type = type, + }; + typeViewerWindow.LoadTypeInformation(); + typeViewerWindow.Show(); + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, ex.ToString()); + } + } + #endregion + + #region 节点对齐 (有些小瑕疵) + + //public void UpdateConnectedLines() + //{ + // //foreach (var nodeControl in selectNodeControls) + // //{ + // // UpdateConnections(nodeControl); + // //} + // this.Dispatcher.Invoke(() => + // { + // foreach (var line in Connections) + // { + // line.AddOrRefreshLine(); // 节点完成对齐 + // } + // }); + + //} + + + #region Plan A 群组对齐 + + public void AlignControlsWithGrouping(List selectNodeControls, double proximityThreshold = 50, double spacing = 10) + { + if (selectNodeControls is null || selectNodeControls.Count < 2) + return; + + // 按照控件的相对位置进行分组 + var horizontalGroups = GroupByProximity(selectNodeControls, proximityThreshold, isHorizontal: true); + var verticalGroups = GroupByProximity(selectNodeControls, proximityThreshold, isHorizontal: false); + + // 对每个水平群组进行垂直对齐 + foreach (var group in horizontalGroups) + { + double avgY = group.Average(c => Canvas.GetTop(c)); // 计算Y坐标平均值 + foreach (var control in group) + { + Canvas.SetTop(control, avgY); // 对齐Y坐标 + } + } + + // 对每个垂直群组进行水平对齐 + foreach (var group in verticalGroups) + { + double avgX = group.Average(c => Canvas.GetLeft(c)); // 计算X坐标平均值 + foreach (var control in group) + { + Canvas.SetLeft(control, avgX); // 对齐X坐标 + } + } + } + + // 基于控件间的距离来分组,按水平或垂直方向 + private List> GroupByProximity(List controls, double proximityThreshold, bool isHorizontal) + { + var groups = new List>(); + + foreach (var control in controls) + { + bool addedToGroup = false; + + // 尝试将控件加入现有的群组 + foreach (var group in groups) + { + if (IsInProximity(group, control, proximityThreshold, isHorizontal)) + { + group.Add(control); + addedToGroup = true; + break; + } + } + + // 如果没有加入任何群组,创建新群组 + if (!addedToGroup) + { + groups.Add(new List { control }); + } + } + + return groups; + } + + // 判断控件是否接近某个群组 + private bool IsInProximity(List group, NodeControlBase control, double proximityThreshold, bool isHorizontal) + { + foreach (var existingControl in group) + { + double distance = isHorizontal + ? Math.Abs(Canvas.GetTop(existingControl) - Canvas.GetTop(control)) // 垂直方向的距离 + : Math.Abs(Canvas.GetLeft(existingControl) - Canvas.GetLeft(control)); // 水平方向的距离 + + if (distance <= proximityThreshold) + { + return true; + } + } + return false; + } + + #endregion + + #region Plan B 规划对齐 + public void AlignControlsWithDynamicProgramming(List selectNodeControls, double spacing = 10) + { + if (selectNodeControls is null || selectNodeControls.Count < 2) + return; + + int n = selectNodeControls.Count; + double[] dp = new double[n]; + int[] split = new int[n]; + + // 初始化动态规划数组 + for (int i = 1; i < n; i++) + { + dp[i] = double.MaxValue; + for (int j = 0; j < i; j++) + { + double cost = CalculateAlignmentCost(selectNodeControls, j, i, spacing); + if (dp[j] + cost < dp[i]) + { + dp[i] = dp[j] + cost; + split[i] = j; + } + } + } + + // 回溯找到最优的对齐方式 + AlignWithSplit(selectNodeControls, split, n - 1, spacing); + } + + // 计算从控件[j]到控件[i]的对齐代价,并考虑控件的大小和间距 + private double CalculateAlignmentCost(List controls, int start, int end, double spacing) + { + double totalWidth = 0; + double totalHeight = 0; + + for (int i = start; i <= end; i++) + { + totalWidth += controls[i].ActualWidth; + totalHeight += controls[i].ActualHeight; + } + + // 水平和垂直方向代价计算,包括控件大小和间距 + double widthCost = totalWidth + (end - start) * spacing; + double heightCost = totalHeight + (end - start) * spacing; + + // 返回较小的代价,表示更优的对齐方式 + return Math.Min(widthCost, heightCost); + } + + // 根据split数组调整控件位置,确保控件不重叠 + private void AlignWithSplit(List controls, int[] split, int end, double spacing) + { + if (end <= 0) + return; + + AlignWithSplit(controls, split, split[end], spacing); + + // 从split[end]到end的控件进行对齐操作 + double currentX = Canvas.GetLeft(controls[split[end]]); + double currentY = Canvas.GetTop(controls[split[end]]); + + for (int i = split[end] + 1; i <= end; i++) + { + // 水平或垂直对齐,确保控件之间有间距 + if (currentX + controls[i].ActualWidth + spacing <= Canvas.GetLeft(controls[end])) + { + Canvas.SetLeft(controls[i], currentX + controls[i].ActualWidth + spacing); + currentX += controls[i].ActualWidth + spacing; + } + else + { + Canvas.SetTop(controls[i], currentY + controls[i].ActualHeight + spacing); + currentY += controls[i].ActualHeight + spacing; + } + } + } + + #endregion + + public enum AlignMode + { + /// + /// 水平对齐 + /// + Horizontal, + /// + /// 垂直对齐 + /// + Vertical, + /// + /// 水平中心对齐 + /// + HorizontalCenter, + /// + /// 垂直中心对齐 + /// + VerticalCenter, + + /// + /// 规划对齐 + /// + Planning, + /// + /// 群组对齐 + /// + Grouping, + } + + + public void AlignControlsWithGrouping(List selectNodeControls, AlignMode alignMode, double proximityThreshold = 50, double spacing = 10) + { + if (selectNodeControls is null || selectNodeControls.Count < 2) + return; + + switch (alignMode) + { + case AlignMode.Horizontal: + AlignHorizontally(selectNodeControls, spacing);// AlignToCenter + break; + + case AlignMode.Vertical: + + AlignVertically(selectNodeControls, spacing); + break; + + case AlignMode.HorizontalCenter: + AlignToCenter(selectNodeControls, isHorizontal: false, spacing); + break; + + case AlignMode.VerticalCenter: + AlignToCenter(selectNodeControls, isHorizontal: true, spacing); + break; + + case AlignMode.Planning: + AlignControlsWithDynamicProgramming(selectNodeControls, spacing); + break; + case AlignMode.Grouping: + AlignControlsWithGrouping(selectNodeControls, proximityThreshold, spacing); + break; + } + + + } + + // 垂直对齐并避免重叠 + private void AlignHorizontally(List controls, double spacing) + { + double avgY = controls.Average(c => Canvas.GetTop(c)); // 计算Y坐标平均值 + double currentY = avgY; + + foreach (var control in controls.OrderBy(c => Canvas.GetTop(c))) // 按Y坐标排序对齐 + { + Canvas.SetTop(control, currentY); + currentY += control.ActualHeight + spacing; // 保证控件之间有足够的垂直间距 + } + } + + // 水平对齐并避免重叠 + private void AlignVertically(List controls, double spacing) + { + double avgX = controls.Average(c => Canvas.GetLeft(c)); // 计算X坐标平均值 + double currentX = avgX; + + foreach (var control in controls.OrderBy(c => Canvas.GetLeft(c))) // 按X坐标排序对齐 + { + Canvas.SetLeft(control, currentX); + currentX += control.ActualWidth + spacing; // 保证控件之间有足够的水平间距 + } + } + + // 按中心点对齐 + private void AlignToCenter(List controls, bool isHorizontal, double spacing) + { + double avgCenter = isHorizontal + ? controls.Average(c => Canvas.GetLeft(c) + c.ActualWidth / 2) // 水平中心点 + : controls.Average(c => Canvas.GetTop(c) + c.ActualHeight / 2); // 垂直中心点 + + foreach (var control in controls) + { + if (isHorizontal) + { + double left = avgCenter - control.ActualWidth / 2; + Canvas.SetLeft(control, left); + } + else + { + double top = avgCenter - control.ActualHeight / 2; + Canvas.SetTop(control, top); + } + } + } + + #endregion + + } diff --git a/Workbench/Views/FlowEditView.xaml b/Workbench/Views/FlowEditView.xaml index 881dc8c..cdfb90e 100644 --- a/Workbench/Views/FlowEditView.xaml +++ b/Workbench/Views/FlowEditView.xaml @@ -19,29 +19,30 @@ - + - + + KeyDown="TextBox_KeyDown" /> - + diff --git a/Workbench/Views/FlowEditView.xaml.cs b/Workbench/Views/FlowEditView.xaml.cs index 27c170b..b08663e 100644 --- a/Workbench/Views/FlowEditView.xaml.cs +++ b/Workbench/Views/FlowEditView.xaml.cs @@ -2,6 +2,7 @@ using Serein.Workbench.ViewModels; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -30,16 +31,18 @@ namespace Serein.Workbench.Views /// public partial class FlowEditView : UserControl { + public FlowEditView() { this.DataContext = App.GetService().FlowEditViewModel; InitializeComponent(); + } private void TextBlock_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { var textBlock = sender as TextBlock; - var tab = textBlock?.DataContext as FlowCanvasModel; + var tab = textBlock?.DataContext as FlowCanvasViewModel; if (tab != null) { DragDrop.DoDragDrop(textBlock, tab, DragDropEffects.Move); @@ -47,7 +50,7 @@ namespace Serein.Workbench.Views } private void TabControl_DragOver(object sender, DragEventArgs e) { - if (e.Data.GetDataPresent(typeof(FlowCanvasModel))) + if (e.Data.GetDataPresent(typeof(FlowCanvasViewModel))) { e.Effects = DragDropEffects.Move; } @@ -59,73 +62,83 @@ namespace Serein.Workbench.Views private void TabControl_Drop(object sender, DragEventArgs e) { - var sourceTab = e.Data.GetData(typeof(FlowCanvasModel)) as FlowCanvasModel; - var targetTab = (sender as TabControl)?.SelectedItem as FlowCanvasModel; + var sourceTab = e.Data.GetData(typeof(FlowEditorTabModel)) as FlowEditorTabModel; + var targetTab = (sender as TabControl)?.SelectedItem as FlowEditorTabModel; var viewModel = (FlowEditViewModel)this.DataContext; if (sourceTab != null && targetTab != null && sourceTab != targetTab) { - var sourceIndex = viewModel.Tabs.IndexOf(sourceTab); - var targetIndex = viewModel.Tabs.IndexOf(targetTab); + var sourceIndex = viewModel.CanvasTabs.IndexOf(sourceTab); + var targetIndex = viewModel.CanvasTabs.IndexOf(targetTab); // 删除源项并插入到目标位置 - viewModel.Tabs.Remove(sourceTab); - viewModel.Tabs.Insert(targetIndex, sourceTab); - + viewModel.CanvasTabs.Remove(sourceTab); + viewModel.CanvasTabs.Insert(targetIndex, sourceTab); + // 更新视图模型中的选中的Tab viewModel.SelectedTab = sourceTab; } } - + /// + /// 按下确认或回车 + /// + /// + /// private void TextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) { - if (e.Key == Key.Enter || e.Key == Key.Escape) { - var textBox = sender as TextBox; - var newName = textBox?.Text; - if (string.IsNullOrEmpty(newName)) + if (sender is TextBox textBox + && textBox.DataContext is FlowEditorTabModel tab + && DataContext is FlowEditViewModel viewModel) { + viewModel.EndEditingTab(tab, textBox.Text); // 确认新名称 return; } - var tab = textBox?.DataContext as FlowCanvasModel; - if (tab != null) - { - var viewModel = (FlowEditViewModel)this.DataContext; - viewModel.EndEditingTab(tab, newName); // 确认新名称 - } - } - } - - private void TextBox_LostFocus(object sender, RoutedEventArgs e) - { - var textBox = sender as TextBox; - var newName = textBox?.Text; - if (string.IsNullOrEmpty(newName)) - { - return; - } - var tab = textBox?.DataContext as FlowCanvasModel; - if (tab != null && tab.IsEditing) - { - var viewModel = (FlowEditViewModel)this.DataContext; - viewModel.EndEditingTab(tab, newName); // 确认新名称 } } + + /// + /// 双击tab进入编辑状态 + /// + /// + /// private void TextBlock_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { - var textBlock = sender as TextBlock; - var tab = textBlock?.DataContext as FlowCanvasModel; - if (tab != null) + if (sender is TextBlock textBlock + && textBlock.DataContext is FlowEditorTabModel tab + && DataContext is FlowEditViewModel viewModel) { - var viewModel = (FlowEditViewModel)this.DataContext; - viewModel.StartEditingTab(tab); + viewModel.StartEditingTab(tab); // 确认新名称 + return; } + + } } + + private FlowEditorTabModel lastTab; + + private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is TabControl tabControl + && tabControl.SelectedIndex > 0 + && DataContext is FlowEditViewModel viewModel + && viewModel.CanvasTabs[tabControl.SelectedIndex] is FlowEditorTabModel tab) + { + + viewModel.EndEditingTab(lastTab); // 确认新名称 + lastTab = tab; + return; + } + + } + + + } } diff --git a/Workbench/Views/MainMenuBarView.xaml b/Workbench/Views/MainMenuBarView.xaml index 2789a97..219e5da 100644 --- a/Workbench/Views/MainMenuBarView.xaml +++ b/Workbench/Views/MainMenuBarView.xaml @@ -4,12 +4,15 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Serein.Workbench.Views" + xmlns:vm="clr-namespace:Serein.Workbench.ViewModels" mc:Ignorable="d" - d:DesignHeight="70" d:DesignWidth="350"> + d:DesignHeight="70" d:DesignWidth="350" + d:DataContext="{d:DesignInstance vm:MainMenuBarViewModel}" + > - + diff --git a/Workbench/Views/MainMenuBarView.xaml.cs b/Workbench/Views/MainMenuBarView.xaml.cs index 82b89a1..2739575 100644 --- a/Workbench/Views/MainMenuBarView.xaml.cs +++ b/Workbench/Views/MainMenuBarView.xaml.cs @@ -23,7 +23,7 @@ namespace Serein.Workbench.Views { public MainMenuBarView() { - this.DataContext = App.GetService().MainViewModel; + this.DataContext = App.GetService().MainMenuBarViewModel; InitializeComponent(); } }