Files
serein-flow/Workbench/MainWindow.xaml.cs

3038 lines
118 KiB
C#
Raw Normal View History

2025-01-05 08:52:37 +08:00
using Microsoft.Win32;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Serein.Library;
using Serein.Library.Api;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Tool;
using Serein.Workbench.Extension;
using Serein.Workbench.Node;
using Serein.Workbench.Node.View;
using Serein.Workbench.Node.ViewModel;
using Serein.Workbench.Themes;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
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);
}
/// <summary>
/// Interaction logic for MainWindow.xaml第一次用git不太懂
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// 全局捕获Console输出事件打印在这个窗体里面
/// </summary>
private readonly LogWindow LogOutWindow = new LogWindow();
/// <summary>
/// 流程环境装饰器,方便在本地与远程环境下切换
/// </summary>
private IFlowEnvironment EnvDecorator => ViewModel.FlowEnvironment;
private IFlowEnvironmentEvent EnvEventDecorator => ViewModel.FlowEnvironment as IFlowEnvironmentEvent;
private MainWindowViewModel ViewModel { get; set; }
/// <summary>
/// 节点对应的控件类型
/// </summary>
// private Dictionary<NodeControlType, (Type controlType, Type viewModelType)> NodeUITypes { get; } = [];
/// <summary>
/// 存储所有与节点有关的控件
/// 任何情景下都应避免直接操作 ViewModel 中的 NodeModel 节点,
/// 而是应该调用 FlowEnvironment 提供接口进行操作,
/// 因为 Workbench 应该更加关注UI视觉效果而非直接干扰流程环境运行的逻辑。
/// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
/// </summary>
private Dictionary<string, NodeControlBase> NodeControls { get; } = [];
/// <summary>
/// 存储所有的连接。考虑集成在运行环境中。
/// </summary>
private List<ConnectionControl> Connections { get; } = [];
/// <summary>
/// 起始节点
/// </summary>
//private NodeControlBase StartNodeControl{ get; set; }
#region
/// <summary>
/// 标记是否正在尝试选取控件
/// </summary>
private bool IsSelectControl;
/// <summary>
/// 标记是否正在进行连接操作
/// </summary>
//private bool IsConnecting;
/// <summary>
/// 标记是否正在拖动控件
/// </summary>
private bool IsControlDragging;
/// <summary>
/// 标记是否正在拖动画布
/// </summary>
private bool IsCanvasDragging;
private bool IsSelectDragging;
/// <summary>
/// 当前选取的控件
/// </summary>
private readonly List<NodeControlBase> selectNodeControls = [];
/// <summary>
/// 记录开始拖动节点控件时的鼠标位置
/// </summary>
private Point startControlDragPoint;
/// <summary>
/// 记录移动画布开始时的鼠标位置
/// </summary>
private Point startCanvasDragPoint;
/// <summary>
/// 记录开始选取节点控件时的鼠标位置
/// </summary>
private Point startSelectControolPoint;
/// <summary>
/// 记录开始连接的文本块
/// </summary>
//private NodeControlBase? startConnectNodeControl;
/// <summary>
/// 当前正在绘制的连接线
/// </summary>
//private Line? currentLine;
/// <summary>
/// 当前正在绘制的真假分支属性
/// </summary>
//private ConnectionInvokeType currentConnectionType;
/// <summary>
/// 组合变换容器
/// </summary>
private readonly TransformGroup canvasTransformGroup;
/// <summary>
/// 缩放画布
/// </summary>
private readonly ScaleTransform scaleTransform;
/// <summary>
/// 平移画布
/// </summary>
private readonly TranslateTransform translateTransform;
#endregion
public MainWindow()
{
ViewModel = new MainWindowViewModel(this);
this.DataContext = ViewModel;
InitializeComponent();
ViewObjectViewer.FlowEnvironment = EnvDecorator; // 设置 节点树视图 的环境为装饰器
IOCObjectViewer.FlowEnvironment = EnvDecorator; // 设置 IOC容器视图 的环境为装饰器
IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation; // 使选择 IOC容器视图 的某项(对象)时,可以在 数据视图 呈现数据
#region NodeControlType Control类型 ViewModel类型
2025-03-14 16:04:06 +08:00
NodeMVVMManagement.RegisterUI(NodeControlType.UI, typeof(UINodeControl), typeof(UINodeControlViewModel));
2025-01-05 08:52:37 +08:00
NodeMVVMManagement.RegisterUI(NodeControlType.Action, typeof(ActionNodeControl), typeof(ActionNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.Flipflop, typeof(FlipflopNodeControl), typeof(FlipflopNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.ExpOp, typeof(ExpOpNodeControl), typeof(ExpOpNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.ExpCondition, typeof(ConditionNodeControl), typeof(ConditionNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.ConditionRegion, typeof(ConditionRegionControl), typeof(ConditionRegionNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.GlobalData, typeof(GlobalDataControl), typeof(GlobalDataNodeControlViewModel));
NodeMVVMManagement.RegisterUI(NodeControlType.Script, typeof(ScriptNodeControl), typeof(ScriptNodeControlViewModel));
#endregion
#region
canvasTransformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
translateTransform = new TranslateTransform();
canvasTransformGroup.Children.Add(scaleTransform);
canvasTransformGroup.Children.Add(translateTransform);
FlowChartCanvas.RenderTransform = canvasTransformGroup;
#endregion
InitFlowEnvironmentEvent(); // 配置环境事件
}
/// <summary>
/// 初始化环境事件
/// </summary>
private void InitFlowEnvironmentEvent()
{
EnvEventDecorator.OnDllLoad += FlowEnvironment_DllLoadEvent;
EnvEventDecorator.OnProjectSaving += EnvDecorator_OnProjectSaving;
EnvEventDecorator.OnProjectLoaded += FlowEnvironment_OnProjectLoaded;
EnvEventDecorator.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent;
EnvEventDecorator.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt;
EnvEventDecorator.OnNodeCreate += FlowEnvironment_NodeCreateEvent;
EnvEventDecorator.OnNodeRemove += FlowEnvironment_NodeRemoveEvent;
EnvEventDecorator.OnNodePlace += EnvDecorator_OnNodePlaceEvent;
EnvEventDecorator.OnNodeTakeOut += EnvDecorator_OnNodeTakeOutEvent;
EnvEventDecorator.OnFlowRunComplete += FlowEnvironment_OnFlowRunCompleteEvent;
EnvEventDecorator.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChangeEvent;
EnvEventDecorator.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChangeEvent;
EnvEventDecorator.OnInterruptTrigger += FlowEnvironment_OnInterruptTriggerEvent;
EnvEventDecorator.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChangedEvent;
EnvEventDecorator.OnNodeLocated += FlowEnvironment_OnNodeLocateEvent;
EnvEventDecorator.OnNodeMoved += FlowEnvironment_OnNodeMovedEvent;
EnvEventDecorator.OnEnvOut += FlowEnvironment_OnEnvOutEvent;
}
/// <summary>
/// 移除环境事件
/// </summary>
private void ResetFlowEnvironmentEvent()
{
EnvEventDecorator.OnDllLoad -= FlowEnvironment_DllLoadEvent;
EnvEventDecorator.OnProjectSaving -= EnvDecorator_OnProjectSaving;
EnvEventDecorator.OnProjectLoaded -= FlowEnvironment_OnProjectLoaded;
EnvEventDecorator.OnStartNodeChange -= FlowEnvironment_StartNodeChangeEvent;
EnvEventDecorator.OnNodeConnectChange -= FlowEnvironment_NodeConnectChangeEvemt;
EnvEventDecorator.OnNodeCreate -= FlowEnvironment_NodeCreateEvent;
EnvEventDecorator.OnNodeRemove -= FlowEnvironment_NodeRemoveEvent;
EnvEventDecorator.OnNodePlace -= EnvDecorator_OnNodePlaceEvent;
EnvEventDecorator.OnNodeTakeOut -= EnvDecorator_OnNodeTakeOutEvent;
EnvEventDecorator.OnFlowRunComplete -= FlowEnvironment_OnFlowRunCompleteEvent;
EnvEventDecorator.OnMonitorObjectChange -= FlowEnvironment_OnMonitorObjectChangeEvent;
EnvEventDecorator.OnNodeInterruptStateChange -= FlowEnvironment_OnNodeInterruptStateChangeEvent;
EnvEventDecorator.OnInterruptTrigger -= FlowEnvironment_OnInterruptTriggerEvent;
EnvEventDecorator.OnIOCMembersChanged -= FlowEnvironment_OnIOCMembersChangedEvent;
EnvEventDecorator.OnNodeLocated -= FlowEnvironment_OnNodeLocateEvent;
EnvEventDecorator.OnNodeMoved -= FlowEnvironment_OnNodeMovedEvent;
EnvEventDecorator.OnEnvOut -= FlowEnvironment_OnEnvOutEvent;
}
#region
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
var currentPath = System.IO.Directory.GetCurrentDirectory(); // 当前目录
var baseLibraryFilePath = Path.Combine(currentPath, FlowLibraryManagement.SereinBaseLibrary);
if (File.Exists(baseLibraryFilePath))
{
EnvDecorator.LoadLibrary(baseLibraryFilePath); // 默认加载
}
if (App.FlowProjectData is not null)
{
try
{
await Task.Run(() =>
{
EnvDecorator.LoadProject(new FlowEnvInfo { Project = App.FlowProjectData }, App.FileDataPath); // 加载项目
});
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
//
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
LogOutWindow.Close();
System.Windows.Application.Current.Shutdown();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
SereinEnv.WriteLine(InfoType.INFO, "load project...");
var project = App.FlowProjectData;
if (project is null)
{
return;
}
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;
if (canvasData is not null)
{
scaleTransform.ScaleX = 1;
scaleTransform.ScaleY = 1;
translateTransform.X = 0;
translateTransform.Y = 0;
scaleTransform.ScaleX = canvasData.ScaleX;
scaleTransform.ScaleY = canvasData.ScaleY;
translateTransform.X += canvasData.ViewX;
translateTransform.Y += canvasData.ViewY;
// 应用变换组
FlowChartCanvas.RenderTransform = canvasTransformGroup;
}
}
#endregion
#region
/// <summary>
/// 环境内容输出
/// </summary>
/// <param name="type"></param>
/// <param name="value"></param>
private void FlowEnvironment_OnEnvOutEvent(InfoType type, string value)
{
LogOutWindow.AppendText($"{DateTime.Now} [{type}] : {value}{Environment.NewLine}");
}
/// <summary>
/// 需要保存项目
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void EnvDecorator_OnProjectSaving(ProjectSavingEventArgs eventArgs)
{
SereinProjectData projectData;
try
{
projectData = EnvDecorator.GetProjectInfoAsync()
.GetAwaiter().GetResult(); // 保存项目
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
projectData.Basic = new Basic
{
Canvas = new FlowCanvas
{
Height = FlowChartCanvas.Height,
Width = FlowChartCanvas.Width,
ViewX = translateTransform.X,
ViewY = translateTransform.Y,
ScaleX = scaleTransform.ScaleX,
ScaleY = scaleTransform.ScaleY,
},
Versions = "1",
};
// 创建一个新的保存文件对话框
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 < projectData.Librarys.Length; index++)
{
NodeLibraryInfo? library = projectData.Librarys[index];
string sourceFilePath = new Uri(library.FilePath).LocalPath; // 源文件夹
string targetFilePath = System.IO.Path.Combine(librarySavePath, 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);
projectData.Librarys[index].FilePath = relativePath;
}
}
JObject projectJsonData = JObject.FromObject(projectData);
File.WriteAllText(savePath, projectJsonData.ToString());
}
/// <summary>
/// 加载完成
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
{
}
/// <summary>
/// 运行完成
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnFlowRunCompleteEvent(FlowEventArgs eventArgs)
{
SereinEnv.WriteLine(InfoType.INFO, "-------运行完成---------\r\n");
this.Dispatcher.Invoke(() =>
{
IOCObjectViewer.ClearObjItem();
});
}
/// <summary>
/// 加载了DLL文件dll内容
/// </summary>
private void FlowEnvironment_DllLoadEvent(LoadDllEventArgs eventArgs)
{
NodeLibraryInfo nodeLibraryInfo = eventArgs.NodeLibraryInfo;
List<MethodDetailsInfo> methodDetailss = eventArgs.MethodDetailss;
var dllControl = new DllControl(nodeLibraryInfo);
foreach (var methodDetailsInfo in methodDetailss)
{
if (!EnumHelper.TryConvertEnum<Library.NodeType>(methodDetailsInfo.NodeType, out var nodeType))
{
continue;
}
switch (nodeType)
{
case Library.NodeType.Action:
dllControl.AddAction(methodDetailsInfo); // 添加动作类型到控件
break;
case Library.NodeType.Flipflop:
dllControl.AddFlipflop(methodDetailsInfo); // 添加触发器方法到控件
break;
2025-03-14 16:04:06 +08:00
case Library.NodeType.UI:
dllControl.AddUI(methodDetailsInfo); // 添加触发器方法到控件
break;
2025-01-05 08:52:37 +08:00
}
}
var menu = new ContextMenu();
menu.Items.Add(CreateMenuItem("卸载", (s, e) =>
{
if (this.EnvDecorator.TryUnloadLibrary(nodeLibraryInfo.AssemblyName))
{
DllStackPanel.Children.Remove(dllControl);
}
else
{
SereinEnv.WriteLine(InfoType.INFO, "卸载失败");
}
}));
dllControl.ContextMenu = menu;
DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示
}
/// <summary>
/// 节点连接关系变更
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
{
string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid;
if (!TryGetControl(fromNodeGuid, out var fromNodeControl)
|| !TryGetControl(toNodeGuid, out var toNodeControl))
{
return;
}
if (eventArgs.JunctionOfConnectionType == JunctionOfConnectionType.Invoke)
{
ConnectionInvokeType connectionType = eventArgs.ConnectionInvokeType;
#region /
#region
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接
{
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,
connectionType,
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(); // 环境触发了创建节点连接事件
}
#endregion
#region
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接
{
// 需要移除连接
var removeConnections = Connections.Where(c =>
c.Start.MyNode.Guid.Equals(fromNodeGuid)
&& c.End.MyNode.Guid.Equals(toNodeGuid)
&& (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
#endregion
}
else
{
ConnectionArgSourceType connectionArgSourceType = eventArgs.ConnectionArgSourceType;
#region /
#region
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接
{
if (fromNodeControl is not INodeJunction IFormJunction || toNodeControl is not INodeJunction IToJunction)
{
SereinEnv.WriteLine(InfoType.INFO, "非预期的情况");
return;
}
JunctionControlBase startJunction = eventArgs.ConnectionArgSourceType switch
{
ConnectionArgSourceType.GetPreviousNodeData => IFormJunction.ReturnDataJunction, // 自身节点
ConnectionArgSourceType.GetOtherNodeData => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点
ConnectionArgSourceType.GetOtherNodeDataOfInvoke => IFormJunction.ReturnDataJunction, // 其它节点的返回值控制点
_ => throw new Exception("窗体事件 FlowEnvironment_NodeConnectChangeEvemt 创建/删除节点之间的参数传递关系 JunctionControlBase 枚举值错误 。非预期的枚举值。") // 应该不会触发
};
if(IToJunction.ArgDataJunction.Length <= eventArgs.ArgIndex)
{
_ = Task.Run(async () =>
{
await Task.Delay(500);
FlowEnvironment_NodeConnectChangeEvemt(eventArgs);
});
return;
}
JunctionControlBase endJunction = IToJunction.ArgDataJunction[eventArgs.ArgIndex];
LineType lineType = LineType.Bezier;
// 添加连接
var connection = new ConnectionControl(
lineType,
FlowChartCanvas,
eventArgs.ArgIndex,
eventArgs.ConnectionArgSourceType,
startJunction,
endJunction,
IToJunction
);
Connections.Add(connection);
fromNodeControl.AddCnnection(connection);
toNodeControl.AddCnnection(connection);
EndConnection(); // 环境触发了创建节点连接事件
}
#endregion
#region
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remove) // 移除连接
{
// 需要移除连接
var removeConnections = Connections.Where(c => c.Start.MyNode.Guid.Equals(fromNodeGuid)
&& c.End.MyNode.Guid.Equals(toNodeGuid))
.ToList(); // 获取这两个节点之间的所有连接关系
foreach (var connection in removeConnections)
{
if (connection.End is ArgJunctionControl junctionControl && junctionControl.ArgIndex == eventArgs.ArgIndex)
{
// 找到符合删除条件的连接线
Connections.Remove(connection); // 从本地记录中移除
fromNodeControl.RemoveConnection(connection); // 从节点持有的记录移除
toNodeControl.RemoveConnection(connection); // 从节点持有的记录移除
}
//if (NodeControls.TryGetValue(connection.End.MyNode.Guid, out var control))
//{
// JudgmentFlipFlopNode(control); // 连接关系变更时判断
//}
}
}
#endregion
#endregion
}
}
/// <summary>
/// 节点移除事件
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_NodeRemoveEvent(NodeRemoveEventArgs eventArgs)
{
var nodeGuid = eventArgs.NodeGuid;
if (!TryGetControl(nodeGuid, out var nodeControl))
{
return;
}
if (nodeControl is null) return;
if (selectNodeControls.Count > 0)
{
if (selectNodeControls.Contains(nodeControl))
{
selectNodeControls.Remove(nodeControl);
}
}
if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器
{
var node = flipflopControl?.ViewModel?.NodeModel;
if (node is not null)
{
NodeTreeViewer.RemoveGlobalFlipFlop(node); // 从全局触发器树树视图中移除
}
}
FlowChartCanvas.Children.Remove(nodeControl);
nodeControl.RemoveAllConection();
NodeControls.Remove(nodeControl.ViewModel.NodeModel.Guid);
}
/// <summary>
/// 添加节点事件
/// </summary>
/// <param name="eventArgs">添加节点事件参数</param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs)
{
var nodeModel = eventArgs.NodeModel;
if (NodeControls.ContainsKey(nodeModel.Guid))
{
SereinEnv.WriteLine(InfoType.WARN, $"OnNodeCreateEvent 事件接收到意外的返回值节点Guid重复 - {nodeModel.Guid}");
return;
}
PositionOfUI position = eventArgs.Position;
if(!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;
}
var nodeCanvas = FlowChartCanvas;
NodeControlBase nodeControl;
try
{
nodeControl = CreateNodeControl(nodeMVVM.ControlType, // 控件UI类型
nodeMVVM.ViewModelType, // 控件VIewModel类型
nodeModel, // 控件数据实体
nodeCanvas); // 所在画布
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
NodeControls.TryAdd(nodeModel.Guid, nodeControl); // 添加到
if (TryPlaceNodeInRegion(nodeControl, position, out var regionControl)) // 判断添加到区域容器
{
// 通知运行环境调用加载节点子项的方法
_ = EnvDecorator.PlaceNodeToContainerAsync(nodeControl.ViewModel.NodeModel.Guid, // 待移动的节点
regionControl.ViewModel.NodeModel.Guid); // 目标的容器节点
}
else
{
// 并非添加在容器中,直接放置节点
PlaceNodeOnCanvas(nodeControl, position.X, position.Y);
}
#region
if (nodeModel.ControlType == NodeControlType.Flipflop)
{
var node = nodeControl?.ViewModel?.NodeModel;
if (node is not null)
{
NodeTreeViewer.AddGlobalFlipFlop(EnvDecorator, node); // 新增的触发器节点添加到全局触发器
}
}
GC.Collect();
#endregion
}
/// <summary>
/// 放置一个节点
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void EnvDecorator_OnNodePlaceEvent(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); // 放置在容器节点中
}
/// <summary>
/// 取出一个节点
/// </summary>
/// <param name="eventArgs"></param>
private void EnvDecorator_OnNodeTakeOutEvent(NodeTakeOutEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
if (!TryGetControl(nodeGuid, out var nodeControl))
{
return;
}
nodeControl.TakeOutContainer(); // 从容器节点中取出
}
/// <summary>
/// 设置了流程起始控件
/// </summary>
/// <param name="oldNodeGuid"></param>
/// <param name="newNodeGuid"></param>
private void FlowEnvironment_StartNodeChangeEvent(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;
if (node is not null)
{
NodeTreeViewer.LoadNodeTreeOfStartNode(EnvDecorator, node);
}
}
/// <summary>
/// 被监视的对象发生改变
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnMonitorObjectChangeEvent(MonitorObjectEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
string monitorKey = MonitorObjectEventArgs.ObjSourceType.NodeFlowData switch
{
MonitorObjectEventArgs.ObjSourceType.NodeFlowData => nodeGuid,
_ => eventArgs.NewData.GetType().FullName,
};
//NodeControlBase nodeControl = GuidToControl(nodeGuid);
if (ViewObjectViewer.MonitorObj is null) // 如果没有加载过对象
{
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象 ViewObjectViewerControl.MonitorType.Obj
}
else
{
if (monitorKey.Equals(ViewObjectViewer.MonitorKey)) // 相同对象
{
ViewObjectViewer.RefreshObjectTree(eventArgs.NewData); // 刷新
}
else
{
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象
}
}
}
/// <summary>
/// 节点中断状态改变。
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnNodeInterruptStateChangeEvent(NodeInterruptStateChangeEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
if (!TryGetControl(nodeGuid, out var nodeControl)) return;
//if (eventArgs.Class == InterruptClass.None)
//{
// nodeControl.ViewModel.IsInterrupt = false;
//}
//else
//{
// nodeControl.ViewModel.IsInterrupt = true;
//}
if(nodeControl.ContextMenu == null)
{
return;
}
foreach (var menuItem in nodeControl.ContextMenu.Items)
{
if (menuItem is MenuItem menu)
{
if ("取消中断".Equals(menu.Header))
{
menu.Header = "在此中断";
}
else if ("在此中断".Equals(menu.Header))
{
menu.Header = "取消中断";
}
}
}
}
/// <summary>
/// 节点触发了中断
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnInterruptTriggerEvent(InterruptTriggerEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
if (!TryGetControl(nodeGuid, out var nodeControl)) return;
if(eventArgs.Type == InterruptTriggerEventArgs.InterruptTriggerType.Exp)
{
SereinEnv.WriteLine(InfoType.INFO, $"表达式触发了中断:{eventArgs.Expression}");
}
else
{
SereinEnv.WriteLine(InfoType.INFO, $"节点触发了中断:{nodeGuid}");
}
}
/// <summary>
/// IOC变更
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnIOCMembersChangedEvent(IOCMembersChangedEventArgs eventArgs)
{
IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance);
}
/// <summary>
/// 节点需要定位
/// </summary>
/// <param name="eventArgs"></param>
/// <exception cref="NotImplementedException"></exception>
private void FlowEnvironment_OnNodeLocateEvent(NodeLocatedEventArgs eventArgs)
{
if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return;
//scaleTransform.ScaleX = 1;
//scaleTransform.ScaleY = 1;
// 获取控件在 FlowChartCanvas 上的相对位置
Rect controlBounds = VisualTreeHelper.GetDescendantBounds(nodeControl);
Point controlPosition = nodeControl.TransformToAncestor(FlowChartCanvas).Transform(new Point(0, 0));
// 获取控件在画布上的中心点
double controlCenterX = controlPosition.X + controlBounds.Width / 2;
double controlCenterY = controlPosition.Y + controlBounds.Height / 2;
// 考虑缩放因素计算目标位置的中心点
double scaledCenterX = controlCenterX * scaleTransform.ScaleX;
double scaledCenterY = controlCenterY * scaleTransform.ScaleY;
//// 计算画布的可视区域大小
//double visibleAreaLeft = scaledCenterX;
//double visibleAreaTop = scaledCenterY;
//double visibleAreaRight = scaledCenterX + FlowChartStackGrid.ActualWidth;
//double visibleAreaBottom = scaledCenterY + FlowChartStackGrid.ActualHeight;
//// 检查控件中心点是否在可视区域内
//bool isInView = scaledCenterX >= visibleAreaLeft && scaledCenterX <= visibleAreaRight &&
// scaledCenterY >= visibleAreaTop && scaledCenterY <= visibleAreaBottom;
//Console.WriteLine($"isInView :{isInView}");
//if (!isInView)
//{
//}
// 计算平移偏移量,使得控件在可视区域的中心
double translateX = scaledCenterX - FlowChartStackGrid.ActualWidth / 2;
double translateY = scaledCenterY - FlowChartStackGrid.ActualHeight / 2;
var translate = this.translateTransform;
// 应用平移变换
translate.X = 0;
translate.Y = 0;
translate.X -= translateX;
translate.Y -= translateY;
// 设置RenderTransform以实现移动效果
TranslateTransform translateTransform = new TranslateTransform();
nodeControl.RenderTransform = translateTransform;
ElasticAnimation(nodeControl, translateTransform, 4, 1, 0.5);
}
/// <summary>
/// 控件抖动
/// 来源https://www.cnblogs.com/RedSky/p/17705411.html
/// 作者HotSky
/// (……太好用了)
/// </summary>
/// <param name="translate"></param>
/// <param name="nodeControl">需要抖动的控件</param>
/// <param name="power">抖动第一下偏移量</param>
/// <param name="range">减弱幅度小于等于power大于0</param>
/// <param name="speed">持续系数(大于0),越大时间越长,</param>
private static void ElasticAnimation(NodeControlBase nodeControl, TranslateTransform translate, int power, int range = 1, double speed = 1)
{
DoubleAnimationUsingKeyFrames animation1 = new DoubleAnimationUsingKeyFrames();
for (int i = power, j = 1; i >= 0; i -= range)
{
animation1.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed)));
animation1.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed)));
}
translate.BeginAnimation(TranslateTransform.YProperty, animation1);
DoubleAnimationUsingKeyFrames animation2 = new DoubleAnimationUsingKeyFrames();
for (int i = power, j = 1; i >= 0; i -= range)
{
animation2.KeyFrames.Add(new LinearDoubleKeyFrame(-i, TimeSpan.FromMilliseconds(j++ * 100 * speed)));
animation2.KeyFrames.Add(new LinearDoubleKeyFrame(i, TimeSpan.FromMilliseconds(j++ * 100 * speed)));
}
translate.BeginAnimation(TranslateTransform.XProperty, animation2);
animation2.Completed += (s, e) =>
{
nodeControl.RenderTransform = null; // 或者重新设置为默认值
};
}
/// <summary>
/// 节点移动
/// </summary>
/// <param name="eventArgs"></param>
private void FlowEnvironment_OnNodeMovedEvent(NodeMovedEventArgs eventArgs)
{
if (!TryGetControl(eventArgs.NodeGuid, out var nodeControl)) return;
nodeControl.UpdateLocationConnections();
//var newLeft = eventArgs.X;
//var newTop = eventArgs.Y;
//// 限制控件不超出FlowChartCanvas的边界
//if (newLeft >= 0 && newLeft + nodeControl.ActualWidth <= FlowChartCanvas.ActualWidth)
//{
// Canvas.SetLeft(nodeControl, newLeft);
//}
//if (newTop >= 0 && newTop + nodeControl.ActualHeight <= FlowChartCanvas.ActualHeight)
//{
// Canvas.SetTop(nodeControl, newTop);
//}
}
/// <summary>
/// Guid 转 NodeControl
/// </summary>
/// <param name="nodeGuid"></param>
/// <param name="nodeControl"></param>
/// <returns></returns>
private bool TryGetControl(string nodeGuid,out NodeControlBase nodeControl)
{
if (string.IsNullOrEmpty(nodeGuid))
{
nodeControl = null;
return false;
}
if (!NodeControls.TryGetValue(nodeGuid, out nodeControl))
{
nodeControl = null;
return false;
}
if(nodeControl is null)
{
return false;
}
return true;
}
#endregion
#region
/// <summary>
/// 创建了节点,添加到画布。配置默认事件
/// </summary>
/// <param name="nodeControl"></param>
/// <param name="x"></param>
/// <param name="y"></param>
private void PlaceNodeOnCanvas(NodeControlBase nodeControl, double x, double y)
{
// 添加控件到画布
FlowChartCanvas.Children.Add(nodeControl);
Canvas.SetLeft(nodeControl, x);
Canvas.SetTop(nodeControl, y);
ConfigureContextMenu(nodeControl); // 配置节点右键菜单
ConfigureNodeEvents(nodeControl); // 配置节点事件
}
/// <summary>
/// 配置节点事件(移动,点击相关)
/// </summary>
/// <param name="nodeControl"></param>
private void ConfigureNodeEvents(NodeControlBase nodeControl)
{
nodeControl.MouseLeftButtonDown += Block_MouseLeftButtonDown;
nodeControl.MouseMove += Block_MouseMove;
nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp;
}
#endregion
#region
/// <summary>
/// 配置节点右键菜单
/// </summary>
/// <param name="nodeControl">
/// <para> 任何情景下都尽量避免直接修改 ViewModel 中的 NodeModel 节点实体相关数据。</para>
/// <para> 而是应该调用 FlowEnvironment 提供接口进行操作。</para>
/// <para> 因为 Workbench 应该更加关注UI视觉效果而非直接干扰流程环境运行的逻辑。</para>
/// <para> 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。</para>
/// </param>
private void ConfigureContextMenu(NodeControlBase nodeControl)
{
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() == "启动触发器")
{
EnvDecorator.ActivateFlipflopNode(nodeGuid);
menuItem.Header = "终结触发器";
}
else
{
EnvDecorator.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) => EnvDecorator.SetStartNodeAsync(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", async (s, e) =>
{
var result = await EnvDecorator.RemoveNodeAsync(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;
}
/// <summary>
/// 查看返回类型(树形结构展开类型的成员)
/// </summary>
/// <param name="type"></param>
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 DLL文件到左侧功能区
/// <summary>
/// 当拖动文件到窗口时触发加载DLL文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_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"))
{
try
{
EnvDecorator.LoadLibrary(file);
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
}
}
}
/// <summary>
/// 当拖动文件经过窗口时触发,设置拖放效果为复制
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
#endregion
#region /
/// <summary>
/// 鼠标在画布移动。
/// 选择控件状态下,调整选择框大小
/// 连接状态下,实时更新连接线的终点位置。
/// 移动画布状态下,移动画布。
/// </summary>
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
var myData = GlobalJunctionData.MyGlobalConnectingData;
if (myData.IsCreateing && e.LeftButton == MouseButtonState.Pressed)
{
if (myData.Type == JunctionOfConnectionType.Invoke)
{
ViewModel.IsConnectionInvokeNode = true; // 正在连接节点的调用关系
}
else
{
ViewModel.IsConnectionArgSourceNode = true; // 正在连接节点的调用关系
}
var currentPoint = e.GetPosition(FlowChartCanvas);
currentPoint.X -= 2;
currentPoint.Y -= 2;
myData.UpdatePoint(currentPoint);
return;
}
if (IsCanvasDragging && e.MiddleButton == MouseButtonState.Pressed) // 正在移动画布(按住中键)
{
Point currentMousePosition = e.GetPosition(this);
double deltaX = currentMousePosition.X - startCanvasDragPoint.X;
double deltaY = currentMousePosition.Y - startCanvasDragPoint.Y;
translateTransform.X += deltaX;
translateTransform.Y += deltaY;
startCanvasDragPoint = currentMousePosition;
foreach (var line in Connections)
{
line.RefreshLine(); // 画布移动时刷新所有连接线
}
}
if (IsSelectControl) // 正在选取节点
{
IsSelectDragging = e.LeftButton == MouseButtonState.Pressed;
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(FlowChartCanvas);
// 更新选取矩形的位置和大小
double x = Math.Min(currentPoint.X, startSelectControolPoint.X);
double y = Math.Min(currentPoint.Y, startSelectControolPoint.Y);
double width = Math.Abs(currentPoint.X - startSelectControolPoint.X);
double height = Math.Abs(currentPoint.Y - startSelectControolPoint.Y);
Canvas.SetLeft(SelectionRectangle, x);
Canvas.SetTop(SelectionRectangle, y);
SelectionRectangle.Width = width;
SelectionRectangle.Height = height;
}
}
/// <summary>
/// 基础节点的拖拽放置创建
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void BaseNodeControl_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (sender is UserControl control)
{
if(e.LeftButton == MouseButtonState.Pressed)
{
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
var dragData = new DataObject(MouseNodeType.CreateBaseNodeInCanvas, control.GetType());
try
{
DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move);
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
}
2025-01-05 08:52:37 +08:00
}
}
}
/// <summary>
/// 放置操作,根据拖放数据创建相应的控件,并处理相关操作
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_Drop(object sender, DragEventArgs e)
{
try
{
var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
PositionOfUI position = new PositionOfUI(canvasDropPosition.X, canvasDropPosition.Y);
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
{
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
{
Task.Run(async () =>
{
await EnvDecorator.CreateNodeAsync(nodeData.NodeControlType, position, nodeData.MethodDetailsInfo); // 创建DLL文件的节点对象
});
}
}
else if (e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas))
{
if (e.Data.GetData(MouseNodeType.CreateBaseNodeInCanvas) is Type droppedType)
{
NodeControlType nodeControlType = droppedType switch
{
Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType) => NodeControlType.ConditionRegion, // 条件区域
Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpCondition,
Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType) => NodeControlType.ExpOp,
Type when typeof(GlobalDataControl).IsAssignableFrom(droppedType) => NodeControlType.GlobalData,
Type when typeof(ScriptNodeControl).IsAssignableFrom(droppedType) => NodeControlType.Script,
_ => NodeControlType.None,
};
if (nodeControlType != NodeControlType.None)
{
Task.Run(async () =>
{
await EnvDecorator.CreateNodeAsync(nodeControlType, position); // 创建基础节点对象
});
}
}
}
e.Handled = true;
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.ERROR, ex.ToString());
}
}
/// <summary>
/// 尝试判断是否为区域,如果是,将节点放置在区域中
/// </summary>
/// <param name="nodeControl"></param>
/// <param name="position"></param>
/// <param name="targetNodeControl">目标节点控件</param>
/// <returns></returns>
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<ConditionRegionControl>(hitElement);
if (conditionRegion is not null)
{
targetNodeControl = conditionRegion;
//// 如果存在条件区域容器
//conditionRegion.AddCondition(nodeControl);
return true;
}
}
else
{
// 准备放置全局数据控件
GlobalDataControl? globalDataControl = GetParentOfType<GlobalDataControl>(hitElement);
if (globalDataControl is not null)
{
targetNodeControl = globalDataControl;
return true;
}
}
}
targetNodeControl = null;
return false;
}
///// <summary>
///// 将节点放在目标区域中
///// </summary>
///// <param name="regionControl">区域容器</param>
///// <param name="nodeControl">节点控件</param>
//private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl)
//{
// // 准备放置条件表达式控件
// if (nodeControl.ViewModel.NodeModel.ControlType == NodeControlType.ExpCondition)
// {
// if (regionControl is ConditionRegionControl conditionRegion)
// {
// conditionRegion.AddCondition(nodeControl); // 条件区域容器
// }
// }
// else if(regionControl.ViewModel.NodeModel.ControlType == NodeControlType.GlobalData)
// {
// if (regionControl is GlobalDataControl globalDataControl)
// {
// // 全局数据节点容器
// globalDataControl.SetDataNodeControl(nodeControl);
// }
// }
//}
/// <summary>
/// 拖动效果,根据拖放数据是否为指定类型设置拖放效果
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_DragOver(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas)
|| e.Data.GetDataPresent(MouseNodeType.CreateBaseNodeInCanvas))
{
e.Effects = DragDropEffects.Move;
}
else
{
e.Effects = DragDropEffects.None;
}
e.Handled = true;
}
/// <summary>
/// 控件的鼠标左键按下事件,启动拖动操作。同时显示当前正在传递的数据。
/// </summary>
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//if (GlobalJunctionData.IsCreatingConnection)
//{
// return;
//}
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; // 防止事件传播影响其他控件
}
}
/// <summary>
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
/// </summary>
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.EnvDecorator.MoveNode(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.EnvDecorator.MoveNode(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.EnvDecorator.MoveNode(nodeControl.ViewModel.NodeModel.Guid, newLeft, newTop); // 移动节点
nodeControl.UpdateLocationConnections();
}
startControlDragPoint = currentPosition; // 更新起始点位置
}
}
// 改变对象树?
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
{
var node = nodeControl.ViewModel.NodeModel;
//if (node is not null && (node.MethodDetails is null || node.MethodDetails.ReturnType != typeof(void))
if (node is not null && node.MethodDetails?.ReturnType != typeof(void))
{
var key = node.Guid;
object instance = null;
//Console.WriteLine("WindowXaml 后台代码中 ChangeViewerObjOfNode 需要重新设计");
//var instance = node.GetFlowData(); // 对象预览树视图获取(后期更改)
if(instance is not null)
{
ViewObjectViewer.LoadObjectInformation(key, instance);
ChangeViewerObj(key, instance);
}
}
}
public void ChangeViewerObj(string key, object instance)
{
if (ViewObjectViewer.MonitorObj is null)
{
// EnvDecorator.SetMonitorObjState(key, true); // 通知环境该节点的数据更新后需要传到UI
return;
}
if (instance is null)
{
return;
}
if (key.Equals(ViewObjectViewer.MonitorKey) == true)
{
ViewObjectViewer.RefreshObjectTree(instance);
return;
}
else
{
//EnvDecorator.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视
//EnvDecorator.SetMonitorObjState(key, true); // 通知环境该节点的数据更新后需要传到UI
}
}
#endregion
#region UI连接控件操作
/// <summary>
/// 控件的鼠标左键松开事件,结束拖动操作
/// </summary>
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;
// }
// EnvDecorator.ConnectNodeAsync(formNodeGuid, toNodeGuid,0,0, currentConnectionType);
//}
//GlobalJunctionData.OK();
}
/// <summary>
/// 结束连接操作,清理状态并移除虚线。
/// </summary>
private void EndConnection()
{
Mouse.OverrideCursor = null; // 恢复视觉效果
ViewModel.IsConnectionArgSourceNode = false;
ViewModel.IsConnectionInvokeNode = false;
GlobalJunctionData.OK();
}
#region
private void FlowChartCanvas_MouseDown(object sender, MouseButtonEventArgs e)
{
IsCanvasDragging = true;
startCanvasDragPoint = e.GetPosition(this);
FlowChartCanvas.CaptureMouse();
e.Handled = true; // 防止事件传播影响其他控件
}
private void FlowChartCanvas_MouseUp(object sender, MouseButtonEventArgs e)
{
if (IsCanvasDragging)
{
IsCanvasDragging = false;
FlowChartCanvas.ReleaseMouseCapture();
}
}
// 单纯缩放画布,不改变画布大小
private void FlowChartCanvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
// if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
if (e.Delta < 0 && scaleTransform.ScaleX < 0.05) return;
if (e.Delta > 0 && scaleTransform.ScaleY > 2.0) return;
// 获取鼠标在 Canvas 内的相对位置
var mousePosition = e.GetPosition(FlowChartCanvas);
// 缩放因子,根据滚轮方向调整
//double zoomFactor = e.Delta > 0 ? 0.1 : -0.1;
double zoomFactor = e.Delta > 0 ? 1.1 : 0.9;
// 当前缩放比例
double oldScale = scaleTransform.ScaleX;
double newScale = oldScale * zoomFactor;
//double newScale = oldScale + zoomFactor;
// 更新缩放比例
scaleTransform.ScaleX = newScale;
scaleTransform.ScaleY = newScale;
// 计算缩放前后鼠标相对于 Canvas 的位置差异
// double offsetX = mousePosition.X - (mousePosition.X * zoomFactor);
// double offsetY = mousePosition.Y - (mousePosition.Y * zoomFactor);
// 更新 TranslateTransform确保以鼠标位置为中心进行缩放
translateTransform.X -= (mousePosition.X * (newScale - oldScale));
translateTransform.Y -= (mousePosition.Y * (newScale - oldScale));
}
}
// 设置画布宽度高度
private void InitializeCanvas(double width, double height)
{
FlowChartCanvas.Width = width;
FlowChartCanvas.Height = height;
}
#region
//private void Thumb_DragDelta_TopLeft(object sender, DragDeltaEventArgs e)
//{
// // 从左上角调整大小
// double newWidth = Math.Max(FlowChartCanvas.ActualWidth - e.HorizontalChange, 0);
// double newHeight = Math.Max(FlowChartCanvas.ActualHeight - e.VerticalChange, 0);
// FlowChartCanvas.Width = newWidth;
// FlowChartCanvas.Height = newHeight;
// Canvas.SetLeft(FlowChartCanvas, Canvas.GetLeft(FlowChartCanvas) + e.HorizontalChange);
// Canvas.SetTop(FlowChartCanvas, Canvas.GetTop(FlowChartCanvas) + e.VerticalChange);
//}
//private void Thumb_DragDelta_TopRight(object sender, DragDeltaEventArgs e)
//{
// // 从右上角调整大小
// double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange, 0);
// double newHeight = Math.Max(FlowChartCanvas.ActualHeight - e.VerticalChange, 0);
// FlowChartCanvas.Width = newWidth;
// FlowChartCanvas.Height = newHeight;
// Canvas.SetTop(FlowChartCanvas, Canvas.GetTop(FlowChartCanvas) + e.VerticalChange);
//}
//private void Thumb_DragDelta_BottomLeft(object sender, DragDeltaEventArgs e)
//{
// // 从左下角调整大小
// double newWidth = Math.Max(FlowChartCanvas.ActualWidth - e.HorizontalChange, 0);
// double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange, 0);
// FlowChartCanvas.Width = newWidth;
// FlowChartCanvas.Height = newHeight;
// Canvas.SetLeft(FlowChartCanvas, Canvas.GetLeft(FlowChartCanvas) + e.HorizontalChange);
//}
private void Thumb_DragDelta_BottomRight(object sender, DragDeltaEventArgs e)
{
// 获取缩放后的水平和垂直变化
double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX;
double verticalChange = e.VerticalChange * scaleTransform.ScaleY;
// 计算新的宽度和高度确保不会小于400
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400);
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400);
newHeight = newHeight < 400 ? 400 : newHeight;
newWidth = newWidth < 400 ? 400 : newWidth;
InitializeCanvas(newWidth, newHeight);
//// 从右下角调整大小
//double newWidth = Math.Max(FlowChartCanvas.ActualWidth + e.HorizontalChange * scaleTransform.ScaleX, 0);
//double newHeight = Math.Max(FlowChartCanvas.ActualHeight + e.VerticalChange * scaleTransform.ScaleY, 0);
//newWidth = newWidth < 400 ? 400 : newWidth;
//newHeight = newHeight < 400 ? 400 : newHeight;
//if (newWidth > 400 && newHeight > 400)
//{
// FlowChartCanvas.Width = newWidth;
// FlowChartCanvas.Height = newHeight;
// double x = e.HorizontalChange > 0 ? -0.5 : 0.5;
// double y = e.VerticalChange > 0 ? -0.5 : 0.5;
// double deltaX = x * scaleTransform.ScaleX;
// double deltaY = y * scaleTransform.ScaleY;
// Test(deltaX, deltaY);
//}
}
//private void Thumb_DragDelta_Left(object sender, DragDeltaEventArgs e)
//{
// // 从左侧调整大小
// double newWidth = Math.Max(FlowChartCanvas.ActualWidth - e.HorizontalChange, 0);
// FlowChartCanvas.Width = newWidth;
// Canvas.SetLeft(FlowChartCanvas, Canvas.GetLeft(FlowChartCanvas) + e.HorizontalChange);
//}
private void Thumb_DragDelta_Right(object sender, DragDeltaEventArgs e)
{
//从右侧调整大小
// 获取缩放后的水平变化
double horizontalChange = e.HorizontalChange * scaleTransform.ScaleX;
// 计算新的宽度确保不会小于400
double newWidth = Math.Max(FlowChartCanvas.ActualWidth + horizontalChange, 400);
newWidth = newWidth < 400 ? 400 : newWidth;
InitializeCanvas(newWidth, FlowChartCanvas.Height);
}
//private void Thumb_DragDelta_Top(object sender, DragDeltaEventArgs e)
//{
// // 从顶部调整大小
// double newHeight = Math.Max(FlowChartCanvas.ActualHeight - e.VerticalChange, 0);
// FlowChartCanvas.Height = newHeight;
// Canvas.SetTop(FlowChartCanvas, Canvas.GetTop(FlowChartCanvas) + e.VerticalChange);
//}
private void Thumb_DragDelta_Bottom(object sender, DragDeltaEventArgs e)
{
// 获取缩放后的垂直变化
double verticalChange = e.VerticalChange * scaleTransform.ScaleY;
// 计算新的高度确保不会小于400
double newHeight = Math.Max(FlowChartCanvas.ActualHeight + verticalChange, 400);
newHeight = newHeight < 400 ? 400 : newHeight;
InitializeCanvas(FlowChartCanvas.Width, newHeight);
}
private void Test(double deltaX, double deltaY)
{
//Console.WriteLine((translateTransform.X, translateTransform.Y));
//translateTransform.X += deltaX;
//translateTransform.Y += deltaY;
}
#endregion
#endregion
#endregion
#region
/// <summary>
/// 在画布中尝试选取控件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (GlobalJunctionData.MyGlobalConnectingData.IsCreateing)
{
return;
}
if (!IsSelectControl)
{
// 进入选取状态
IsSelectControl = true;
IsSelectDragging = false; // 初始化为非拖动状态
// 记录鼠标起始点
startSelectControolPoint = e.GetPosition(FlowChartCanvas);
// 初始化选取矩形的位置和大小
Canvas.SetLeft(SelectionRectangle, startSelectControolPoint.X);
Canvas.SetTop(SelectionRectangle, startSelectControolPoint.Y);
SelectionRectangle.Width = 0;
SelectionRectangle.Height = 0;
// 显示选取矩形
SelectionRectangle.Visibility = Visibility.Visible;
SelectionRectangle.ContextMenu ??= ConfiguerSelectionRectangle();
// 捕获鼠标以便在鼠标移动到Canvas外部时仍能处理事件
FlowChartCanvas.CaptureMouse();
}
else
{
// 如果已经是选取状态,单击则认为结束框选
CompleteSelection();
}
e.Handled = true; // 防止事件传播影响其他控件
}
/// <summary>
/// 在画布中释放鼠标按下,结束选取状态 / 停止创建连线,尝试连接节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsSelectControl)
{
// 松开鼠标时判断是否为拖动操作
if (IsSelectDragging)
{
// 完成拖动框选
CompleteSelection();
}
// 释放鼠标捕获
FlowChartCanvas.ReleaseMouseCapture();
}
// 创建连线
if (GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
{
if (myData.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));
if (changingJunctionRect.Contains(currentendPoint)) // 可以创建连接
{
#region
if (myData.Type == JunctionOfConnectionType.Invoke)
{
this.EnvDecorator.ConnectInvokeNodeAsync(myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid,
myData.StartJunction.JunctionType,
myData.CurrentJunction.JunctionType,
myData.ConnectionInvokeType);
}
#endregion
#region
else if (myData.Type == JunctionOfConnectionType.Arg)
{
var argIndex = 0;
if (myData.StartJunction is ArgJunctionControl argJunction1)
{
argIndex = argJunction1.ArgIndex;
}
else if (myData.CurrentJunction is ArgJunctionControl argJunction2)
{
argIndex = argJunction2.ArgIndex;
}
this.EnvDecorator.ConnectArgSourceNodeAsync(myData.StartJunction.MyNode.Guid, myData.CurrentJunction.MyNode.Guid,
myData.StartJunction.JunctionType,
myData.CurrentJunction.JunctionType,
myData.ConnectionArgSourceType,
argIndex);
}
#endregion
}
EndConnection();
}
}
e.Handled = true;
}
/// 完成选取操作
/// </summary>
private void CompleteSelection()
{
IsSelectControl = false;
// 隐藏选取矩形
SelectionRectangle.Visibility = Visibility.Collapsed;
// 获取选取范围
Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle),
Canvas.GetTop(SelectionRectangle),
SelectionRectangle.Width,
SelectionRectangle.Height);
// 处理选取范围内的控件
// selectNodeControls.Clear();
foreach (UIElement element in FlowChartCanvas.Children)
{
Rect elementBounds = new Rect(Canvas.GetLeft(element), Canvas.GetTop(element),
element.RenderSize.Width, element.RenderSize.Height);
if (selectionArea.Contains(elementBounds))
{
if (element is NodeControlBase control)
{
if (!selectNodeControls.Contains(control))
{
selectNodeControls.Add(control);
}
}
}
}
// 选中后的操作
SelectedNode();
}
private ContextMenu ConfiguerSelectionRectangle()
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) =>
{
if (selectNodeControls.Count > 0)
{
foreach (var node in selectNodeControls.ToArray())
{
var guid = node?.ViewModel?.NodeModel?.Guid;
if (!string.IsNullOrEmpty(guid))
{
EnvDecorator.RemoveNodeAsync(guid);
}
}
}
SelectionRectangle.Visibility = Visibility.Collapsed;
}));
return contextMenu;
// nodeControl.ContextMenu = contextMenu;
}
private void SelectedNode()
{
if (selectNodeControls.Count == 0)
{
//Console.WriteLine($"没有选择控件");
SelectionRectangle.Visibility = Visibility.Collapsed;
return;
}
if(selectNodeControls.Count == 1)
{
// ChangeViewerObjOfNode(selectNodeControls[0]);
}
//Console.WriteLine($"一共选取了{selectNodeControls.Count}个控件");
foreach (var node in selectNodeControls)
{
//node.ViewModel.IsSelect =true;
// node.ViewModel.CancelSelect();
node.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FFC700"));
node.BorderThickness = new Thickness(4);
}
}
private void CancelSelectNode()
{
IsSelectControl = false;
foreach (var nodeControl in selectNodeControls)
{
//nodeControl.ViewModel.IsSelect = false;
nodeControl.BorderBrush = Brushes.Black;
nodeControl.BorderThickness = new Thickness(0);
if (nodeControl.ViewModel.NodeModel.IsStart)
{
nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
nodeControl.BorderThickness = new Thickness(2);
}
}
selectNodeControls.Clear();
}
#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<NodeControlBase> 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<List<NodeControlBase>> GroupByProximity(List<NodeControlBase> controls, double proximityThreshold, bool isHorizontal)
{
var groups = new List<List<NodeControlBase>>();
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<NodeControlBase> { control });
}
}
return groups;
}
// 判断控件是否接近某个群组
private bool IsInProximity(List<NodeControlBase> 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<NodeControlBase> 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<NodeControlBase> 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<NodeControlBase> 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
{
/// <summary>
/// 水平对齐
/// </summary>
Horizontal,
/// <summary>
/// 垂直对齐
/// </summary>
Vertical,
/// <summary>
/// 水平中心对齐
/// </summary>
HorizontalCenter,
/// <summary>
/// 垂直中心对齐
/// </summary>
VerticalCenter,
/// <summary>
/// 规划对齐
/// </summary>
Planning,
/// <summary>
/// 群组对齐
/// </summary>
Grouping,
}
public void AlignControlsWithGrouping(List<NodeControlBase> 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<NodeControlBase> 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<NodeControlBase> 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<NodeControlBase> 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
#region
/// <summary>
/// 创建节点控件
/// </summary>
/// <param name="controlType">节点控件视图控件类型</param>
/// <param name="viewModelType">节点控件ViewModel类型</param>
/// <param name="model">节点Model实例</param>
/// <param name="nodeCanvas">节点所在画布</param>
/// <returns></returns>
/// <exception cref="Exception">无法创建节点控件</exception>
private static NodeControlBase CreateNodeControl(Type controlType, Type viewModelType, NodeModelBase model, Canvas 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.NodeCanvas = nodeCanvas;
return nodeControl;
}
else
{
throw new Exception("无法创建节点控件");
}
}
/// <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;
}
#endregion
#region IOC视图管理
private void JudgmentFlipFlopNode(NodeControlBase nodeControl)
{
if (nodeControl is FlipflopNodeControl flipflopControl
&& flipflopControl?.ViewModel?.NodeModel is NodeModelBase nodeModel) // 判断是否为触发器
{
int count = 0;
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
count += nodeModel.PreviousNodes[ct].Count;
}
if (count == 0)
{
NodeTreeViewer.AddGlobalFlipFlop(EnvDecorator, nodeModel); // 添加到全局触发器树树视图
}
else
{
NodeTreeViewer.RemoveGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
}
}
}
void LoadIOCObjectViewer()
{
}
#endregion
#region -
/// <summary>
/// 运行测试
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
{
LogOutWindow?.Show();
#if WINDOWS
//Dispatcher uiDispatcher = Application.Current.MainWindow.Dispatcher;
//SynchronizationContext? uiContext = SynchronizationContext.Current;
//EnvDecorator.IOC.CustomRegisterInstance(typeof(SynchronizationContextk).FullName, uiContext, false);
#endif
// 获取主线程的 SynchronizationContext
Action<SynchronizationContext, Action> uiInvoke = (uiContext, action) => uiContext?.Post(state => action?.Invoke(), null);
SereinEnv.WriteLine(InfoType.INFO, "流程开始运行");
try
{
await EnvDecorator.StartFlowAsync();
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
// await EnvDecorator.StartAsync();
//await Task.Factory.StartNew(FlowEnvironment.StartAsync);
}
/// <summary>
/// 退出
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
{
try
{
await EnvDecorator.ExitFlowAsync(); // 在运行平台上点击了退出
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
/// <summary>
/// 从选定的节点开始运行
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void ButtonStartFlowInSelectNode_Click(object sender, RoutedEventArgs e)
{
if (selectNodeControls.Count == 0)
{
SereinEnv.WriteLine(InfoType.INFO, "请至少选择一个节点");
}
else if (selectNodeControls.Count > 1)
{
SereinEnv.WriteLine(InfoType.INFO, "请只选择一个节点");
}
try
{
await this.EnvDecorator.StartAsyncInSelectNode(selectNodeControls[0].ViewModel.NodeModel.Guid);
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
#endregion
#region -
/// <summary>
/// 保存为项目文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void ButtonSaveFile_Click(object sender, RoutedEventArgs e)
{
try
{
EnvDecorator.SaveProject();
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
/// <summary>
/// 打开本地项目文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonOpenLocalProject_Click(object sender, RoutedEventArgs e)
{
}
#endregion
#region -
/// <summary>
/// 重置画布
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e)
{
translateTransform.X = 0;
translateTransform.Y = 0;
scaleTransform.ScaleX = 1;
scaleTransform.ScaleY = 1;
}
/// <summary>
/// 查看输出日志窗口
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonOpenConsoleOutWindow_Click(object sender, RoutedEventArgs e)
{
LogOutWindow?.Show();
}
/// <summary>
/// 定位节点
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonLocationNode_Click(object sender, RoutedEventArgs e)
{
InputDialog inputDialog = new InputDialog();
inputDialog.Closed += (s, e) =>
{
var nodeGuid = inputDialog.InputValue;
EnvDecorator.NodeLocated(nodeGuid);
};
inputDialog.ShowDialog();
}
#endregion
#region -
private async void ButtonStartRemoteServer_Click(object sender, RoutedEventArgs e)
{
try
{
await this.EnvDecorator.StartRemoteServerAsync();
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
}
/// <summary>
/// 连接远程运行环境
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonConnectionRemoteEnv_Click(object sender, RoutedEventArgs e)
{
var windowEnvRemoteLoginView = new WindowEnvRemoteLoginView(async (addres, port, token) =>
{
ResetFlowEnvironmentEvent();// 移除事件
(var isConnect, var _) = await this.EnvDecorator.ConnectRemoteEnv(addres, port, token);
InitFlowEnvironmentEvent(); // 重新添加事件(如果没有连接成功,那么依然是原本的环境)
if (isConnect)
{
// 连接成功,加载远程项目
_ = Task.Run(async () =>
{
try
{
var flowEnvInfo = await EnvDecorator.GetEnvInfoAsync();
EnvDecorator.LoadProject(flowEnvInfo, string.Empty);// 加载远程环境的项目
}
catch (Exception ex)
{
SereinEnv.WriteLine(ex);
return;
}
});
}
});
windowEnvRemoteLoginView.Show();
}
#endregion
/// <summary>
/// 窗体按键监听。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
e.Handled = true; // 禁止默认的Tab键行为
}
#region
if (Keyboard.Modifiers == ModifierKeys.Control)
{
if (e.Key == Key.C && selectNodeControls.Count > 0)
{
CpoyNodeInfo();
}
else if (e.Key == Key.V)
{
PasteNodeInfo();
}
}
#endregion
if (e.KeyStates == Keyboard.GetKeyStates(Key.Escape))
{
IsControlDragging = false;
IsCanvasDragging = false;
SelectionRectangle.Visibility = Visibility.Collapsed;
CancelSelectNode();
EndConnection();
}
if(GlobalJunctionData.MyGlobalConnectingData is ConnectingData myData && myData.IsCreateing)
{
if(myData.Type == JunctionOfConnectionType.Invoke)
{
ConnectionInvokeType connectionInvokeType = e.KeyStates switch
{
KeyStates k when k == Keyboard.GetKeyStates(Key.D1) => ConnectionInvokeType.Upstream,
KeyStates k when k == Keyboard.GetKeyStates(Key.D2) => ConnectionInvokeType.IsSucceed,
KeyStates k when k == Keyboard.GetKeyStates(Key.D3) => ConnectionInvokeType.IsFail,
KeyStates k when k == Keyboard.GetKeyStates(Key.D4) => ConnectionInvokeType.IsError,
_ => ConnectionInvokeType.None,
};
if (connectionInvokeType != ConnectionInvokeType.None)
{
myData.ConnectionInvokeType = connectionInvokeType;
myData.MyLine.Line.UpdateLineColor(connectionInvokeType.ToLineColor());
}
}
else if (myData.Type == JunctionOfConnectionType.Arg)
{
ConnectionArgSourceType connectionArgSourceType = e.KeyStates switch
{
KeyStates k when k == Keyboard.GetKeyStates(Key.D1) => ConnectionArgSourceType.GetOtherNodeData,
KeyStates k when k == Keyboard.GetKeyStates(Key.D2) => ConnectionArgSourceType.GetOtherNodeDataOfInvoke,
_ => ConnectionArgSourceType.GetPreviousNodeData,
};
if (connectionArgSourceType != ConnectionArgSourceType.GetPreviousNodeData)
{
myData.ConnectionArgSourceType = connectionArgSourceType;
myData.MyLine.Line.UpdateLineColor(connectionArgSourceType.ToLineColor());
}
}
myData.CurrentJunction.InvalidateVisual(); // 刷新目标节点控制点样式
}
}
#region
/// <summary>
/// 复制节点
/// </summary>
private void CpoyNodeInfo()
{
if(selectNodeControls.Count == 0)
{
return;
}
// 处理复制操作
var dictSelection = selectNodeControls
.Select(control => control.ViewModel.NodeModel).ToList();
// 遍历当前已选节点
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
{
//Clipboard.SetDataObject(result, true); // 持久性设置
Clipboard.SetDataObject(jsonText, true); // 持久性设置
SereinEnv.WriteLine(InfoType.INFO, $"复制已选节点({dictSelection.Count}个)");
}
catch (Exception ex)
{
SereinEnv.WriteLine(InfoType.ERROR, $"复制失败:{ex.Message}");
}
}
/// <summary>
/// 粘贴节点
/// </summary>
private void PasteNodeInfo()
{
if (Clipboard.ContainsText())
{
try
{
string clipboardText = Clipboard.GetText(TextDataFormat.Text);
string jsonText = JObject.Parse(clipboardText)["nodes"].ToString();
List<NodeInfo> nodes = JsonConvert.DeserializeObject<List<NodeInfo>>(jsonText);
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);
}
}
}
//var flashText = new FlashText.NET.TextReplacer();
//var t = guids.Select(kvp => (kvp.Key, kvp.Value)).ToArray();
//var result = flashText.ReplaceWords(jsonText, t);
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
Point mousePosition = Mouse.GetPosition(FlowChartCanvas);
PositionOfUI positionOfUI = new PositionOfUI(mousePosition.X, mousePosition.Y); // 坐标数据
// 获取第一个节点的原始位置
var index0NodeX = nodes[0].Position.X;
var index0NodeY = nodes[0].Position.Y;
// 计算所有节点相对于第一个节点的偏移量
foreach (var node in nodes)
{
var offsetX = node.Position.X - index0NodeX;
var offsetY = node.Position.Y - index0NodeY;
// 根据鼠标位置平移节点
node.Position = new PositionOfUI(positionOfUI.X + offsetX, positionOfUI.Y + offsetY);
}
_ = EnvDecorator.LoadNodeInfosAsync(nodes);
}
catch (Exception ex)
{
//SereinEnv.WriteLine(InfoType.ERROR, $"粘贴节点时发生异常:{ex}");
}
// SereinEnv.WriteLine(InfoType.INFO, $"剪贴板文本内容: {clipboardText}");
}
else if (Clipboard.ContainsImage())
{
// var image = Clipboard.GetImage();
}
else
{
SereinEnv.WriteLine(InfoType.INFO, "剪贴板中没有可识别的数据。");
}
}
#endregion
/* /// <summary>
/// 对象装箱测试
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ButtonTestExpObj_Click(object sender, RoutedEventArgs e)
{
//string jsonString =
//"""
//{
// "Name": "张三",
// "Age": 24,
// "Address": {
// "City": "北京",
// "PostalCode": "10000"
// }
//}
//""";
var externalData = new Dictionary<string, object>
{
{ "Name", "John" },
{ "Age", 30 },
{ "Addresses", new List<Dictionary<string, object>>
{
new Dictionary<string, object>
{
{ "Street", "123 Main St" },
{ "City", "New York" }
},
new Dictionary<string, object>
{
{ "Street", "456 Another St" },
{ "City", "Los Angeles" }
}
}
}
};
if (!ObjDynamicCreateHelper.TryResolve(externalData, "RootType",out var result))
{
SereinEnv.WriteLine(InfoType.ERROR, "赋值过程中有错误,请检查属性名和类型!");
return;
}
ObjDynamicCreateHelper.PrintObjectProperties(result!);
var exp = "@set .Addresses[1].Street = 233";
var data = SerinExpressionEvaluator.Evaluate(exp, result!, out bool isChange);
exp = "@get .Addresses[1].Street";
data = SerinExpressionEvaluator.Evaluate(exp,result!, out isChange);
SereinEnv.WriteLine(InfoType.INFO, $"{exp} => {data}");
}
*/
}
}