实现了多画布下,节点的复制粘贴功能

This commit is contained in:
fengjiayi
2025-05-27 18:32:40 +08:00
parent 7ad6041be6
commit 7848af0363
53 changed files with 1187 additions and 499 deletions

View File

@@ -1097,16 +1097,19 @@ namespace Serein.Library.Api
void SetUIContextOperation(UIContextOperation uiContextOperation);
/// <summary>
/// 开始运行
/// 开始运行流程
/// </summary>
Task<bool> StartFlowAsync();
/// <param name="canvasGuids">需要运行的流程Guid</param>
/// <returns></returns>
Task<bool> StartFlowAsync(string[] canvasGuids);
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
Task<bool> StartAsyncInSelectNode(string startNodeGuid);
Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid);
/// <summary>
/// 结束运行

View File

@@ -7,12 +7,5 @@ using System.Threading.Tasks;
namespace Serein.Library
{
/// <summary>
/// 拖拽创建节点使用的数据
/// </summary>
public class MoveNodeData
{
public NodeControlType NodeControlType { get; set; }
public MethodDetailsInfo MethodDetailsInfo { get; set; }
}
}

View File

@@ -101,10 +101,10 @@ namespace Serein.Library
// 生成参数列表
ParameterData[] parameterData = nodeModel.SaveParameterInfo();
NodeInfo nodeInfo = new NodeInfo
var nodeInfo = new NodeInfo
{
Guid = nodeModel.Guid,
CanvasGuid = nodeModel.CanvasGuid,
Guid = nodeModel.Guid,
AssemblyName = nodeModel.MethodDetails.AssemblyName,
MethodName = nodeModel.MethodDetails?.MethodName,
Label = nodeModel.MethodDetails?.MethodAnotherName,
@@ -130,10 +130,12 @@ namespace Serein.Library
/// <summary>
/// 从节点信息加载节点
/// </summary>
/// <param name="nodeModel"></param>
/// <param name="nodeInfo"></param>
/// <returns></returns>
public static void LoadInfo(this NodeModelBase nodeModel, NodeInfo nodeInfo)
{
nodeModel.CanvasGuid = nodeInfo.CanvasGuid;
nodeModel.Guid = nodeInfo.Guid;
nodeModel.Position = nodeInfo.Position ?? new PositionOfUI(0, 0);// 加载位置信息
var md = nodeModel.MethodDetails; // 当前节点的方法说明
@@ -191,6 +193,7 @@ namespace Serein.Library
/// <summary>
/// 开始执行
/// </summary>
/// <param name="nodeModel"></param>
/// <param name="context"></param>
/// <param name="token">流程运行</param>
/// <returns></returns>

View File

@@ -23,6 +23,7 @@ namespace Serein.Library
Env = env;
}
public IFlowEnvironment Env { get; }
/// <summary>
@@ -79,8 +80,6 @@ namespace Serein.Library
/// </summary>
private string _startNode;
}

View File

@@ -27,7 +27,7 @@ namespace Serein.Library
public string NodeType { get; set; }
/// <summary>
/// 方法说明
/// 方法别名
/// </summary>
public string MethodAnotherName { get; set; }

View File

@@ -336,6 +336,7 @@ namespace Serein.Library
_x = x; _y = y;
}
/// <summary>
/// 指示控件在画布的横向向方向上的位置
/// </summary>

View File

@@ -14,6 +14,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<LangVersion>latest</LangVersion>
<SatelliteResourceLanguages>no</SatelliteResourceLanguages>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>

View File

@@ -368,27 +368,87 @@ namespace Serein.NodeFlow.Env
/// 异步运行
/// </summary>
/// <returns></returns>
public async Task<bool> StartFlowAsync()
public async Task<bool> StartFlowAsync(string[] canvasGuids)
{
#region
HashSet<string> guids = new HashSet<string>();
bool isBreak = false;
foreach (var canvasGuid in canvasGuids)
{
if (guids.Contains(canvasGuid))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布重复,停止运行。{canvasGuid}");
isBreak = true;
}
if (!FlowCanvass.ContainsKey(canvasGuid))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{canvasGuid}");
isBreak = true;
}
var count = NodeModels.Values.Count(n => n.CanvasGuid.Equals(canvasGuid));
if(count == 0)
{
SereinEnv.WriteLine(InfoType.WARN, $"画布没有节点,停止运行。{canvasGuid}");
isBreak = true;
}
else
{
guids.Add(canvasGuid);
}
}
if (isBreak)
{
guids.Clear();
return false;
}
#endregion
#region
Dictionary<string, FlowTask> flowTasks = [];
foreach (var guid in guids)
{
if (!TryGetCanvasModel(guid, out var canvasModel))
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在,停止运行。{guid}");
return false;
}
var ft = new FlowTask();
ft.GetNodes = () => NodeModels.Values.Where(node => node.CanvasGuid.Equals(guid)).ToList();
var startNodeModel = NodeModels.GetValueOrDefault(canvasModel.StartNode);
if(startNodeModel is null)
{
SereinEnv.WriteLine(InfoType.WARN, $"画布不存在起始节点,将停止运行。{guid}");
return false;
}
ft.GetStartNode = () => startNodeModel;
flowTasks.Add(guid, ft);
}
#endregion
IOC.Reset();
IOC.Register<IScriptFlowApi, ScriptFlowApi>(); // 注册脚本接口
var flowTaskOptions = new FlowWorkOptions
{
Environment = this,
FlowContextPool = new ObjectPool<IDynamicContext>(() => new DynamicContext(this)),
//Nodes = NodeModels.Values.ToList(),
AutoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(),
InitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init),
Environment = this, // 流程
Flows = flowTasks,
FlowContextPool = new ObjectPool<IDynamicContext>(() => new DynamicContext(this)), // 上下文对象池
AutoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(), // 需要自动实例化的类型
InitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init),
LoadMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Loading),
ExitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Exit),
};
flowTaskManagement = new FlowWorkManagement(flowTaskOptions);
var cts = new CancellationTokenSource();
try
{
var t =await flowTaskManagement.RunAsync(cts.Token);
var t = await flowTaskManagement.RunAsync(cts.Token);
}
catch (Exception ex)
{
@@ -405,12 +465,13 @@ namespace Serein.NodeFlow.Env
}
/// <summary>
/// 从选定节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
public async Task<bool> StartAsyncInSelectNode(string startNodeGuid)
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
{
if (flowTaskManagement is null)
@@ -425,12 +486,6 @@ namespace Serein.NodeFlow.Env
{
return false;
}
//var getExp = "@get .DebugSetting.IsEnable";
//var getExpResult1 = SerinExpressionEvaluator.Evaluate(getExp, nodeModel,out _);
//var setExp = "@set .DebugSetting.IsEnable = false";
//SerinExpressionEvaluator.Evaluate(setExp, nodeModel,out _);
//var getExpResult2 = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _);
await flowTaskManagement.StartFlowInSelectNodeAsync(this, nodeModel);
return true;
}
@@ -559,9 +614,22 @@ namespace Serein.NodeFlow.Env
LoadLibrary(dllFilePath); // 加载项目文件时加载对应的程序集
}
_ = Task.Run( async () =>
{
{
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
LoadCanvas(canvasInfo);
}
await LoadNodeInfosAsync(projectData.Nodes.ToList()); // 加载节点信息
// 加载画布
foreach (var canvasInfo in projectData.Canvass)
{
await SetStartNodeAsync(canvasInfo.Guid, canvasInfo.StartNode); // 设置起始节点
}
//await SetStartNodeAsync("", projectData.StartNode); // 设置起始节点
});
@@ -779,20 +847,31 @@ namespace Serein.NodeFlow.Env
/// <returns></returns>
public async Task<FlowCanvasDetailsInfo> CreateCanvasAsync(string canvasName, int width, int height)
{
var model = new FlowCanvasDetails(this)
var info = new FlowCanvasDetailsInfo()
{
Guid = Guid.NewGuid().ToString(),
Height = height,
Width = width,
ViewX = 0,
ViewY = 0,
ScaleY = 1,
ScaleX = 1,
Name = !string.IsNullOrWhiteSpace(canvasName) ? canvasName : $"流程图{_addCanvasCount++}",
};
var model = LoadCanvas(info);
return info;
}
private FlowCanvasDetails LoadCanvas(FlowCanvasDetailsInfo info)
{
var model = new FlowCanvasDetails(this);
model.LoadInfo(info);
FlowCanvass.Add(model.Guid, model);
await UIContextOperation.InvokeAsync(() =>
UIContextOperation.InvokeAsync(() =>
{
OnCanvasCreate.Invoke(new CanvasCreateEventArgs(model));
});
var info = model.ToInfo();
return info;
return model;
}
/// <summary>
@@ -957,7 +1036,7 @@ namespace Serein.NodeFlow.Env
#region
foreach (var toNode in NodeModels.Values)
{
var canvasGuid = toNode.Guid;
var canvasGuid = toNode.CanvasGuid;
if (toNode.MethodDetails.ParameterDetailss == null)
{
continue;
@@ -1727,7 +1806,7 @@ namespace Serein.NodeFlow.Env
private bool TryAddNode(NodeModelBase nodeModel)
{
nodeModel.Guid ??= Guid.NewGuid().ToString();
NodeModels[nodeModel.Guid] = nodeModel;
NodeModels.TryAdd(nodeModel.Guid, nodeModel);
// 如果是触发器,则需要添加到专属集合中
if (nodeModel is SingleFlipflopNode flipflopNode)
@@ -1944,32 +2023,22 @@ namespace Serein.NodeFlow.Env
/// <summary>
/// 更改起点节点
/// </summary>
/// <param name="newStartNode"></param>
/// <param name="oldStartNode"></param>
/// <param name="cavnasModel">节点所在的画布</param>
/// <param name="newStartNode">起始节点</param>
private void SetStartNode(FlowCanvasDetails cavnasModel, NodeModelBase newStartNode)
{
var oldNodeGuid = cavnasModel.StartNode;
/*if(TryGetNodeModel(oldNodeGuid, out var newStartNodeModel))
{
newStartNode.IsStart = false;
}*/
cavnasModel.StartNode = newStartNode.Guid;
//newStartNode.IsStart = true;
UIContextOperation?.Invoke(() => OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(cavnasModel.Guid, oldNodeGuid, cavnasModel.StartNode)));
//if (OperatingSystem.IsWindows())
//{
// }
}
///// <summary>
///// 输出内容
///// </summary>
///// <param name="msg"></param>
//private void Output(string msg)
//{
// if (OperatingSystem.IsWindows())
// {
// UIContextOperation?.Invoke(() => OnEnvOut?.Invoke(msg));
// }
//}
/// <summary>
/// 向容器登记缓存的持久化实例
/// </summary>

View File

@@ -263,7 +263,7 @@ namespace Serein.NodeFlow.Env
/// <param name="toNodeJunctionType">目标节点控制点</param>
/// <param name="invokeType">决定了方法执行后的后继行为</param>
public async Task<bool> ConnectInvokeNodeAsync(string canvasGuid,
string fromNodeGuid,
string fromNodeGuid,
string toNodeGuid,
JunctionType fromNodeJunctionType,
JunctionType toNodeJunctionType,
@@ -518,14 +518,16 @@ namespace Serein.NodeFlow.Env
return await currentFlowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid);
}
public async Task<bool> StartFlowAsync()
public async Task<bool> StartFlowAsync(string[] canvasGuids)
{
return await currentFlowEnvironment.StartFlowAsync();
return await currentFlowEnvironment.StartFlowAsync(canvasGuids);
}
public async Task<bool> StartAsyncInSelectNode(string startNodeGuid)
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
{
return await currentFlowEnvironment.StartAsyncInSelectNode(startNodeGuid);
return await currentFlowEnvironment.StartFlowFromSelectNodeAsync(startNodeGuid);
}

View File

@@ -173,10 +173,10 @@ namespace Serein.NodeFlow.Env
/// </summary>
/// <returns></returns>
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlow)]
private async Task<object> StartAsync()
private async Task<object> StartAsync(string[] canvasGuid)
{
var uiContextOperation = environment.IOC.Get<UIContextOperation>();
var state = await environment.StartFlowAsync();
var state = await environment.StartFlowAsync(canvasGuid);
return new
{
state = state,
@@ -191,7 +191,7 @@ namespace Serein.NodeFlow.Env
[AutoSocketHandle(ThemeValue = EnvMsgTheme.StartFlowInSelectNode)]
private async Task<object> StartAsyncInSelectNode(string nodeGuid)
{
var state = await environment.StartAsyncInSelectNode(nodeGuid);
var state = await environment.StartFlowFromSelectNodeAsync(nodeGuid);
return new
{
state = state,

View File

@@ -420,18 +420,22 @@ namespace Serein.NodeFlow.Env
/// 启动远程环境的流程
/// </summary>
/// <returns></returns>
public async Task<bool> StartFlowAsync()
public async Task<bool> StartFlowAsync(string[] canvasGuids)
{
// 远程环境下不需要UI上下文
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.StartFlow);
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.StartFlow, new
{
canvasGuids
});
return result;
}
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="startNodeGuid"></param>
/// <returns></returns>
public async Task<bool> StartAsyncInSelectNode(string startNodeGuid)
public async Task<bool> StartFlowFromSelectNodeAsync(string startNodeGuid)
{
var result = await msgClient.SendAndWaitDataAsync<bool>(EnvMsgTheme.StartFlowInSelectNode, new
{

View File

@@ -43,7 +43,7 @@ namespace Serein.NodeFlow
}
/// <summary>
/// 初始化
/// 初始化
/// </summary>
/// <returns></returns>
public async Task<bool> RunAsync(CancellationToken token)
@@ -85,22 +85,19 @@ namespace Serein.NodeFlow
var flowNodes = flow.GetNodes();
// 找到流程的起始节点,开始运行
NodeModelBase? startNode = flowNodes.FirstOrDefault(node => node.IsStart);
if (startNode is null)
{
return false;
}
NodeModelBase startNode = flow.GetStartNode();
// 是否后台运行当前画布流程
if (flow.IsTaskAsync)
{
_ = Task.Run(async () => await CallStartNode(startNode));
_ = Task.Run(async () => await CallStartNode(startNode), token); // 后台调用流程中的触发器
}
else
{
await CallStartNode(startNode);
}
var flipflopTasks = CallFlipflopNode(flow); // 获取所有触发器异步任务
_ = Task.Run(async () => await flipflopTasks); // 后台调用流程中的触发器
_ = Task.Run(async () => await CallFlipflopNode(flow), token); // 后台调用流程中的触发器
}
// 等待流程运行完成
@@ -226,7 +223,7 @@ namespace Serein.NodeFlow
return isSuccessful;
}
private Task CallFlipflopNode(FlowTask flow)
private async Task CallFlipflopNode(FlowTask flow)
{
var env = WorkOptions.Environment;
var flipflopNodes = flow.GetNodes().Where(item => item is SingleFlipflopNode node
@@ -242,9 +239,8 @@ namespace Serein.NodeFlow
{
await RunGlobalFlipflopAsync(env, node); // 启动流程时启动全局触发器
});
Task.WhenAll(tasks);
await Task.WhenAll(tasks);
}
return Task.CompletedTask;
}
/// <summary>
@@ -257,6 +253,7 @@ namespace Serein.NodeFlow
var pool = WorkOptions.FlowContextPool;
var token = WorkOptions.CancellationTokenSource.Token;
var context = pool.Allocate();
context.Reset();
await startNode.StartFlowAsync(context, token);
context.Exit();
pool.Free(context);

View File

@@ -46,7 +46,7 @@ namespace Serein.NodeFlow
/// <summary>
/// 上下文线程池
/// </summary>
public Serein.Library.Utils.ObjectPool<IDynamicContext> FlowContextPool { get; set; }
public Serein.Library.Utils.ObjectPool<IDynamicContext> FlowContextPool { get; set; }
/// <summary>
/// 每个画布需要启用的节点

View File

@@ -69,6 +69,11 @@ namespace Serein.NodeFlow.Tool
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
if (!Directory.Exists(path))
{
SereinEnv.WriteLine(InfoType.ERROR, $"尝试加载Dll时失败路径不存在。{path}");
return;
}
foreach (var file in Directory.GetFiles(path, "*.dll"))
{
LoadWindowsLibrarie(file);

View File

@@ -23,6 +23,11 @@ namespace Serein.Workbench.Api
/// </summary>
string Name { get; }
/// <summary>
/// 数据
/// </summary>
FlowCanvasDetails Model { get; }
/// <summary>
/// 移除节点
/// </summary>

View File

@@ -5,8 +5,6 @@
xmlns:view="clr-namespace:Serein.Workbench.Views"
StartupUri="Views/FlowWorkbenchView.xaml"
Startup="Application_Startup">
<!--StartupUri="Views/FlowWorkbenchView.xaml"-->
<!--StartupUri="MainWindow.xaml"-->
<Application.Resources>
<ResourceDictionary>

View File

@@ -10,6 +10,7 @@ using Serein.Workbench.ViewModels;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
namespace Serein.Workbench
@@ -38,10 +39,9 @@ namespace Serein.Workbench
public static void AddWorkbenchServices(this IServiceCollection collection)
{
collection.AddSingleton<IFlowEEForwardingService, FlowEEForwardingService>(); // 流程事件管理
collection.AddSingleton<IKeyEventService, KeyEventService>();// 按键事件管理
collection.AddSingleton<IWorkbenchEventService, WorkbenchEventService>(); // 流程事件管理
collection.AddSingleton<FlowNodeService>(); // 节点操作管理
// collection.AddSingleton<IKeyEventService, KeyEventService>(); // 按键事件管理
//collection.AddSingleton<FlowNodeControlService>(); // 流程节点控件管理
}
@@ -87,27 +87,30 @@ namespace Serein.Workbench
{
return ServiceProvider?.GetService<T>() ?? throw new NullReferenceException();
}
public App()
{
var collection = new ServiceCollection();
collection.AddWorkbenchServices();
collection.AddFlowServices();
collection.AddViewModelServices();
var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口
App.ServiceProvider = services;
#if DEBUG
_ = this.LoadLocalProjectAsync();
#endif
}
private async Task LoadLocalProjectAsync()
{
await Task.Delay(500);
#if DEBUG
if (1 == 10)
if (1 ==1)
{
// 这里是测试代码,可以删除
string filePath;
@@ -115,12 +118,17 @@ namespace Serein.Workbench
filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\banyunqi\project.dnf";
filePath = @"F:\临时\project\project.dnf";
filePath = @"F:\TempFile\flow\qrcode\project.dnf";
//filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\test.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
var dir = Path.GetDirectoryName(filePath);
App.GetService<IFlowEnvironment>().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath);
filePath = @"F:\TempFile\flow\temp\project.dnf";
if (File.Exists(filePath))
{
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
App.FlowProjectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;//
var dir = Path.GetDirectoryName(filePath);
App.GetService<IFlowEnvironment>().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath);
}
}
#endif
}

View File

@@ -11,16 +11,16 @@
<converter:CountToVisibilityConverter x:Key="CountToVisibilityConverter"/>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Nodes}"
Visibility="{Binding Nodes, Converter={StaticResource CountToVisibilityConverter}}"
Background="{Binding BackgroundColor}">
<ListBox ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
Visibility="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource CountToVisibilityConverter}}"
Background="{Binding Background, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="2">
<Grid Margin="2" MouseLeftButtonDown="Grid_MouseLeftButtonDown" MouseMove="Grid_MouseMove" DataContext="{Binding}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"></TextBlock>
<TextBlock Text="{Binding AnotherName}" Margin="4,0,0,0"></TextBlock>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"></TextBlock>
<TextBlock Text="{Binding NodeType}"/>
<TextBlock Text="{Binding MethodAnotherName}" Margin="4,0,0,0"/>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"/>
</StackPanel>
</Grid>
</DataTemplate>

View File

@@ -1,4 +1,5 @@
using Serein.Library;
using Serein.Library.Utils;
using Serein.Workbench.Models;
using System;
using System.Collections;
@@ -20,10 +21,19 @@ using System.Windows.Shapes;
namespace Serein.Workbench.Customs
{
public class Test: DependencyObject
/// <summary>
/// 拖拽创建节点类型
/// </summary>
public static class MouseNodeType
{
/// <summary>
/// 创建来自DLL的节点
/// </summary>
public static string CreateDllNodeInCanvas { get; } = nameof(CreateDllNodeInCanvas);
/// <summary>
/// 创建基础节点
/// </summary>
public static string CreateBaseNodeInCanvas { get; } = nameof(CreateBaseNodeInCanvas);
}
/// <summary>
@@ -35,66 +45,90 @@ namespace Serein.Workbench.Customs
public FlowMethodInfoListBox()
{
this.DataContext = this;
InitializeComponent();
}
public IEnumerable<FlowLibraryMethodDetailsInfo> Nodes
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(IEnumerable), typeof(FlowMethodInfoListBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable<FlowLibraryMethodDetailsInfo>)GetValue(NodesProperty); }
set { SetValue(NodesProperty, value); }
get => (IEnumerable)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
//public ItemCollection Items
//{
// get
// {
// return (ItemCollection)GetValue(ItemsProperty);
// }
// set
// {
// SetValue(ItemsProperty, value);
// }
//}
public static readonly DependencyProperty BackgroundProperty =
DependencyProperty.Register(nameof(Background), typeof(Brush), typeof(FlowMethodInfoListBox), new PropertyMetadata(Brushes.Transparent));
public static readonly DependencyProperty NodesProperty = DependencyProperty.Register("NodesProperty", typeof(IEnumerable<FlowLibraryMethodDetailsInfo>), typeof(FlowMethodInfoListBox));
//public int TurnValue
//{
// get
// {
// return (int)GetValue(TurnValueProperty);
// }
// set
// {
// SetValue(TurnValueProperty, value);
// }
//}
// public static readonly DependencyProperty NodesProperty = DependencyProperty.Register(nameof(Nodes), typeof(IEnumerable<FlowLibraryMethodDetailsInfo>), typeof(FlowMethodInfoListBox), new PropertyMetadata(null));
public Brush BackgroundColor
public Brush Background
{
get { return (Brush)GetValue(BackgroundColorProperty); }
set { SetValue(BackgroundColorProperty, value); }
get => (Brush)GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
public static readonly DependencyProperty BackgroundColorProperty =
DependencyProperty.Register(nameof(BackgroundColor), typeof(Brush), typeof(FlowMethodInfoListBox), new PropertyMetadata(Brushes.White));
/// <summary>
/// 存储拖拽开始时的鼠标位置
/// </summary>
private Point _dragStartPoint;
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 记录鼠标按下时的位置
_dragStartPoint = e.GetPosition(null);
}
private void Grid_MouseMove(object sender, MouseEventArgs e)
{
// 获取当前鼠标位置
Point mousePos = e.GetPosition(null);
// 计算鼠标移动的距离
Vector diff = _dragStartPoint - mousePos;
// 判断是否符合拖拽的最小距离要求
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
// 获取触发事件的 TextBlock
if (sender is Grid grid && grid.DataContext is MethodDetailsInfo mdInfo)
{
if (!EnumHelper.TryConvertEnum<Library.NodeType>(mdInfo.NodeType, out var nodeType))
{
return;
}
MoveNodeModel moveNodeModel = new MoveNodeModel()
{
NodeControlType = nodeType switch
{
NodeType.Action => NodeControlType.Action,
NodeType.Flipflop => NodeControlType.Flipflop,
NodeType.UI => NodeControlType.UI,
_ => NodeControlType.None,
},
MethodDetailsInfo = mdInfo
};
//MoveNodeData moveNodeData = new MoveNodeData
//{
// MethodDetailsInfo = mdInfo,
//};
if (moveNodeModel.NodeControlType == NodeControlType.None)
{
return;
}
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeModel);
DragDrop.DoDragDrop(grid, dragData, DragDropEffects.Move);
}
}
}
}
}

View File

@@ -17,6 +17,10 @@ namespace Serein.Workbench
/// </summary>
public partial class LogWindow : Window
{
private static LogWindow instance = new LogWindow();
public static LogWindow Instance => instance;
private StringBuilder logBuffer = new StringBuilder();
private int logUpdateInterval = 200; // 批量更新的时间间隔(毫秒)
private Timer logUpdateTimer;

View File

@@ -9,6 +9,7 @@ using Serein.NodeFlow.Env;
using Serein.NodeFlow.Tool;
using Serein.Workbench.Api;
using Serein.Workbench.Extension;
using Serein.Workbench.Models;
using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
@@ -27,20 +28,7 @@ using DataObject = System.Windows.DataObject;
namespace Serein.Workbench
{
/// <summary>
/// 拖拽创建节点类型
/// </summary>
public static class MouseNodeType
{
/// <summary>
/// 创建来自DLL的节点
/// </summary>
public static string CreateDllNodeInCanvas { get; } = nameof(CreateDllNodeInCanvas);
/// <summary>
/// 创建基础节点
/// </summary>
public static string CreateBaseNodeInCanvas { get; } = nameof(CreateBaseNodeInCanvas);
}
@@ -1257,6 +1245,7 @@ namespace Serein.Workbench
#endregion
#region DLL文件到左侧功能区
/// <summary>
/// 当拖动文件到窗口时触发加载DLL文件
/// </summary>
@@ -1308,6 +1297,7 @@ namespace Serein.Workbench
/// </summary>
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
dynamic GlobalJunctionData = "";
var myData = GlobalJunctionData.MyGlobalConnectingData;
if (myData.IsCreateing && e.LeftButton == MouseButtonState.Pressed)
{
@@ -1406,12 +1396,12 @@ namespace Serein.Workbench
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
{
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeModel moveModel)
{
Task.Run(async () =>
{
await EnvDecorator.CreateNodeAsync("MainCanvas", nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象
await EnvDecorator.CreateNodeAsync("MainCanvas", moveModel.NodeControlType, position, moveModel.MethodDetailsInfo); // 创建DLL文件的节点对象
});
}
}
@@ -1704,7 +1694,8 @@ namespace Serein.Workbench
Mouse.OverrideCursor = null; // 恢复视觉效果
ViewModel.IsConnectionArgSourceNode = false;
ViewModel.IsConnectionInvokeNode = false;
GlobalJunctionData.OK();
dynamic GlobalJunctionData = "";
}
#region
@@ -1908,6 +1899,8 @@ namespace Serein.Workbench
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
dynamic GlobalJunctionData = "";
if (GlobalJunctionData.MyGlobalConnectingData.IsCreateing)
{
return;
@@ -1963,6 +1956,7 @@ namespace Serein.Workbench
FlowChartCanvas.ReleaseMouseCapture();
}
dynamic GlobalJunctionData = "";
// 创建连线
if (GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
{
@@ -2818,8 +2812,9 @@ public class FlowLibrary
CancelSelectNode();
EndConnection();
}
dynamic GlobalJunctionData = "";
if(GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
if (GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
{
if(myData.Type == JunctionOfConnectionType.Invoke)
{

View File

@@ -9,21 +9,10 @@ using System.Threading.Tasks;
namespace Serein.Workbench.Models
{
public partial class FlowLibraryMethodDetailsInfo(MethodDetailsInfo info): ObservableObject
{
[ObservableProperty]
private string _anotherName = info.MethodAnotherName;
[ObservableProperty]
private string _assmblyName = info.AssemblyName;
[ObservableProperty]
private string _methodName = info.MethodName;
[ObservableProperty]
private string _nodeType = info.NodeType;
}
/// <summary>
/// 依赖信息
/// </summary>
internal partial class FlowLibraryInfo : ObservableObject
{
[ObservableProperty]
@@ -33,12 +22,11 @@ namespace Serein.Workbench.Models
private string _libraryName;
[ObservableProperty]
private ObservableCollection<FlowLibraryMethodDetailsInfo> _methodInfo;
private ObservableCollection<MethodDetailsInfo> _methodInfo;
public List<FlowLibraryMethodDetailsInfo> ActionNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Action.ToString()).ToList(); set { } }
public List<FlowLibraryMethodDetailsInfo> FlipflopNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Flipflop.ToString()).ToList(); set { } }
public List<FlowLibraryMethodDetailsInfo> UINodes { get => MethodInfo.Where(x => x.NodeType == NodeType.UI.ToString()).ToList(); set { } }
public List<MethodDetailsInfo> ActionNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Action.ToString()).ToList(); set { } }
public List<MethodDetailsInfo> FlipflopNodes { get => MethodInfo.Where(x => x.NodeType == NodeType.Flipflop.ToString()).ToList(); set { } }
public List<MethodDetailsInfo> UINodes { get => MethodInfo.Where(x => x.NodeType == NodeType.UI.ToString()).ToList(); set { } }
}
}

View File

@@ -0,0 +1,18 @@
using Serein.Library;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.Workbench.Models
{
/// <summary>
/// 拖拽创建节点使用的数据
/// </summary>
internal class MoveNodeModel
{
public NodeControlType NodeControlType { get; set; }
public MethodDetailsInfo MethodDetailsInfo { get; set; }
}
}

View File

@@ -12,9 +12,15 @@ using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Windows.Documents;
using System.Threading;
using Serein.Workbench.Services;
using Serein.Workbench.Tool;
namespace Serein.Workbench.Node.View
{
/// <summary>
/// 控制带的拓展方法
/// </summary>
internal static class MyUIFunc
{
public static Pen CreateAndFreezePen()
@@ -31,10 +37,11 @@ namespace Serein.Workbench.Node.View
}
}
/// <summary>
/// 入参控件
/// </summary>
public class ParamsArgControl: Shape
{
public ParamsArgControl()
{
this.MouseDown += ParamsArg_OnMouseDown; // 增加或删除
@@ -159,8 +166,10 @@ namespace Serein.Workbench.Node.View
public abstract class JunctionControlBase : Shape
{
private readonly FlowNodeService flowNodeService;
protected JunctionControlBase()
{
flowNodeService = App.GetService<FlowNodeService>();
this.Width = 25;
this.Height = 20;
this.MouseDown += JunctionControlBase_MouseDown;
@@ -229,7 +238,7 @@ namespace Serein.Workbench.Node.View
{
if(_isMouseOver != value)
{
GlobalJunctionData.MyGlobalConnectingData.CurrentJunction = this;
flowNodeService.ConnectingData.CurrentJunction = this;
_isMouseOver = value;
InvalidateVisual();
}
@@ -252,22 +261,22 @@ namespace Serein.Workbench.Node.View
/// <returns></returns>
protected Brush GetBackgrounp()
{
var myData = GlobalJunctionData.MyGlobalConnectingData;
if(!myData.IsCreateing)
var cd = flowNodeService.ConnectingData;
if(!cd.IsCreateing)
{
return Brushes.Transparent;
}
if (IsMouseOver)
{
if (myData.IsCanConnected)
if (cd.IsCanConnected)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
if (cd.Type == JunctionOfConnectionType.Invoke)
{
return myData.ConnectionInvokeType.ToLineColor();
return cd.ConnectionInvokeType.ToLineColor();
}
else
{
return myData.ConnectionArgSourceType.ToLineColor();
return cd.ConnectionArgSourceType.ToLineColor();
}
}
else
@@ -320,15 +329,15 @@ namespace Serein.Workbench.Node.View
{
if (e.LeftButton == MouseButtonState.Pressed)
{
var canvas = MainWindow.GetParentOfType<Canvas>(this);
var canvas = WpfFuncTool.GetParentOfType<Canvas>(this);
if (canvas != null)
{
var myData = GlobalJunctionData.MyGlobalConnectingData;
myData.Reset();
myData.IsCreateing = true; // 表示开始连接
myData.StartJunction = this;
myData.CurrentJunction = this;
myData.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
var cd = flowNodeService.ConnectingData;
cd.Reset();
cd.IsCreateing = true; // 表示开始连接
cd.StartJunction = this;
cd.CurrentJunction = this;
cd.StartPoint = this.TranslatePoint(new Point(this.Width / 2, this.Height / 2), canvas);
var junctionOfConnectionType = this.JunctionType.ToConnectyionType();
ConnectionLineShape bezierLine; // 类别
@@ -346,13 +355,13 @@ namespace Serein.Workbench.Node.View
return;
}
bezierLine = new ConnectionLineShape(LineType.Bezier,
myData.StartPoint,
myData.StartPoint,
cd.StartPoint,
cd.StartPoint,
brushColor,
isTop: true); // 绘制临时的线
Mouse.OverrideCursor = Cursors.Cross; // 设置鼠标为正在创建连线
myData.MyLine = new MyLine(canvas, bezierLine);
cd.MyLine = new MyLine(canvas, bezierLine);
}
}
e.Handled = true;

View File

@@ -123,7 +123,7 @@ namespace Serein.Workbench.Node.View
}
/// <summary>
/// 重置
/// 重置连线状态
/// </summary>
public void Reset()
{
@@ -139,7 +139,7 @@ namespace Serein.Workbench.Node.View
}
public static class GlobalJunctionData
public static class GlobalJunctionData1
{
//private static ConnectingData? myGlobalData;
//private static object _lockObj = new object();

View File

@@ -1,6 +1,7 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Extension;
using Serein.Workbench.Tool;
using System;
using System.Net;
using System.Windows;
@@ -224,8 +225,8 @@ namespace Serein.Workbench.Node.View
private void ConfigureLineContextMenu()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => Remote()));
contextMenu.Items.Add(MainWindow.CreateMenuItem("于父节点调用顺序中置顶", (s, e) => Topping()));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除连线", (s, e) => Remote()));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("于父节点调用顺序中置顶", (s, e) => Topping()));
BezierLine.ContextMenu = contextMenu;
}

View File

@@ -140,26 +140,26 @@ namespace Serein.Workbench.Node.View
return;
}
MoveNodeData moveNodeData = new MoveNodeData
{
//MoveNodeData moveNodeData = new MoveNodeData
//{
NodeControlType = nodeType switch
{
NodeType.Action => NodeControlType.Action,
NodeType.Flipflop => NodeControlType.Flipflop,
NodeType.UI => NodeControlType.UI,
_ => NodeControlType.None,
},
MethodDetailsInfo = mdInfo,
};
if(moveNodeData.NodeControlType == NodeControlType.None)
{
return;
}
// NodeControlType = nodeType switch
// {
// NodeType.Action => NodeControlType.Action,
// NodeType.Flipflop => NodeControlType.Flipflop,
// NodeType.UI => NodeControlType.UI,
// _ => NodeControlType.None,
// },
// MethodDetailsInfo = mdInfo,
//};
//if(moveNodeData.NodeControlType == NodeControlType.None)
//{
// return;
//}
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData);
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
//// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
//DataObject dragData = new DataObject(MouseNodeType.CreateDllNodeInCanvas, moveNodeData);
//DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}

View File

@@ -24,6 +24,8 @@
</ItemGroup>
<ItemGroup>
<Compile Remove="MainWindow.xaml.cs" />
<Compile Remove="MainWindowViewModel.cs" />
<Compile Remove="Node\FlipflopRegionControl.xaml.cs" />
<Compile Remove="Node\INodeContainerControl.cs" />
<Compile Remove="Node\Junction\NodeJunctionViewBase.cs" />
@@ -38,6 +40,7 @@
</ItemGroup>
<ItemGroup>
<Page Remove="MainWindow.xaml" />
<Page Remove="Node\FlipflopRegionControl.xaml" />
<Page Remove="Node\View\ActionRegionControl.xaml" />
<Page Remove="Themes\ConditionControl.xaml" />

View File

@@ -26,17 +26,21 @@ namespace Serein.Workbench.Services
/// </summary>
private readonly IFlowEnvironment flowEnvironment;
private readonly IFlowEnvironmentEvent flowEnvironmentEvent;
private readonly UIContextOperation uIContextOperation;
/// <summary>
/// 转发流程运行环境各个事件的实现类
/// </summary>
/// <param name="flowEnvironment"></param>
/// <param name="flowEnvironmentEvent"></param>
/// <param name="uIContextOperation"></param>
public FlowEEForwardingService(IFlowEnvironment flowEnvironment,
IFlowEnvironmentEvent flowEnvironmentEvent)
IFlowEnvironmentEvent flowEnvironmentEvent,
UIContextOperation uIContextOperation)
{
this.flowEnvironment = flowEnvironment;
this.flowEnvironmentEvent = flowEnvironmentEvent;
this.uIContextOperation = uIContextOperation;
InitFlowEnvironmentEvent();
}
@@ -185,7 +189,10 @@ namespace Serein.Workbench.Services
/// <param name="value"></param>
private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value)
{
//LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
uIContextOperation.Invoke(() =>
{
OnEnvOut?.Invoke(type, value);
});
}
/// <summary>

View File

@@ -1,4 +1,6 @@
using Serein.Library;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
using Serein.Workbench.Api;
using Serein.Workbench.Node;
@@ -6,7 +8,10 @@ using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.ViewModels;
using Serein.Workbench.Views;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Serein.Workbench.Services
{
@@ -34,7 +39,6 @@ namespace Serein.Workbench.Services
#endregion
#region
/// <summary>
/// 当前查看的画布
@@ -51,7 +55,6 @@ namespace Serein.Workbench.Services
/// </summary>
public NodeControlType CurrentNodeControlType { get; set; } = NodeControlType.None;
/// <summary>
/// 当前鼠标位置
/// </summary>
@@ -61,6 +64,12 @@ namespace Serein.Workbench.Services
/// 当前选中的节点
/// </summary>
public NodeControlBase? CurrentSelectNodeControl { get; set; }
/// <summary>
/// 连接数据
/// </summary>
public ConnectingData ConnectingData { get; } = new ConnectingData();
#endregion
/// <summary>
@@ -72,6 +81,11 @@ namespace Serein.Workbench.Services
/// </summary>
public NodeControlBase? ConnectionEndNode { get; set; }
/// <summary>
/// 当前所有画布
/// </summary>
public FlowCanvasView[] FlowCanvass => Canvass.Select(c => c.Value).ToArray();
/// <summary>
/// 记录流程画布
/// </summary>
@@ -134,7 +148,27 @@ namespace Serein.Workbench.Services
flowEEForwardingService.OnNodeTakeOut += FlowEEForwardingService_OnNodeTakeOut; ; // 节点从容器中取出
flowEEForwardingService.OnNodeConnectChange += FlowEEForwardingService_OnNodeConnectChange; // 节点连接状态改变事件
flowEEForwardingService.OnStartNodeChange += FlowEEForwardingService_OnStartNodeChange; // 画布起始节点改变
}
private void FlowEEForwardingService_OnStartNodeChange(StartNodeChangeEventArgs eventArgs)
{
string oldNodeGuid = eventArgs.OldNodeGuid;
string newNodeGuid = eventArgs.NewNodeGuid;
if (!TryGetControl(newNodeGuid, out var newStartNodeControl)) return;
if (!string.IsNullOrEmpty(oldNodeGuid))
{
if (!TryGetControl(oldNodeGuid, out var oldStartNodeControl)) return;
oldStartNodeControl.BorderBrush = Brushes.Black;
oldStartNodeControl.BorderThickness = new Thickness(0);
}
newStartNodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
newStartNodeControl.BorderThickness = new Thickness(2);
var node = newStartNodeControl?.ViewModel?.NodeModel;
}
private void FlowEEForwardingService_OnNodeConnectChange(NodeConnectChangeEventArgs e)
@@ -387,7 +421,167 @@ namespace Serein.Workbench.Services
return Canvass.TryGetValue(nodeGuid, out flowCanvas);
}
#region
/// <summary>
/// 从节点信息转换为Json文本数据
/// </summary>
public string CpoyNodeInfo(List<NodeModelBase> dictSelection)
{
// 遍历当前已选节点
foreach (var node in dictSelection.ToArray())
{
if (node.ChildrenNode.Count == 0)
{
continue;
}
// 遍历这些节点的子节点,添加过来
foreach (var childNode in node.ChildrenNode)
{
dictSelection.Add(childNode);
}
}
var nodeInfos = dictSelection.Select(item => item.ToInfo());
JObject json = new JObject()
{
["nodes"] = JArray.FromObject(nodeInfos)
};
var jsonText = json.ToString();
try
{
SereinEnv.WriteLine(InfoType.INFO, $"复制已选节点({dictSelection.Count}个)");
return jsonText;
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.ERROR, $"复制失败:{ex.Message}");
return string.Empty;
}
}
/// <summary>
/// 从Json中加载节点
/// </summary>
/// <param name="canvasGuid">需要加载在哪个画布上</param>
/// <param name="jsonText">文本内容</param>
/// <param name="positionOfUI">需要加载的位置</param>
public void PasteNodeInfo(string canvasGuid, string jsonText, PositionOfUI positionOfUI)
{
try
{
List<NodeInfo>? nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(jsonText);
if (nodes is not null && nodes.Count != 0)
{
}
if (nodes is null || nodes.Count < 0)
{
return;
}
#region
Dictionary<string, string> guids = new Dictionary<string, string>(); // 记录 Guid
// 遍历当前节点
foreach (var node in nodes.ToArray())
{
if (NodeControls.ContainsKey(node.Guid) && !guids.ContainsKey(node.Guid))
{
// 如果是没出现过、且在当前记录中重复的Guid则记录并新增对应的映射。
guids.TryAdd(node.Guid, Guid.NewGuid().ToString());
}
else
{
// 出现过的Guid说明重复添加了。应该不会走到这。
continue;
}
if (node.ChildNodeGuids is null)
{
continue; // 跳过没有子节点的节点
}
// 遍历这些节点的子节点,获得完整的已选节点信息
foreach (var childNodeGuid in node.ChildNodeGuids)
{
if (NodeControls.ContainsKey(node.Guid) && !NodeControls.ContainsKey(node.Guid))
{
// 当前Guid并不重复跳过替换
continue;
}
if (!guids.ContainsKey(childNodeGuid))
{
// 如果是没出现过的Guid则记录并新增对应的映射。
guids.TryAdd(node.Guid, Guid.NewGuid().ToString());
}
if (!string.IsNullOrEmpty(childNodeGuid)
&& NodeControls.TryGetValue(childNodeGuid, out var nodeControl))
{
var newNodeInfo = nodeControl.ViewModel.NodeModel.ToInfo();
nodes.Add(newNodeInfo);
}
}
}
// Guid去重
StringBuilder sb = new StringBuilder(jsonText);
foreach (var kv in guids)
{
sb.Replace(kv.Key, kv.Value);
}
string result = sb.ToString();
/*var replacer = new GuidReplacer();
foreach (var kv in guids)
{
replacer.AddReplacement(kv.Key, kv.Value);
}
string result = replacer.Replace(jsonText);*/
//SereinEnv.WriteLine(InfoType.ERROR, result);
nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(result);
if (nodes is null || nodes.Count < 0)
{
return;
}
#endregion
// 获取第一个节点的原始位置
var index0NodeX = nodes[0].Position.X;
var index0NodeY = nodes[0].Position.Y;
// 计算所有节点相对于第一个节点的偏移量
foreach (var node in nodes)
{
node.CanvasGuid = canvasGuid; // 替换画布Guid
var offsetX = node.Position.X - index0NodeX;
var offsetY = node.Position.Y - index0NodeY;
// 根据鼠标位置平移节点
node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY);
}
_ = flowEnvironment.LoadNodeInfosAsync(nodes);
}
catch (Exception ex)
{
//SereinEnv.WriteLine(InfoType.ERROR, $"粘贴节点时发生异常:{ex}");
}
// SereinEnv.WriteLine(InfoType.INFO, $"剪贴板文本内容: {clipboardText}");
}
#endregion
#region

View File

@@ -6,19 +6,20 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using static System.Windows.Forms.AxHost;
namespace Serein.Workbench.Services
{
delegate void KeyDownEventHandler(Key key);
delegate void KeyUpEventHandler(Key key);
public delegate void KeyDownEventHandler(Key key);
public delegate void KeyUpEventHandler(Key key);
/// <summary>
/// 全局事件服务
/// 全局按键事件服务
/// </summary>
internal interface IKeyEventService
public interface IKeyEventService
{
event KeyDownEventHandler KeyDown;
event KeyUpEventHandler KeyUp;
event KeyDownEventHandler OnKeyDown;
event KeyUpEventHandler OnKeyUp;
/// <summary>
/// 获取某个按键状态
@@ -26,27 +27,34 @@ namespace Serein.Workbench.Services
/// <param name="key"></param>
/// <returns></returns>
bool GetKeyState(Key key);
/// <summary>
/// 设置某个按键的状态
/// 按下了某个键
/// </summary>
/// <param name="key"></param>
/// <param name="state"></param>
void SetKeyState(Key key, bool statestate);
void KeyDown(Key key);
/// <summary>
/// 抬起了某个键
/// </summary>
/// <param name="key"></param>
void KeyUp(Key key);
}
/// <summary>
/// 管理按键状态
/// </summary>
internal class KeyEventService : IKeyEventService
public class KeyEventService : IKeyEventService
{
/// <summary>
/// 按键按下
/// </summary>
public event KeyDownEventHandler KeyDown;
public event KeyDownEventHandler OnKeyDown;
/// <summary>
/// 按键松开
/// </summary>
public event KeyUpEventHandler KeyUp;
public event KeyUpEventHandler OnKeyUp;
public KeyEventService()
{
@@ -62,18 +70,23 @@ namespace Serein.Workbench.Services
{
return KeysState[(int)key];
}
public void SetKeyState(Key key, bool state)
public void KeyDown(Key key)
{
if (state)
{
KeyDown?.Invoke(key);
}
else
{
KeyUp?.Invoke(key);
}
//Debug.WriteLine($"按键事件:{key} - {state}");
KeysState[(int)key] = state;
KeysState[(int)key] = true;
OnKeyDown?.Invoke(key);
Debug.WriteLine($"按键按下事件:{key}");
}
public void KeyUp(Key key)
{
KeysState[(int)key] = false;
OnKeyUp?.Invoke(key);
Debug.WriteLine($"按键抬起事件:{key}");
}
}
}

View File

@@ -1,55 +0,0 @@
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Env;
using Serein.Workbench.Api;
using Serein.Workbench.Avalonia.Api;
using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace Serein.Workbench.Services
{
/// <summary>
/// 节点操作相关服务
/// </summary>
internal class NodeControlService
{
public NodeControlService(IFlowEnvironment flowEnvironment,
IFlowEEForwardingService feefService)
{
/* this.flowEnvironment = flowEnvironment;
this.feefService = feefService;
feefService.OnNodeCreate += FeefService_OnNodeCreate; // 订阅运行环境创建节点事件
feefService.OnNodeConnectChange += FeefService_OnNodeConnectChange; // 订阅运行环境连接了节点事件
// 手动加载项目
_ = Task.Run(async delegate
{
await Task.Delay(1000);
var flowEnvironment = new FlowEnvironment();// App.GetService<IFlowEnvironment>();
var filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\project.dnf";
string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
var projectData = JsonConvert.DeserializeObject<SereinProjectData>(content);
var projectDfilePath = System.IO.Path.GetDirectoryName(filePath)!;
flowEnvironment.LoadProject(new FlowEnvInfo { Project = projectData }, projectDfilePath);
}, CancellationToken.None);*/
}
}
}

View File

@@ -7,6 +7,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
@@ -71,9 +72,15 @@ namespace Serein.Workbench.Services
private void InitEvents()
{
flowEEForwardingService.OnProjectSaving += SaveProjectToLocalFile;
flowEEForwardingService.OnEnvOut += FlowEEForwardingService_OnEnvOut;
}
private void FlowEEForwardingService_OnEnvOut(InfoType type, string value)
{
LogWindow.Instance.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
}
/// <summary>
/// 预览了某个方法信息(待创建)
@@ -103,6 +110,8 @@ namespace Serein.Workbench.Services
private void SaveProjectToLocalFile(ProjectSavingEventArgs e)
{
var project = e.ProjectData;
#region
// 创建一个新的保存文件对话框
SaveFileDialog saveFileDialog = new()
{
@@ -128,8 +137,10 @@ namespace Serein.Workbench.Services
SereinEnv.WriteLine(InfoType.ERROR, "保存项目DLL时返回了意外的文件保存路径");
return;
}
#endregion
#region Dll输出到指定路径
Uri saveProjectFileUri = new Uri(savePath);
SereinEnv.WriteLine(InfoType.INFO, "项目文件保存路径:" + savePath);
for (int index = 0; index < project.Librarys.Length; index++)
@@ -180,12 +191,104 @@ namespace Serein.Workbench.Services
}
}
#endregion
#region
JObject projectJsonData = JObject.FromObject(project);
File.WriteAllText(savePath, projectJsonData.ToString());
#endregion
}
}
}
#region net运行时
/*
1. 扫描目录并计算哈希
string[] directories = new[] { "path1", "path2" };
var fileHashMap = new Dictionary<string, List<string>>(); // hash -> List<full paths>
foreach (var dir in directories)
{
foreach (var file in Directory.EnumerateFiles(dir, "*.*", SearchOption.AllDirectories))
{
using var stream = File.OpenRead(file);
using var sha = SHA256.Create();
var hash = Convert.ToHexString(sha.ComputeHash(stream));
if (!fileHashMap.ContainsKey(hash))
fileHashMap[hash] = new List<string>();
fileHashMap[hash].Add(file);
}
}
2. 将重复文件压缩并保存
string archiveDir = "compressed_output";
Directory.CreateDirectory(archiveDir);
var manifest = new List<FileRecord>();
foreach (var kvp in fileHashMap.Where(kvp => kvp.Value.Count > 1))
{
var hash = kvp.Key;
var originalFile = kvp.Value[0];
var archivePath = Path.Combine(archiveDir, $"{hash}.gz");
using (var input = File.OpenRead(originalFile))
using (var output = File.Create(archivePath))
using (var gzip = new GZipStream(output, CompressionLevel.Optimal))
{
input.CopyTo(gzip);
}
manifest.Add(new FileRecord
{
Hash = hash,
ArchiveFile = $"{hash}.gz",
OriginalPaths = kvp.Value
});
}
3. 生成清单文件JSON
public class FileRecord
{
public string Hash { get; set; }
public string ArchiveFile { get; set; }
public List<string> OriginalPaths { get; set; }
}
File.WriteAllText("manifest.json", JsonSerializer.Serialize(manifest, new JsonSerializerOptions { WriteIndented = true }));
4. 根据清单还原原始文件结构
var manifestJson = File.ReadAllText("manifest.json");
var manifest = JsonSerializer.Deserialize<List<FileRecord>>(manifestJson);
foreach (var record in manifest)
{
var archivePath = Path.Combine("compressed_output", record.ArchiveFile);
foreach (var path in record.OriginalPaths)
{
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
using var input = File.OpenRead(archivePath);
using var gzip = new GZipStream(input, CompressionMode.Decompress);
using var output = File.Create(path);
gzip.CopyTo(output);
}
}
*/
#endregion

View File

@@ -1,6 +1,7 @@
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.Workbench.Tool;
using System.Windows;
using System.Windows.Controls;
@@ -137,11 +138,11 @@ namespace Serein.Workbench.Themes
treeViewItem.Expanded += TreeViewItem_Expanded;
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem("从此节点执行", async (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("从此节点执行", async (s, e) =>
{
try
{
await flowEnvironment.StartAsyncInSelectNode(tmpNodeTreeModel.RootNode.Guid);
await flowEnvironment.StartFlowFromSelectNodeAsync(tmpNodeTreeModel.RootNode.Guid);
}
catch (Exception ex)
{
@@ -149,7 +150,7 @@ namespace Serein.Workbench.Themes
return;
}
}));
contextMenu.Items.Add(MainWindow.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocated(tmpNodeTreeModel.RootNode.Guid)));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("定位", (s, e) => flowEnvironment.NodeLocated(tmpNodeTreeModel.RootNode.Guid)));
treeViewItem.ContextMenu = contextMenu;
treeViewItem.Margin = new Thickness(-20, 0, 0, 0);

View File

@@ -1,6 +1,7 @@
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Utils.SereinExpression;
using Serein.Workbench.Tool;
using System;
using System.Collections;
using System.Collections.Generic;
@@ -294,7 +295,7 @@ namespace Serein.Workbench.Themes
// 配置右键菜单
var contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"表达式", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem($"表达式", (s, e) =>
{
ExpressionTextBox.Text = subPath; // 获取表达式

View File

@@ -1,4 +1,5 @@
using System;
using Serein.Workbench.Tool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -165,7 +166,7 @@ namespace Serein.Workbench.Themes
{
isChange = true;
contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem($"取值表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = "@Get " + fullPath;
@@ -181,7 +182,7 @@ namespace Serein.Workbench.Themes
{
isChange = true;
contextMenu = new ContextMenu();
contextMenu.Items.Add(MainWindow.CreateMenuItem($"取值表达式", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem($"取值表达式", (s, e) =>
{
string fullPath = GetNodeFullPath(memberNode);
string copyValue = "@Get " + fullPath;

View File

@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows;
namespace Serein.Workbench.Tool
{
internal static class WpfFuncTool
{ /// <summary>
/// 创建菜单子项
/// </summary>
/// <param name="header"></param>
/// <param name="handler"></param>
/// <returns></returns>
public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
{
var menuItem = new MenuItem { Header = header };
menuItem.Click += handler;
return menuItem;
}
/// <summary>
/// 穿透元素获取区域容器
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="element"></param>
/// <returns></returns>
public static T? GetParentOfType<T>(DependencyObject element) where T : DependencyObject
{
while (element != null)
{
if (element is T e)
{
return e;
}
element = VisualTreeHelper.GetParent(element);
}
return null;
}
}
}

View File

@@ -19,9 +19,9 @@ namespace Serein.Workbench.ViewModels
{
/// <summary>
/// 画布当前选中的节点
/// 画布当前的节点
/// </summary>
public NodeControlBase CurrentSelectNode { get; set; }
public Dictionary<string, NodeControlBase> NodeControls { get; set; } = [];
/// <summary>
/// 正在创建节点方法调用关系

View File

@@ -23,9 +23,6 @@ namespace Serein.Workbench.ViewModels
public partial class FlowEditViewModel : ObservableObject
{
public ObservableCollection<FlowEditorTabModel> CanvasTabs { get; set; } = [];
public ICommand AddTabCommand { get; set; }
public ICommand RemoveTabCommand { get; set; }
public ICommand RenameTabCommand { get; set; }
/// <summary>
@@ -39,8 +36,7 @@ namespace Serein.Workbench.ViewModels
public FlowEditViewModel(FlowNodeService flowNodeService)
{
this.flowNodeService = flowNodeService;
AddTabCommand = new RelayCommand(AddTab);
RemoveTabCommand = new RelayCommand(RemoveTab);
flowNodeService.OnCreateFlowCanvasView += OnCreateFlowCanvasView; // 创建了画布
flowNodeService.OnRemoveFlowCanvasView += OnRemoveFlowCanvasView; // 移除了画布
@@ -81,13 +77,6 @@ namespace Serein.Workbench.ViewModels
#endregion
private void AddTab() => flowNodeService.CreateFlowCanvas();
private void RemoveTab()
{
if (CanvasTabs.Count > 0 && SelectedTab != null) flowNodeService.RemoveFlowCanvas();
}
/// <summary>

View File

@@ -1,5 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Env;
using Serein.Workbench.Api;
using Serein.Workbench.Models;
using Serein.Workbench.Services;
@@ -15,16 +17,33 @@ namespace Serein.Workbench.ViewModels
internal partial class FlowLibrarysViewModel : ObservableObject
{
private readonly IFlowEEForwardingService flowEEForwardingService;
private readonly IFlowEnvironment flowEnvironment;
[ObservableProperty]
private ObservableCollection<FlowLibraryInfo> flowLibraryInfos;
public FlowLibrarysViewModel(IFlowEEForwardingService flowEEForwardingService)
public FlowLibrarysViewModel(IFlowEEForwardingService flowEEForwardingService,IFlowEnvironment flowEnvironment)
{
this.flowEEForwardingService = flowEEForwardingService;
this.flowEnvironment = flowEnvironment;
FlowLibraryInfos = new ObservableCollection<FlowLibraryInfo>();
flowEEForwardingService.OnDllLoad += FlowEEForwardingService_OnDllLoad;
}
/// <summary>
/// 加载文件依赖
/// </summary>
/// <param name="filePath"></param>
public void LoadFileLibrary(string filePath)
{
try
{
flowEnvironment.LoadLibrary(filePath);
}
catch (Exception ex)
{
flowEnvironment.WriteLine(Library.InfoType.ERROR, ex.ToString());
return;
}
}
private void FlowEEForwardingService_OnDllLoad(Library.Api.LoadDllEventArgs eventArgs)
{
@@ -32,10 +51,10 @@ namespace Serein.Workbench.ViewModels
List<MethodDetailsInfo> mds = eventArgs.MethodDetailss;
NodeLibraryInfo libraryInfo = eventArgs.NodeLibraryInfo;
var methodInfo = new ObservableCollection<FlowLibraryMethodDetailsInfo>();
var methodInfo = new ObservableCollection<MethodDetailsInfo>();
foreach (var md in mds)
{
methodInfo.Add(new FlowLibraryMethodDetailsInfo(md));
methodInfo.Add(md);
}
var flInfo = new FlowLibraryInfo
{

View File

@@ -1,4 +1,6 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Serein.Library.Api;
using Serein.NodeFlow.Env;
using Serein.Workbench.Api;
using Serein.Workbench.Models;
using Serein.Workbench.Services;
@@ -8,19 +10,36 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace Serein.Workbench.ViewModels
{
internal partial class FlowWorkbenchViewModel : ObservableObject
{
private readonly IFlowEEForwardingService flowEEForwardingService;
private readonly IFlowEnvironment flowEnvironment;
private readonly IWorkbenchEventService workbenchEventService;
private readonly IKeyEventService keyEventService;
public FlowWorkbenchViewModel(IFlowEEForwardingService flowEEForwardingService, IWorkbenchEventService workbenchEventService)
public FlowWorkbenchViewModel(IFlowEnvironment flowEnvironment,
IWorkbenchEventService workbenchEventService,
IKeyEventService keyEventService)
{
this.flowEEForwardingService = flowEEForwardingService;
this.flowEnvironment = flowEnvironment;
this.workbenchEventService = workbenchEventService;
//flowEEForwardingService.OnDllLoad += FlowEEForwardingService_OnDllLoad;
this.keyEventService = keyEventService;
EventManager.RegisterClassHandler(typeof(Window), Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown)); // 按下事件
EventManager.RegisterClassHandler(typeof(Window), Keyboard.KeyUpEvent, new KeyEventHandler(OnKeyUp)); // 松开事件
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
keyEventService.KeyDown(e.Key);
}
private void OnKeyUp(object sender, KeyEventArgs e)
{
keyEventService.KeyUp(e.Key);
}
}
}

View File

@@ -1,6 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Serein.Library.Api;
using Serein.Workbench.Services;
using System.Windows.Input;
namespace Serein.Workbench.ViewModels
@@ -8,6 +9,7 @@ namespace Serein.Workbench.ViewModels
public class MainMenuBarViewModel : ObservableObject
{
private readonly IFlowEnvironment environment;
private readonly FlowNodeService flowNodeService;
/// <summary>
/// 保存项目
@@ -31,6 +33,10 @@ namespace Serein.Workbench.ViewModels
/// </summary>
public ICommand RemoteFlowCanvasCommand { get; private set; }
/// <summary>
/// 运行当前画布流程
/// </summary>
public ICommand StartFlowCommand { get; private set; }
/// <summary>
/// 运行当前画布流程
/// </summary>
@@ -55,10 +61,10 @@ namespace Serein.Workbench.ViewModels
public MainMenuBarViewModel(IFlowEnvironment environment)
public MainMenuBarViewModel(IFlowEnvironment environment, FlowNodeService flowNodeService)
{
this.environment = environment;
this.flowNodeService = flowNodeService;
SaveProjectCommand = new RelayCommand(SaveProject); // 保存项目
LoadLocalProjectCommand = new RelayCommand(LoadLocalProject); // 加载本地项目
LoadRemoteProjectCommand = new RelayCommand(LoadRemoteProject); // 加载远程项目
@@ -66,26 +72,30 @@ namespace Serein.Workbench.ViewModels
CreateFlowCanvasCommand = new RelayCommand(CreateFlowCanvas); // 增加画布
RemoteFlowCanvasCommand = new RelayCommand(RemoteFlowCanvas); // 移除画布
StartCurrentCanvasFlowCommand = new RelayCommand(StartCurrentCanvasFlow); // 运行当前流程
StartFlowCommand = new RelayCommand(StartFlow);
StartCurrentCanvasFlowCommand = new RelayCommand(StartCurrentCanvasFlow); // 运行当前所查看画布的流程
StopCurrentCanvasFlowCommand = new RelayCommand(StopCurrentCanvasFlow); // 停止当前流程
OpenEnvOutWindowCommand = new RelayCommand(OpenEnvOutWindow); // 打开运行输出窗口
OpenDynamicCompilerCommand = new RelayCommand(OpenDynamicCompiler); // 打开动态编译仓库窗口
}
private void SaveProject() {
environment.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 LoadRemoteProject()
{
}
private void CreateFlowCanvas() => flowNodeService.CreateFlowCanvas();
private void RemoteFlowCanvas() => flowNodeService.RemoveFlowCanvas();
private void StartFlow() => environment.StartFlowAsync([.. flowNodeService.FlowCanvass.Select(c => c.Guid)]);
private void StartCurrentCanvasFlow() => environment.StartFlowAsync([flowNodeService.CurrentSelectCanvas.Guid]);
private void StopCurrentCanvasFlow() { }
private void OpenDynamicCompiler() { }
private void OpenEnvOutWindow() { }
private void OpenEnvOutWindow() => LogWindow.Instance?.Show();
}
}

View File

@@ -1,17 +1,25 @@
using CommunityToolkit.Mvvm.ComponentModel;
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;
namespace Serein.Workbench.ViewModels
{
public class MainViewModel : ObservableObject
{
public MainViewModel()
private readonly IKeyEventService keyEventService;
public MainViewModel(IKeyEventService keyEventService)
{
this.keyEventService = keyEventService;
}
}
}

View File

@@ -1,4 +1,5 @@
using Serein.Library;
using Serein.Workbench.Customs;
using Serein.Workbench.ViewModels;
using System;
using System.Collections.Generic;

View File

@@ -23,7 +23,7 @@
<Canvas ClipToBounds="True"
x:Name="FlowChartCanvas"
Background="#E1FBEA"
Background="#FFFFFF"
AllowDrop="True"
Width="{Binding Model.Width, Mode=TwoWay}"
Height="{Binding Model.Height, Mode=TwoWay}"

View File

@@ -1,14 +1,21 @@
using Serein.Library;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using Serein.Library;
using Serein.Library.Api;
using Serein.NodeFlow.Env;
using Serein.Workbench.Api;
using Serein.Workbench.Customs;
using Serein.Workbench.Extension;
using Serein.Workbench.Models;
using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Services;
using Serein.Workbench.Themes;
using Serein.Workbench.Tool;
using Serein.Workbench.ViewModels;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Drawing.Printing;
using System.Linq;
@@ -19,13 +26,20 @@ using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;
using static Serein.Workbench.MainWindow;
using Binding = System.Windows.Data.Binding;
using DragDropEffects = System.Windows.DragDropEffects;
using DragEventArgs = System.Windows.DragEventArgs;
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
using UserControl = System.Windows.Controls.UserControl;
using Clipboard = System.Windows.Clipboard;
using TextDataFormat = System.Windows.TextDataFormat;
namespace Serein.Workbench.Views
{
@@ -34,9 +48,11 @@ namespace Serein.Workbench.Views
/// </summary>
public partial class FlowCanvasView : UserControl, IFlowCanvas
{
private readonly IFlowEnvironment flowEnvironment;
private readonly IKeyEventService keyEventService;
private readonly FlowNodeService flowNodeService;
/// <summary>
/// 存储所有的连接。考虑集成在运行环境中。
/// </summary>
@@ -44,7 +60,6 @@ namespace Serein.Workbench.Views
private FlowCanvasViewModel ViewModel => this.DataContext as FlowCanvasViewModel ?? throw new ArgumentNullException();
#region
private IFlowCanvas Api => this;
@@ -57,9 +72,10 @@ namespace Serein.Workbench.Views
}
public string Name => ViewModel.Model.Name;
FlowCanvasDetails IFlowCanvas.Model => ViewModel.Model;
void IFlowCanvas.Remove(NodeControlBase nodeControl)
{
ViewModel.NodeControls.Remove(nodeControl.ViewModel.NodeModel.Guid);
FlowChartCanvas.Dispatcher.Invoke(() =>
{
FlowChartCanvas.Children.Remove(nodeControl);
@@ -67,11 +83,15 @@ namespace Serein.Workbench.Views
}
void IFlowCanvas.Add(NodeControlBase nodeControl)
{
ViewModel.NodeControls.TryAdd(nodeControl.ViewModel.NodeModel.Guid, nodeControl);
FlowChartCanvas.Dispatcher.Invoke(() =>
{
FlowChartCanvas.Children.Add(nodeControl);
});
ConfigureNodeEvents(nodeControl); // 配置相关事件
ConfigureContextMenu(nodeControl); // 添加右键菜单
}
void IFlowCanvas.CreateInvokeConnection(NodeControlBase fromNodeControl, NodeControlBase toNodeControl, ConnectionInvokeType type)
@@ -224,6 +244,9 @@ namespace Serein.Workbench.Views
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging;
/// <summary>
/// 是否正在选取控件
/// </summary>
private bool IsSelectDragging;
/// <summary>
@@ -258,8 +281,7 @@ namespace Serein.Workbench.Views
private readonly TranslateTransform translateTransform;
#endregion
#region
#region
public FlowCanvasView(FlowCanvasDetails model)
{
@@ -270,9 +292,9 @@ namespace Serein.Workbench.Views
flowEnvironment = App.GetService<IFlowEnvironment>();
flowNodeService = App.GetService<FlowNodeService>();
keyEventService = App.GetService<IKeyEventService>();
flowNodeService.OnCreateNode += OnCreateNode;
keyEventService.OnKeyDown += KeyEventService_OnKeyDown; ;
// 缩放平移容器
canvasTransformGroup = new TransformGroup();
@@ -286,6 +308,10 @@ namespace Serein.Workbench.Views
}
/// <summary>
/// 设置绑定
/// </summary>
/// <param name="canvasModel"></param>
private void SetBinding(FlowCanvasDetails canvasModel)
{
Binding bindingScaleX = new(nameof(canvasModel.ScaleX)) { Source = canvasModel, Mode = BindingMode.TwoWay };
@@ -325,8 +351,7 @@ namespace Serein.Workbench.Views
{
// 并非添加在容器中,直接放置节点
Api.Add(nodeControl); // 添加到对应的画布上
ConfigureNodeEvents(nodeControl); // 添加了节点
ConfigureContextMenu(nodeControl); // 添加右键菜单
}
}
@@ -349,7 +374,7 @@ namespace Serein.Workbench.Views
// 准备放置条件表达式控件
if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition)
{
ConditionRegionControl? conditionRegion = GetParentOfType<ConditionRegionControl>(hitElement);
ConditionRegionControl? conditionRegion = WpfFuncTool.GetParentOfType<ConditionRegionControl>(hitElement);
if (conditionRegion is not null)
{
targetNodeControl = conditionRegion;
@@ -362,7 +387,7 @@ namespace Serein.Workbench.Views
else
{
// 准备放置全局数据控件
GlobalDataControl? globalDataControl = GetParentOfType<GlobalDataControl>(hitElement);
GlobalDataControl? globalDataControl = WpfFuncTool.GetParentOfType<GlobalDataControl>(hitElement);
if (globalDataControl is not null)
{
targetNodeControl = globalDataControl;
@@ -376,6 +401,107 @@ namespace Serein.Workbench.Views
#endregion
#region
/// <summary>
/// 监听按键事件
/// </summary>
/// <param name="key"></param>
private void KeyEventService_OnKeyDown(Key key)
{
if (!flowNodeService.CurrentSelectCanvas.Guid.Equals(Guid))
{
return;
}
if (key == Key.Escape)
{
IsControlDragging = false;
IsCanvasDragging = false;
SelectionRectangle.Visibility = Visibility.Collapsed;
CancelSelectNode();
EndConnection();
return;
}
// 复制节点
if (selectNodeControls.Count > 0 && key == Key.C && (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl)))
{
var text = flowNodeService.CpoyNodeInfo([.. selectNodeControls.Select(c => c.ViewModel.NodeModel)]);
Clipboard.SetDataObject(text, true); // 复制,持久性设置
return;
}
// 粘贴节点
if (key == Key.V && (keyEventService.GetKeyState(Key.LeftCtrl) || keyEventService.GetKeyState(Key.RightCtrl)))
{
string clipboardText = Clipboard.GetText(TextDataFormat.Text); // 获取复制的文本
var jobject = JObject.Parse(clipboardText);
var nodesText = jobject["nodes"]?.ToString();
if (!string.IsNullOrWhiteSpace(nodesText))
{
if (Clipboard.ContainsText())
{
Point mousePosition = Mouse.GetPosition(FlowChartCanvas);
PositionOfUI positionOfUI = new PositionOfUI(mousePosition.X, mousePosition.Y); // 坐标数据
flowNodeService.PasteNodeInfo(Guid, nodesText, positionOfUI); // 粘贴节点信息
}
else if (Clipboard.ContainsImage())
{
// var image = Clipboard.GetImage();
}
else
{
SereinEnv.WriteLine(InfoType.INFO, "剪贴板中没有可识别的数据。");
}
}
return;
}
var cd = flowNodeService.ConnectingData;
if (cd.IsCreateing)
{
if (cd.Type == JunctionOfConnectionType.Invoke)
{
ConnectionInvokeType connectionInvokeType = key switch
{
Key.D1 => ConnectionInvokeType.Upstream,
Key.D2 => ConnectionInvokeType.IsSucceed,
Key.D3 => ConnectionInvokeType.IsFail,
Key.D4 => ConnectionInvokeType.IsError,
_ => ConnectionInvokeType.None,
};
if (connectionInvokeType != ConnectionInvokeType.None)
{
cd.ConnectionInvokeType = connectionInvokeType;
cd.MyLine.Line.UpdateLineColor(connectionInvokeType.ToLineColor());
}
}
else if (cd.Type == JunctionOfConnectionType.Arg)
{
ConnectionArgSourceType connectionArgSourceType = key switch
{
Key.D1 => ConnectionArgSourceType.GetOtherNodeData,
Key.D2 => ConnectionArgSourceType.GetOtherNodeDataOfInvoke,
_ => ConnectionArgSourceType.GetPreviousNodeData,
};
if (connectionArgSourceType != ConnectionArgSourceType.GetPreviousNodeData)
{
cd.ConnectionArgSourceType = connectionArgSourceType;
cd.MyLine.Line.UpdateLineColor(connectionArgSourceType.ToLineColor());
}
}
cd.CurrentJunction.InvalidateVisual(); // 刷新目标节点控制点样式
}
}
#endregion
#region
/// <summary>
/// 鼠标在画布移动。
@@ -385,11 +511,11 @@ namespace Serein.Workbench.Views
/// </summary>
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
var myData = GlobalJunctionData.MyGlobalConnectingData;
if (myData.IsCreateing && e.LeftButton == MouseButtonState.Pressed)
var cd = flowNodeService.ConnectingData;
if (cd.IsCreateing && e.LeftButton == MouseButtonState.Pressed)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
if (cd.Type == JunctionOfConnectionType.Invoke)
{
ViewModel.IsConnectionInvokeNode = true; // 正在连接节点的调用关系
@@ -401,7 +527,7 @@ namespace Serein.Workbench.Views
var currentPoint = e.GetPosition(FlowChartCanvas);
currentPoint.X -= 2;
currentPoint.Y -= 2;
myData.UpdatePoint(currentPoint);
cd.UpdatePoint(currentPoint);
return;
}
@@ -456,10 +582,10 @@ namespace Serein.Workbench.Views
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
{
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeModel nodeModel)
{
flowNodeService.CurrentNodeControlType = nodeData.NodeControlType; // 设置基础节点类型
flowNodeService.CurrentDragMdInfo = nodeData.MethodDetailsInfo; // 基础节点不需要参数信息
flowNodeService.CurrentNodeControlType = nodeModel.NodeControlType; // 设置基础节点类型
flowNodeService.CurrentDragMdInfo = nodeModel.MethodDetailsInfo; // 基础节点不需要参数信息
flowNodeService.CurrentMouseLocation = position; // 设置当前鼠标为止
flowNodeService.CreateNode(); // 创建来自DLL加载的方法节点
}
@@ -524,7 +650,8 @@ namespace Serein.Workbench.Views
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (GlobalJunctionData.MyGlobalConnectingData.IsCreateing)
if (flowNodeService.ConnectingData.IsCreateing)
{
return;
}
@@ -580,42 +707,42 @@ namespace Serein.Workbench.Views
}
// 创建连线
if (GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
var cd = flowNodeService.ConnectingData;
if (cd.IsCreateing)
{
if (myData.IsCanConnected)
if (cd.IsCanConnected)
{
var canvas = this.FlowChartCanvas;
var currentendPoint = e.GetPosition(canvas); // 当前鼠标落点
var changingJunctionPosition = myData.CurrentJunction.TranslatePoint(new Point(0, 0), canvas);
var changingJunctionRect = new Rect(changingJunctionPosition, new Size(myData.CurrentJunction.Width, myData.CurrentJunction.Height));
var changingJunctionPosition = cd.CurrentJunction.TranslatePoint(new Point(0, 0), canvas);
var changingJunctionRect = new Rect(changingJunctionPosition, new Size(cd.CurrentJunction.Width, cd.CurrentJunction.Height));
if (changingJunctionRect.Contains(currentendPoint)) // 可以创建连接
{
#region
if (myData.Type == JunctionOfConnectionType.Invoke)
if (cd.Type == JunctionOfConnectionType.Invoke)
{
var canvasGuid = this.Guid;
await flowEnvironment.ConnectInvokeNodeAsync(
canvasGuid,
myData.StartJunction.MyNode.Guid,
myData.CurrentJunction.MyNode.Guid,
myData.StartJunction.JunctionType,
myData.CurrentJunction.JunctionType,
myData.ConnectionInvokeType);
cd.StartJunction.MyNode.Guid,
cd.CurrentJunction.MyNode.Guid,
cd.StartJunction.JunctionType,
cd.CurrentJunction.JunctionType,
cd.ConnectionInvokeType);
}
#endregion
#region
else if (myData.Type == JunctionOfConnectionType.Arg)
else if (cd.Type == JunctionOfConnectionType.Arg)
{
var argIndex = 0;
if (myData.StartJunction is ArgJunctionControl argJunction1)
if (cd.StartJunction is ArgJunctionControl argJunction1)
{
argIndex = argJunction1.ArgIndex;
}
else if (myData.CurrentJunction is ArgJunctionControl argJunction2)
else if (cd.CurrentJunction is ArgJunctionControl argJunction2)
{
argIndex = argJunction2.ArgIndex;
}
@@ -623,11 +750,11 @@ namespace Serein.Workbench.Views
await flowEnvironment.ConnectArgSourceNodeAsync(
canvasGuid,
myData.StartJunction.MyNode.Guid,
myData.CurrentJunction.MyNode.Guid,
myData.StartJunction.JunctionType,
myData.CurrentJunction.JunctionType,
myData.ConnectionArgSourceType,
cd.StartJunction.MyNode.Guid,
cd.CurrentJunction.MyNode.Guid,
cd.StartJunction.JunctionType,
cd.CurrentJunction.JunctionType,
cd.ConnectionArgSourceType,
argIndex);
}
#endregion
@@ -640,7 +767,14 @@ namespace Serein.Workbench.Views
}
#region
/// <summary>
/// 开始拖动画布
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
IsCanvasDragging = true;
@@ -649,11 +783,13 @@ namespace Serein.Workbench.Views
e.Handled = true; // 防止事件传播影响其他控件
}
/// <summary>
/// 停止拖动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (IsCanvasDragging)
{
IsCanvasDragging = false;
@@ -661,7 +797,11 @@ namespace Serein.Workbench.Views
}
}
// 单纯缩放画布,不改变画布大小
/// <summary>
/// 单纯缩放画布,不改变画布大小
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
// if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
@@ -820,6 +960,9 @@ namespace Serein.Workbench.Views
#endregion
#endregion
#endregion
#region
/// <summary>
/// 完成选取操作
@@ -887,6 +1030,27 @@ namespace Serein.Workbench.Views
}
}
private void CancelSelectNode()
{
IsSelectControl = false;
foreach (var nodeControl in selectNodeControls)
{
//nodeControl.ViewModel.IsSelect = false;
nodeControl.BorderBrush = Brushes.Black;
nodeControl.BorderThickness = new Thickness(0);
var startNodeGuid = ViewModel.Model.StartNode;
var nodeGuid = nodeControl.ViewModel.NodeModel.Guid;
if (startNodeGuid.Equals(nodeGuid))
{
nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
nodeControl.BorderThickness = new Thickness(2);
}
}
selectNodeControls.Clear();
}
/// <summary>
/// 结束连接操作,清理状态并移除虚线。
/// </summary>
@@ -895,21 +1059,9 @@ namespace Serein.Workbench.Views
Mouse.OverrideCursor = null; // 恢复视觉效果
ViewModel.IsConnectionArgSourceNode = false;
ViewModel.IsConnectionInvokeNode = false;
GlobalJunctionData.OK();
flowNodeService.ConnectingData.Reset();
}
/// <summary>
/// 创建菜单子项
/// </summary>
/// <param name="header"></param>
/// <param name="handler"></param>
/// <returns></returns>
public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
{
var menuItem = new MenuItem { Header = header };
menuItem.Click += handler;
return menuItem;
}
/// <summary>
/// 选择范围配置
@@ -918,7 +1070,7 @@ namespace Serein.Workbench.Views
private ContextMenu ConfiguerSelectionRectangle()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", (s, e) =>
{
if (selectNodeControls.Count > 0)
{
@@ -938,6 +1090,10 @@ namespace Serein.Workbench.Views
// nodeControl.ContextMenu = contextMenu;
}
#endregion
#region
/// <summary>
/// 配置节点事件(移动,点击相关)
@@ -950,13 +1106,7 @@ namespace Serein.Workbench.Views
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;
}
@@ -972,8 +1122,8 @@ namespace Serein.Workbench.Views
IsControlDragging = true;
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标
e.Handled = true; // 防止事件传播影响其他控件
}
e.Handled = true; // 防止事件传播影响其他控件
}
/// <summary>
@@ -981,12 +1131,13 @@ namespace Serein.Workbench.Views
/// </summary>
private void Block_MouseMove(object sender, MouseEventArgs e)
{
if (IsCanvasDragging)
return;
if (IsSelectControl)
return;
if (IsControlDragging) // 如果正在拖动控件
if (IsControlDragging && !flowNodeService.ConnectingData.IsCreateing) // 如果正在拖动控件
{
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
@@ -1005,21 +1156,26 @@ namespace Serein.Workbench.Views
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)
List<(string Guid, double NewLeft, double NewTop, double MaxWidth, double MaxHeight)> moveSizes =
selectNodeControls.Select(control =>
(control.ViewModel.NodeModel.Guid,
Canvas.GetLeft(control) + actualDeltaX,
Canvas.GetTop(control) + actualDeltaY,
control.FlowCanvas.Model.Width - control.ActualWidth - 10,
control.FlowCanvas.Model.Height - control.ActualHeight - 10)).ToList();
var isNeedCancel = moveSizes.Exists(item => item.NewLeft < 5 || item.NewTop < 5|| item.NewLeft > item.MaxWidth || item.NewTop > item.MaxHeight);
if (isNeedCancel)
{
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); // 移动节点
}
return;
}
foreach (var item in moveSizes)
{
this.flowEnvironment.MoveNode(this.Guid, item.Guid, item.NewLeft, item.NewTop); // 移动节点
}
// 更新节点之间线的连接位置
@@ -1038,6 +1194,14 @@ namespace Serein.Workbench.Views
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
double newLeft = Canvas.GetLeft(nodeControl) + deltaX; // 新的左边距
double newTop = Canvas.GetTop(nodeControl) + deltaY; // 新的上边距
// 如果被移动的控件接触到画布边缘,则限制移动范围
var canvasModel = nodeControl.FlowCanvas.Model;
var canvasWidth = canvasModel.Width - nodeControl.ActualWidth - 10;
var canvasHeight= canvasModel.Height - nodeControl.ActualHeight - 10;
newLeft = newLeft < 5 ? 5 : newLeft > canvasWidth ? canvasWidth : newLeft;
newTop = newTop < 5 ? 5 : newTop > canvasHeight ? canvasHeight : newTop;
this.flowEnvironment.MoveNode(Guid, nodeControl.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点
nodeControl.UpdateLocationConnections();
}
@@ -1095,7 +1259,7 @@ namespace Serein.Workbench.Views
if (nodeControl.ViewModel?.NodeModel.ControlType == NodeControlType.Flipflop)
{
contextMenu.Items.Add(CreateMenuItem("启动触发器", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("启动触发器", (s, e) =>
{
if (s is MenuItem menuItem)
{
@@ -1119,7 +1283,7 @@ namespace Serein.Workbench.Views
if (nodeControl.ViewModel?.NodeModel?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
{
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("查看返回类型", (s, e) =>
{
DisplayReturnTypeTreeViewer(returnType);
}));
@@ -1127,8 +1291,8 @@ namespace Serein.Workbench.Views
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", async (s, e) =>
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("设为起点", (s, e) => flowEnvironment.SetStartNodeAsync(canvasGuid, nodeGuid)));
contextMenu.Items.Add(WpfFuncTool.CreateMenuItem("删除", async (s, e) =>
{
var result = await flowEnvironment.RemoveNodeAsync(canvasGuid, nodeGuid);
}));
@@ -1136,28 +1300,28 @@ namespace Serein.Workbench.Views
#region -
var AvoidMenu = new MenuItem();
AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("群组对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Grouping);
}));
AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("规划对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Planning);
}));
AvoidMenu.Items.Add(CreateMenuItem("水平中心对齐", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("水平中心对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.HorizontalCenter);
}));
AvoidMenu.Items.Add(CreateMenuItem("垂直中心对齐 ", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("垂直中心对齐 ", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.VerticalCenter);
}));
AvoidMenu.Items.Add(CreateMenuItem("垂直对齐时水平斜分布", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("垂直对齐时水平斜分布", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Vertical);
}));
AvoidMenu.Items.Add(CreateMenuItem("水平对齐时垂直斜分布", (s, e) =>
AvoidMenu.Items.Add(WpfFuncTool.CreateMenuItem("水平对齐时垂直斜分布", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Horizontal);
}));

View File

@@ -56,11 +56,11 @@ AllowDrop="True"-->
</TabControl>
<!-- Tab control buttons -->
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
<!--<StackPanel Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right">
<Button Content="添加" Command="{Binding AddTabCommand}" Margin="5" Width="80"/>
<Button Content="移除" Command="{Binding RemoveTabCommand}" Margin="5" Width="80"/>
<!--<Button Content="Rename Tab" Command="{Binding RenameTabCommand}" />-->
</StackPanel>
--><!--<Button Content="Rename Tab" Command="{Binding RenameTabCommand}" />--><!--
</StackPanel>-->
</Grid>
</UserControl>

View File

@@ -126,12 +126,11 @@ namespace Serein.Workbench.Views
private void TabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is TabControl tabControl
&& tabControl.SelectedIndex > 0
&& tabControl.SelectedIndex > -1
&& DataContext is FlowEditViewModel viewModel
&& viewModel.CanvasTabs[tabControl.SelectedIndex] is FlowEditorTabModel tab)
{
viewModel.EndEditingTab(lastTab); // 确认新名称
viewModel.EndEditingTab(lastTab); // 取消编辑
lastTab = tab;
return;
}

View File

@@ -9,11 +9,24 @@
xmlns:vm="clr-namespace:Serein.Workbench.ViewModels"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance vm:FlowLibrarysViewModel}"
d:DesignHeight="450" d:DesignWidth="800">
d:DesignHeight="450" d:DesignWidth="800"
AllowDrop="True"
Drop="FlowLibrarysView_Drop"
DragOver="FlowLibrarysView_DragOver" >
<UserControl.Resources>
<converter:CountToVisibilityConverter x:Key="CountToVisibilityConverter"/>
<!--<DataTemplate x:Key="NodeListTemplate">
<Grid Margin="2">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"/>
<TextBlock Text="{Binding AnotherName}" Margin="4,0,0,0"/>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"/>
</StackPanel>
</Grid>
</DataTemplate>-->
</UserControl.Resources>
<ScrollViewer>
@@ -31,58 +44,27 @@
<TextBlock Text="{Binding LibraryName}" ></TextBlock>
<TextBlock Text="{Binding FilePath}" Margin="6,0,0,0"></TextBlock>
</StackPanel>
<!--<custom:FlowMethodInfoListBox Grid.Row="1" Nodes="{Binding ActionNodes}" BackgroundColor="#D0F1F9"/>
<custom:FlowMethodInfoListBox Grid.Row="2" Nodes="{Binding FlipflopNodes}" BackgroundColor="#FACFC1"/>
<custom:FlowMethodInfoListBox Grid.Row="3" Nodes="{Binding UINodes}" BackgroundColor="#FFFBD7"/>-->
<ListBox Grid.Row="1" Margin="6,2,2,2" ItemsSource="{Binding ActionNodes}"
<custom:FlowMethodInfoListBox Grid.Row="1" ItemsSource="{Binding ActionNodes}" Background="#D0F1F9"/>
<custom:FlowMethodInfoListBox Grid.Row="2" ItemsSource="{Binding FlipflopNodes}" Background="#FACFC1"/>
<custom:FlowMethodInfoListBox Grid.Row="3" ItemsSource="{Binding UINodes}" Background="#FFFBD7"/>
<!--<ListBox Grid.Row="1" Margin="6,2,2,2" ItemsSource="{Binding ActionNodes}"
Visibility="{Binding ActionNodes, Converter={StaticResource CountToVisibilityConverter}}"
Background="#D0F1F9">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="2" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"></TextBlock>
<TextBlock Text="{Binding AnotherName}" Margin="4,0,0,0"></TextBlock>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Background="#D0F1F9"
ItemTemplate="{StaticResource NodeListTemplate}">
</ListBox>
<ListBox Grid.Row="2" Margin="6,2,2,2" ItemsSource="{Binding FlipflopNodes}"
Visibility="{Binding FlipflopNodes, Converter={StaticResource CountToVisibilityConverter}}"
Background="#FACFC1">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="2" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"></TextBlock>
<TextBlock Text="{Binding AnotherName}" Margin="4,0,0,0"></TextBlock>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
Background="#FACFC1"
ItemTemplate="{StaticResource NodeListTemplate}">
</ListBox>
<ListBox Grid.Row="3" Margin="6,2,2,2" ItemsSource="{Binding UINodes}"
Visibility="{Binding UINodes, Converter={StaticResource CountToVisibilityConverter}}"
Background="#FFFBD7">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="2" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding NodeType}"></TextBlock>
<TextBlock Text="{Binding AnotherName}" Margin="4,0,0,0"></TextBlock>
<TextBlock Text="{Binding MethodName}" Margin="6,0,0,0"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Background="#FFFBD7"
ItemTemplate="{StaticResource NodeListTemplate}">
</ListBox>-->
</Grid>
</DataTemplate>
@@ -90,3 +72,9 @@
</ItemsControl>
</ScrollViewer>
</UserControl>
<!--<custom:FlowMethodInfoListBox Grid.Row="1" Nodes="{Binding ActionNodes}" BackgroundColor="#D0F1F9"/>
<custom:FlowMethodInfoListBox Grid.Row="2" Nodes="{Binding FlipflopNodes}" BackgroundColor="#FACFC1"/>
<custom:FlowMethodInfoListBox Grid.Row="3" Nodes="{Binding UINodes}" BackgroundColor="#FFFBD7"/> -->

View File

@@ -1,4 +1,5 @@
using Serein.Workbench.ViewModels;
using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -21,10 +22,32 @@ namespace Serein.Workbench.Views
/// </summary>
public partial class FlowLibrarysView : UserControl
{
private FlowLibrarysViewModel ViewModel => DataContext as FlowLibrarysViewModel ?? throw new ArgumentNullException();
public FlowLibrarysView()
{
this.DataContext = App.GetService<Locator>().FlowLibrarysViewModel;
InitializeComponent();
}
private void FlowLibrarysView_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
foreach (string file in files)
{
if (file.EndsWith(".dll"))
{
ViewModel.LoadFileLibrary(file);
}
}
}
}
private void FlowLibrarysView_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
}
}

View File

@@ -6,7 +6,8 @@
xmlns:local="clr-namespace:Serein.Workbench.Views"
mc:Ignorable="d"
Loaded="Window_Loaded"
Title="FlowWorkbenchView" Height="450" Width="800">
Title="FlowWorkbenchView" Height="450" Width="800"
Closing="Window_Closing">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
@@ -23,7 +24,7 @@
<local:MainMenuBarView Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5"/>
<!--左侧功能区-->
<Grid Grid.Row="1" Grid.Column="0" >
<Grid Grid.Row="1" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
@@ -31,10 +32,13 @@
<!--<RowDefinition Height="3*"></RowDefinition>-->
</Grid.RowDefinitions>
<local:BaseNodesView Grid.Row="0" Grid.ColumnSpan="1" Margin="0,0,0,15"/>
<local:FlowLibrarysView Grid.Row="1" Grid.ColumnSpan="1" Margin="0,0,0,15"/>
<local:FlowLibrarysView Grid.Row="1" Grid.ColumnSpan="1" Margin="0,0,0,15" />
</Grid>
<!--功能区和编辑区的分割线-->
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="#CCD5F0" />
<!--流程编辑区-->
<local:FlowEditView Grid.Row="1" Grid.Column="2"/>
<!--编辑区和视图区的分割线-->
<GridSplitter Grid.Row="1" Grid.Column="3" Width="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ResizeBehavior="PreviousAndNext" Background="#CCD5F0" />
</Grid>
</Window>

View File

@@ -20,6 +20,7 @@ namespace Serein.Workbench.Views
/// </summary>
public partial class FlowWorkbenchView : Window
{
private FlowWorkbenchViewModel ViewModel => ViewModel as FlowWorkbenchViewModel;
public FlowWorkbenchView()
{
this.DataContext = App.GetService<Locator>().FlowWorkbenchViewModel;
@@ -40,5 +41,12 @@ namespace Serein.Workbench.Views
this.Width = System.Windows.SystemParameters.PrimaryScreenWidth;
this.Height = System.Windows.SystemParameters.PrimaryScreenHeight;*/
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
// 确保所有窗口关闭
LogWindow.Instance.Close();
System.Windows.Application.Current.Shutdown();
}
}
}

View File

@@ -16,19 +16,22 @@
<MenuItem Header="加载本地项目" ></MenuItem>
<MenuItem Header="加载远程项目"></MenuItem>
</MenuItem>
<MenuItem Header="编辑">
<MenuItem Header="增加画布"></MenuItem>
<MenuItem Header="删除当前画布"></MenuItem>
<MenuItem Header="画布">
<MenuItem Header="增加画布" Command="{Binding CreateFlowCanvasCommand}"></MenuItem>
<MenuItem Header="删除当前画布" Command="{Binding RemoteFlowCanvasCommand}"></MenuItem>
</MenuItem>
<MenuItem Header="调试">
<MenuItem Header="运行">
<MenuItem Header="运行(仅当前画布)" Command="{Binding StartCurrentCanvasFlowCommand}"></MenuItem>
<MenuItem Header="运行(从起始节点)"></MenuItem>
<MenuItem Header="运行(从选定节点)" ></MenuItem>
<MenuItem Header="运行" Command="{Binding StartFlowCommand}"></MenuItem>
<MenuItem Header="结束流程" ></MenuItem>
</MenuItem>
<MenuItem Header="视图">
<MenuItem Header="输出窗口" ></MenuItem>
<MenuItem Header="输出窗口" Command="{Binding OpenEnvOutWindowCommand}"></MenuItem>
<MenuItem Header="重置画布"></MenuItem>
<MenuItem Header="定位节点" ></MenuItem>
</MenuItem>