using Microsoft.Win32;
using Newtonsoft.Json.Linq;
using Serein.Library.Api;
using Serein.Library.Entity;
using Serein.Library.Enums;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Base;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Tool;
using Serein.NodeFlow.Tool.SereinExpression;
using Serein.WorkBench.Node;
using Serein.WorkBench.Node.View;
using Serein.WorkBench.Node.ViewModel;
using Serein.WorkBench.Themes;
using Serein.WorkBench.tool;
using System.IO;
using System.Reflection;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml.Linq;
using DataObject = System.Windows.DataObject;
namespace Serein.WorkBench
{
///
/// 拖拽创建节点类型
///
public static class MouseNodeType
{
public static string CreateDllNodeInCanvas { get; } = nameof(CreateDllNodeInCanvas);
public static string CreateBaseNodeInCanvas { get; } = nameof(CreateBaseNodeInCanvas);
//public static string RegionType { get; } = nameof(RegionType);
//public static string BaseNodeType { get; } = nameof(BaseNodeType);
//public static string DllNodeType { get; } = nameof(DllNodeType);
}
///
/// Interaction logic for MainWindow.xaml,第一次用git,不太懂
///
public partial class MainWindow : Window
{
///
/// 全局捕获Console输出事件,打印在这个窗体里面
///
private readonly LogWindow logWindow;
///
/// 流程运行环境
///
private IFlowEnvironment FlowEnvironment { get; }
private MainWindowViewModel ViewModel { get; set; }
///
/// 存储所有与节点有关的控件
/// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,
/// 而是应该调用 FlowEnvironment 提供接口进行操作,
/// 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。
/// 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
///
private Dictionary NodeControls { get; } = [];
///
/// 存储所有的连接。考虑集成在运行环境中。
///
private List Connections { get; } = [];
///
/// 起始节点
///
//private NodeControlBase StartNodeControl{ get; set; }
#region 与画布相关的字段
///
/// 标记是否正在尝试选取控件
///
private bool IsSelectControl;
///
/// 标记是否正在进行连接操作
///
private bool IsConnecting;
///
/// 标记是否正在拖动控件
///
private bool IsControlDragging;
///
/// 标记是否正在拖动画布
///
private bool IsCanvasDragging;
private bool IsSelectDragging;
///
/// 当前选取的控件
///
private readonly List selectNodeControls = [];
///
/// 记录开始拖动节点控件时的鼠标位置
///
private Point startControlDragPoint;
///
/// 记录移动画布开始时的鼠标位置
///
private Point startCanvasDragPoint;
///
/// 记录开始选取节点控件时的鼠标位置
///
private Point startSelectControolPoint;
///
/// 记录开始连接的文本块
///
private NodeControlBase? startConnectNodeControl;
///
/// 当前正在绘制的连接线
///
private Line? currentLine;
///
/// 当前正在绘制的真假分支属性
///
private ConnectionType currentConnectionType;
///
/// 组合变换容器
///
private TransformGroup canvasTransformGroup;
///
/// 缩放画布
///
private ScaleTransform scaleTransform;
///
/// 平移画布
///
private TranslateTransform translateTransform;
#endregion
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel(this);
FlowEnvironment = ViewModel.FlowEnvironment;
ViewObjectViewer.FlowEnvironment = FlowEnvironment;
IOCObjectViewer.FlowEnvironment = FlowEnvironment;
InitFlowEnvironmentEvent(); // 配置环境事件
logWindow = InitConsoleOut(); // 重定向 Console 输出
InitCanvasUI(); // 配置画布
if(App.FlowProjectData is not null)
{
FlowEnvironment.LoadProject(App.FlowProjectData, App.FileDataPath); // 加载项目
}
IOCObjectViewer.SelectObj += ViewObjectViewer.LoadObjectInformation;
}
private void InitFlowEnvironmentEvent()
{
FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent;
// FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent;
FlowEnvironment.OnProjectLoaded += FlowEnvironment_OnProjectLoaded;
FlowEnvironment.OnStartNodeChange += FlowEnvironment_StartNodeChangeEvent;
FlowEnvironment.OnNodeConnectChange += FlowEnvironment_NodeConnectChangeEvemt;
FlowEnvironment.OnNodeCreate += FlowEnvironment_NodeCreateEvent;
FlowEnvironment.OnNodeRemote += FlowEnvironment_NodeRemoteEvent;
FlowEnvironment.OnFlowRunComplete += FlowEnvironment_OnFlowRunComplete;
FlowEnvironment.OnMonitorObjectChange += FlowEnvironment_OnMonitorObjectChange;
FlowEnvironment.OnNodeInterruptStateChange += FlowEnvironment_OnNodeInterruptStateChange;
FlowEnvironment.OnInterruptTrigger += FlowEnvironment_OnInterruptTrigger;
FlowEnvironment.OnIOCMembersChanged += FlowEnvironment_OnIOCMembersChanged;
FlowEnvironment.OnNodeLocate += FlowEnvironment_OnNodeLocate;
}
private void InitCanvasUI()
{
canvasTransformGroup = new TransformGroup();
scaleTransform = new ScaleTransform();
translateTransform = new TranslateTransform();
canvasTransformGroup.Children.Add(scaleTransform);
canvasTransformGroup.Children.Add(translateTransform);
FlowChartCanvas.RenderTransform = canvasTransformGroup;
}
private LogWindow InitConsoleOut()
{
var logWindow = new LogWindow();
logWindow.Show();
// 重定向 Console 输出
var logTextWriter = new LogTextWriter(msg => logWindow.AppendText(msg), () => logWindow.Clear()); ;
Console.SetOut(logTextWriter);
return logWindow;
}
#region 窗体加载方法
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
logWindow.Close();
System.Windows.Application.Current.Shutdown();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
var project = App.FlowProjectData;
if (project is null)
{
return;
}
InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小
foreach (var connection in Connections)
{
connection.Refresh();
}
var canvasData = project.Basic.Canvas;
if (canvasData != 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 运行环境事件
///
/// 加载完成
///
///
private void FlowEnvironment_OnProjectLoaded(ProjectLoadedEventArgs eventArgs)
{
}
///
/// 运行完成
///
///
///
private void FlowEnvironment_OnFlowRunComplete(FlowEventArgs eventArgs)
{
Console.WriteLine("-------运行完成---------\r\n");
IOCObjectViewer.ClearObjItem();
}
///
/// 加载了DLL文件,dll内容
///
private void FlowEnvironment_DllLoadEvent(LoadDLLEventArgs eventArgs)
{
this.Dispatcher.Invoke(() => {
Assembly assembly = eventArgs.Assembly;
List methodDetailss = eventArgs.MethodDetailss;
var dllControl = new DllControl
{
Header = "DLL name : " + assembly.GetName().Name // 设置控件标题为程序集名称
};
foreach (var methodDetails in methodDetailss)
{
switch (methodDetails.MethodDynamicType)
{
case Library.Enums.NodeType.Action:
dllControl.AddAction(methodDetails.Clone()); // 添加动作类型到控件
break;
case Library.Enums.NodeType.Flipflop:
dllControl.AddFlipflop(methodDetails.Clone()); // 添加触发器方法到控件
break;
}
}
DllStackPanel.Children.Add(dllControl); // 将控件添加到界面上显示
});
}
///
/// 节点连接关系变更
///
///
///
///
private void FlowEnvironment_NodeConnectChangeEvemt(NodeConnectChangeEventArgs eventArgs)
{
string fromNodeGuid = eventArgs.FromNodeGuid;
string toNodeGuid = eventArgs.ToNodeGuid;
NodeControlBase? fromNode = GuidToControl(fromNodeGuid);
NodeControlBase? toNode = GuidToControl(toNodeGuid);
if(fromNode != null && toNode != null)
{
}
else
{
return;
}
ConnectionType connectionType = eventArgs.ConnectionType;
Action? action = null;
if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Create) // 添加连接
{
// 添加连接
var connection = new Connection
{
Start = fromNode,
End = toNode,
Type = connectionType
};
if (toNode is FlipflopNodeControl flipflopControl) // 某个节点连接到了触发器,尝试从全局触发器视图中移除该触发器
{
var nodeModel = flipflopControl?.ViewModel?.Node;
NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
}
action = () => {
BsControl.Draw(FlowChartCanvas, connection); // 添加贝塞尔曲线显示
ConfigureLineContextMenu(connection); // 设置连接右键事件
Connections.Add(connection);
EndConnection();
};
}
else if (eventArgs.ChangeType == NodeConnectChangeEventArgs.ConnectChangeType.Remote) // 移除连接
{
// 需要移除连接
var removeConnections = Connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(fromNodeGuid)
&& c.End.ViewModel.Node.Guid.Equals(toNodeGuid))
.ToList();
action = () =>
{
foreach (var connection in removeConnections)
{
connection.RemoveFromCanvas();
Connections.Remove(connection);
JudgmentFlipFlopNode(connection.End); // 连接关系变更时判断
}
};
}
this.Dispatcher.Invoke(() =>
{
action?.Invoke();
});
}
///
/// 节点移除事件
///
///
private void FlowEnvironment_NodeRemoteEvent(NodeRemoteEventArgs eventArgs)
{
var nodeGuid = eventArgs.NodeGuid;
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
if (nodeControl is null) return;
if (selectNodeControls.Count > 0)
{
if (selectNodeControls.Contains(nodeControl))
{
selectNodeControls.Remove(nodeControl);
}
}
#region 节点树视图
if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器
{
var node = flipflopControl?.ViewModel?.Node;
if (node is not null)
{
NodeTreeViewer.RemoteGlobalFlipFlop(node); // 从全局触发器树树视图中移除
}
}
#endregion
this.Dispatcher.Invoke(() =>
{
FlowChartCanvas.Children.Remove(nodeControl);
NodeControls.Remove(nodeControl.ViewModel.Node.Guid);
});
}
///
/// 编辑项目时添加了节点
///
///
///
private void FlowEnvironment_NodeCreateEvent(NodeCreateEventArgs eventArgs)
{
this.Dispatcher.Invoke(() =>
{
if (eventArgs.NodeModel is not NodeModelBase nodeModelBase)
{
return;
}
// MethodDetails methodDetailss = eventArgs.MethodDetailss;
Position position = eventArgs.Position;
// 创建对应控件
NodeControlBase? nodeControl = nodeModelBase.ControlType switch
{
NodeControlType.Action => CreateNodeControl(nodeModelBase), //typeof(ActionNodeControl),
NodeControlType.Flipflop => CreateNodeControl(nodeModelBase),
NodeControlType.ExpCondition => CreateNodeControl(nodeModelBase),
NodeControlType.ExpOp => CreateNodeControl(nodeModelBase),
NodeControlType.ConditionRegion => CreateNodeControl(nodeModelBase),
_ => null,
};
if (nodeControl is null)
{
return;
}
NodeControls.TryAdd(nodeModelBase.Guid, nodeControl);
if (eventArgs.IsAddInRegion && NodeControls.TryGetValue(eventArgs.RegeionGuid, out NodeControlBase? regionControl))
{
if (regionControl is not null)
{
TryPlaceNodeInRegion(regionControl, nodeControl);
}
return;
}
else
{
if (!TryPlaceNodeInRegion(nodeControl, position)) // 将节点放置在区域中
{
PlaceNodeOnCanvas(nodeControl, position.X, position.Y); // 将节点放置在画布上
}
}
#region 节点树视图
if (nodeModelBase.ControlType == NodeControlType.Flipflop)
{
var node = nodeControl?.ViewModel?.Node;
if(node is not null)
{
NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, node); // 新增的触发器节点添加到全局触发器
}
}
#endregion
});
}
///
/// 设置了流程起始控件
///
///
///
private void FlowEnvironment_StartNodeChangeEvent(StartNodeChangeEventArgs eventArgs)
{
this.Dispatcher.Invoke(() =>
{
string oldNodeGuid = eventArgs.OldNodeGuid;
string newNodeGuid = eventArgs.NewNodeGuid;
NodeControlBase? newStartNodeControl = GuidToControl(newNodeGuid);
if (newStartNodeControl is null) return;
if (!string.IsNullOrEmpty(oldNodeGuid))
{
NodeControlBase? oldStartNodeControl = GuidToControl(oldNodeGuid);
if (oldStartNodeControl is null) 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?.Node;
if (node is not null)
{
NodeTreeViewer.LoadNodeTreeOfStartNode(FlowEnvironment, node);
}
});
}
///
/// 被监视的对象发生改变
///
///
private void FlowEnvironment_OnMonitorObjectChange(MonitorObjectEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
string monitorKey = MonitorObjectEventArgs.ObjSourceType.NodeFlowData switch
{
MonitorObjectEventArgs.ObjSourceType.NodeFlowData => nodeGuid,
_ => eventArgs.NewData.GetType().FullName,
};
//NodeControlBase nodeControl = GuidToControl(nodeGuid);
ViewObjectViewer.Dispatcher.BeginInvoke(() => {
if (ViewObjectViewer.MonitorObj is null) // 如果没有加载过对象
{
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象 ViewObjectViewerControl.MonitorType.Obj
}
else
{
if (ViewObjectViewer.MonitorKey.Equals(monitorKey)) // 相同对象
{
ViewObjectViewer.RefreshObjectTree(eventArgs.NewData); // 刷新
}
else
{
ViewObjectViewer.LoadObjectInformation(monitorKey, eventArgs.NewData); // 加载对象
}
}
});
}
///
/// 节点中断状态改变。
///
///
private void FlowEnvironment_OnNodeInterruptStateChange(NodeInterruptStateChangeEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
if (nodeControl is null) return;
if (eventArgs.Class == InterruptClass.None)
{
nodeControl.ViewModel.IsInterrupt = false;
}
else
{
nodeControl.ViewModel.IsInterrupt = true;
}
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 = "取消中断";
}
}
}
}
///
/// 节点触发了中断
///
///
///
private void FlowEnvironment_OnInterruptTrigger(InterruptTriggerEventArgs eventArgs)
{
string nodeGuid = eventArgs.NodeGuid;
NodeControlBase? nodeControl = GuidToControl(nodeGuid);
if (nodeControl is null) return;
if(eventArgs.Type == InterruptTriggerEventArgs.InterruptTriggerType.Exp)
{
Console.WriteLine($"表达式触发了中断:{eventArgs.Expression}");
}
else
{
Console.WriteLine($"节点触发了中断:{nodeGuid}");
}
}
///
/// IOC变更
///
///
///
private void FlowEnvironment_OnIOCMembersChanged(IOCMembersChangedEventArgs eventArgs)
{
IOCObjectViewer.AddDependenciesInstance(eventArgs.Key, eventArgs.Instance);
}
///
/// 节点需要定位
///
///
///
private void FlowEnvironment_OnNodeLocate(NodeLocatedEventArgs eventArgs)
{
NodeControlBase? nodeControl = GuidToControl(eventArgs.NodeGuid);
if (nodeControl is null) 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);
}
///
/// 控件抖动
/// 来源:https://www.cnblogs.com/RedSky/p/17705411.html
/// 作者:HotSky
/// (……太好用了)
///
///
/// 抖动第一下偏移量
/// 减弱幅度(小于等于power,大于0)
/// 持续系数(大于0),越大时间越长,
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; // 或者重新设置为默认值
};
}
///
/// Guid 转 NodeControl
///
/// 节点Guid
/// 节点Model
/// 无法获取节点、Guid/节点为null时报错
private NodeControlBase? GuidToControl(string nodeGuid)
{
if (string.IsNullOrEmpty(nodeGuid))
{
return null;
throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid));
}
if (!NodeControls.TryGetValue(nodeGuid, out NodeControlBase? nodeControl) || nodeControl is null)
{
return null;
throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid));
}
return nodeControl;
}
#endregion
#region 加载项目文件后触发事件相关方法
///
/// 运行环节加载了项目文件,需要创建节点控件
///
///
///
///
///
private NodeControlBase? CreateNodeControlOfNodeInfo(NodeInfo nodeInfo, MethodDetails methodDetailss)
{
// 创建控件实例
NodeControlBase nodeControl = nodeInfo.Type switch
{
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" =>
CreateNodeControl(methodDetailss),// 动作节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" =>
CreateNodeControl(methodDetailss), // 触发器节点控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" =>
CreateNodeControl(), // 条件表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" =>
CreateNodeControl(), // 操作表达式控件
$"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" =>
CreateNodeControl(), // 条件区域控件
_ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.Type}"),
};
return nodeControl;
}
///
/// 加载文件时,添加节点到区域中
///
///
///
private void AddNodeControlInRegeionControl(NodeControlBase regionControl, NodeInfo[] childNodes)
{
foreach (var childNode in childNodes)
{
if (FlowEnvironment.TryGetMethodDetails(childNode.MethodName, out MethodDetails md))
{
var childNodeControl = CreateNodeControlOfNodeInfo(childNode, md);
if (childNodeControl is null)
{
Console.WriteLine($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n");
continue;
}
if (regionControl is ConditionRegionControl conditionRegion)
{
conditionRegion.AddCondition(childNodeControl);
}
}
}
}
#endregion
#region 节点控件的创建
///
/// 创建了节点,添加到画布。配置默认事件
///
///
///
private void PlaceNodeOnCanvas(NodeControlBase nodeControl, double x, double y)
{
FlowChartCanvas.Dispatcher.Invoke(() =>
{
// 添加控件到画布
FlowChartCanvas.Children.Add(nodeControl);
Canvas.SetLeft(nodeControl, x);
Canvas.SetTop(nodeControl, y);
ConfigureContextMenu(nodeControl); // 配置节点右键菜单
ConfigureNodeEvents(nodeControl); // 配置节点事件
});
}
///
/// 配置节点事件
///
///
private void ConfigureNodeEvents(NodeControlBase nodeControl)
{
nodeControl.MouseLeftButtonDown += Block_MouseLeftButtonDown;
nodeControl.MouseMove += Block_MouseMove;
nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp;
}
///
/// 开始创建连接 True线 操作,设置起始块和绘制连接线。
///
private void StartConnection(NodeControlBase startNodeControl, ConnectionType connectionType)
{
var tf = Connections.FirstOrDefault(it => it.Start == startNodeControl)?.Type;
IsConnecting = true;
currentConnectionType = connectionType;
startConnectNodeControl = startNodeControl;
// 确保起点和终点位置的正确顺序
currentLine = new Line
{
Stroke = connectionType == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
: connectionType == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
: connectionType == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
: new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
StrokeDashArray = new DoubleCollection([2]),
StrokeThickness = 2,
X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2,
Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2,
X2 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2, // 初始时终点与起点重合
Y2 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2,
};
FlowChartCanvas.Children.Add(currentLine);
this.KeyDown += MainWindow_KeyDown;
}
#endregion
#region 配置右键菜单
///
/// 配置节点右键菜单
///
/// 任何情景下都尽量避免直接操作 ViewModel 中的 NodeModel 节点,而是应该调用 FlowEnvironment 提供接口进行操作。 因为 Workbench 应该更加关注UI视觉效果,而非直接干扰流程环境运行的逻辑。 之所以暴露 NodeModel 属性,因为有些场景下不可避免的需要直接获取节点的属性。
private void ConfigureContextMenu(NodeControlBase nodeControl)
{
var contextMenu = new ContextMenu();
// var nodeModel = nodeControl.ViewModel.Node;
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
{
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
{
DisplayReturnTypeTreeViewer(returnType);
}));
}
var nodeGuid = nodeControl?.ViewModel?.Node?.Guid;
#region 右键菜单功能 - 中断
contextMenu.Items.Add(CreateMenuItem("在此中断", (s, e) =>
{
if ((s is MenuItem menuItem) && menuItem is not null)
{
if (nodeControl?.ViewModel?.Node?.DebugSetting?.InterruptClass == InterruptClass.None)
{
FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.Branch);
menuItem.Header = "取消中断";
}
else
{
FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.None);
menuItem.Header = "在此中断";
}
}
}));
#endregion
contextMenu.Items.Add(CreateMenuItem("设为起点", (s, e) => FlowEnvironment.SetStartNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("删除", (s, e) => FlowEnvironment.RemoteNode(nodeGuid)));
contextMenu.Items.Add(CreateMenuItem("添加 真分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsSucceed)));
contextMenu.Items.Add(CreateMenuItem("添加 假分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsFail)));
contextMenu.Items.Add(CreateMenuItem("添加 异常分支", (s, e) => StartConnection(nodeControl, ConnectionType.IsError)));
contextMenu.Items.Add(CreateMenuItem("添加 上游分支", (s, e) => StartConnection(nodeControl, ConnectionType.Upstream)));
#region 右键菜单功能 - 控件对齐
var AvoidMenu = new MenuItem();
AvoidMenu.Items.Add(CreateMenuItem("群组对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Grouping);
}));
AvoidMenu.Items.Add(CreateMenuItem("规划对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Planning);
}));
AvoidMenu.Items.Add(CreateMenuItem("水平中心对齐", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.HorizontalCenter);
}));
AvoidMenu.Items.Add(CreateMenuItem("垂直中心对齐 ", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.VerticalCenter);
}));
AvoidMenu.Items.Add(CreateMenuItem("垂直对齐时水平斜分布", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Vertical);
}));
AvoidMenu.Items.Add(CreateMenuItem("水平对齐时垂直斜分布", (s, e) =>
{
AlignControlsWithGrouping(selectNodeControls, AlignMode.Horizontal);
}));
AvoidMenu.Header = "对齐";
contextMenu.Items.Add(AvoidMenu);
#endregion
nodeControl.ContextMenu = contextMenu;
}
///
/// 配置连接曲线的右键菜单
///
///
private void ConfigureLineContextMenu(Connection connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ArrowPath.ContextMenu = contextMenu;
connection.BezierPath.ContextMenu = contextMenu;
}
///
/// 删除该连线
///
///
private void DeleteConnection(Connection connection)
{
var connectionToRemove = connection;
if (connectionToRemove is null)
{
return;
}
// 获取起始节点与终止节点,消除映射关系
var fromNodeGuid = connectionToRemove.Start.ViewModel.Node.Guid;
var toNodeGuid = connectionToRemove.End.ViewModel.Node.Guid;
FlowEnvironment.RemoteConnect(fromNodeGuid, toNodeGuid, connection.Type);
}
///
/// 查看返回类型(树形结构展开类型的成员)
///
///
private void DisplayReturnTypeTreeViewer(Type type)
{
try
{
var typeViewerWindow = new TypeViewerWindow
{
Type = type,
};
typeViewerWindow.LoadTypeInformation();
typeViewerWindow.Show();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
//private void DisplayFlowDataTreeViewer(object @object)
//{
// try
// {
// var typeViewerWindow = new ViewObjectViewerWindow();
// typeViewerWindow.LoadObjectInformation(@object);
// typeViewerWindow.Show();
// }
// catch (Exception ex)
// {
// Console.WriteLine(ex);
// }
//}
#endregion
#region 拖拽DLL文件到左侧功能区,加载相关节点清单
///
/// 当拖动文件到窗口时触发,加载DLL文件
///
///
///
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"))
{
FlowEnvironment.LoadDll(file);
}
}
}
}
///
/// 当拖动文件经过窗口时触发,设置拖放效果为复制
///
///
///
private void Window_DragOver(object sender, DragEventArgs e)
{
e.Effects = DragDropEffects.Copy;
e.Handled = true;
}
#endregion
#region 与流程图与节点相关
///
/// 鼠标在画布移动。
/// 选择控件状态下,调整选择框大小
/// 连接状态下,实时更新连接线的终点位置。
/// 移动画布状态下,移动画布。
///
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (IsConnecting) // 正在连接节点
{
Point position = e.GetPosition(FlowChartCanvas);
if (currentLine is null || startConnectNodeControl is null)
{
return;
}
currentLine.X1 = Canvas.GetLeft(startConnectNodeControl) + startConnectNodeControl.ActualWidth / 2;
currentLine.Y1 = Canvas.GetTop(startConnectNodeControl) + startConnectNodeControl.ActualHeight / 2;
currentLine.X2 = position.X;
currentLine.Y2 = position.Y;
}
if (IsCanvasDragging && e.MiddleButton == MouseButtonState.Pressed) // 按住中键的同时进行画布的移动 IsCanvasDragging &&
{
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.Refresh();
}
}
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;
}
}
///
/// 基础节点的拖拽放置创建
///
///
///
private void BaseNodeControl_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (sender is UserControl control)
{
// 创建一个 DataObject 用于拖拽操作,并设置拖拽效果
var dragData = new DataObject(MouseNodeType.CreateBaseNodeInCanvas, control.GetType());
DragDrop.DoDragDrop(control, dragData, DragDropEffects.Move);
}
}
///
/// 放置操作,根据拖放数据创建相应的控件,并处理相关操作
///
///
///
private void FlowChartCanvas_Drop(object sender, DragEventArgs e)
{
var canvasDropPosition = e.GetPosition(FlowChartCanvas); // 更新画布落点
Position position = new Position(canvasDropPosition.X, canvasDropPosition.Y);
if (e.Data.GetDataPresent(MouseNodeType.CreateDllNodeInCanvas))
{
if (e.Data.GetData(MouseNodeType.CreateDllNodeInCanvas) is MoveNodeData nodeData)
{
// 创建DLL文件的节点对象
FlowEnvironment.CreateNode(nodeData.NodeControlType, position, nodeData.MethodDetails);
}
}
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,
_ => NodeControlType.None,
};
if(nodeControlType != NodeControlType.None)
{
// 创建基础节点对象
FlowEnvironment.CreateNode(nodeControlType, position);
}
}
}
e.Handled = true;
}
///
/// 尝试将节点放置在区域中
///
///
///
///
///
private bool TryPlaceNodeInRegion(NodeControlBase nodeControl, Position position)
{
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.Node.ControlType == NodeControlType.ExpCondition)
{
ConditionRegionControl conditionRegion = GetParentOfType(hitElement);
if (conditionRegion != null)
{
TryPlaceNodeInRegion(conditionRegion, nodeControl);
//// 如果存在条件区域容器
//conditionRegion.AddCondition(nodeControl);
return true;
}
}
}
return false;
}
///
/// 将节点放在目标区域中
///
/// 区域容器
/// 节点控件
private void TryPlaceNodeInRegion(NodeControlBase regionControl, NodeControlBase nodeControl)
{
// 准备放置条件表达式控件
if (nodeControl.ViewModel.Node.ControlType == NodeControlType.ExpCondition)
{
ConditionRegionControl conditionRegion = regionControl as ConditionRegionControl;
if (conditionRegion != null)
{
// 如果存在条件区域容器
conditionRegion.AddCondition(nodeControl);
}
}
}
///
/// 拖动效果,根据拖放数据是否为指定类型设置拖放效果
///
///
///
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;
}
///
/// 控件的鼠标左键按下事件,启动拖动操作。同时显示当前正在传递的数据。
///
private void Block_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if(sender is NodeControlBase nodeControl)
{
ChangeViewerObjOfNode(nodeControl);
if (nodeControl?.ViewModel?.Node?.MethodDetails?.IsProtectionParameter == true) return;
IsControlDragging = true;
startControlDragPoint = e.GetPosition(FlowChartCanvas); // 记录鼠标按下时的位置
((UIElement)sender).CaptureMouse(); // 捕获鼠标
e.Handled = true; // 防止事件传播影响其他控件
}
}
///
/// 控件的鼠标移动事件,根据鼠标拖动更新控件的位置。批量移动计算移动逻辑。
///
private void Block_MouseMove(object sender, MouseEventArgs e)
{
if (IsConnecting)
return;
if (IsCanvasDragging)
return;
if (IsSelectControl)
return;
if (IsControlDragging) // 如果正在拖动控件
{
Point currentPosition = e.GetPosition(FlowChartCanvas); // 获取当前鼠标位置
// 批量移动 与 单个节点控件移动
if (selectNodeControls.Count > 0 && sender is NodeControlBase element && selectNodeControls.Contains(element))
{
// 获取element控件的旧位置
double oldLeft = Canvas.GetLeft(element);
double oldTop = Canvas.GetTop(element);
// 计算被选择控件的偏移量
double deltaX = (int)(currentPosition.X - startControlDragPoint.X);
double deltaY = (int)(currentPosition.Y - startControlDragPoint.Y);
// 移动被选择的控件
double newLeft = oldLeft + deltaX;
double newTop = oldTop + deltaY;
// 限制控件不超出FlowChartCanvas的边界
if (newLeft >= 0 && newLeft + element.ActualWidth <= FlowChartCanvas.ActualWidth)
{
Canvas.SetLeft(element, newLeft);
}
if (newTop >= 0 && newTop + element.ActualHeight <= FlowChartCanvas.ActualHeight)
{
Canvas.SetTop(element, newTop);
}
// 计算element实际移动的距离
double actualDeltaX = newLeft - oldLeft;
double actualDeltaY = newTop - oldTop;
// 移动其它选中的控件
foreach (var nodeControl in selectNodeControls)
{
if (nodeControl != element) // 跳过已经移动的控件
{
double otherNewLeft = Canvas.GetLeft(nodeControl) + actualDeltaX;
double otherNewTop = Canvas.GetTop(nodeControl) + actualDeltaY;
// 限制控件不超出FlowChartCanvas的边界
if (otherNewLeft >= 0 && otherNewLeft + nodeControl.ActualWidth <= FlowChartCanvas.ActualWidth)
{
Canvas.SetLeft(nodeControl, otherNewLeft);
}
if (otherNewTop >= 0 && otherNewTop + nodeControl.ActualHeight <= FlowChartCanvas.ActualHeight)
{
Canvas.SetTop(nodeControl, otherNewTop);
}
}
}
foreach (var nodeControl in selectNodeControls)
{
UpdateConnections(nodeControl);
}
startControlDragPoint = currentPosition; // 更新起始点位置
}
else
{ // 获取引发事件的控件
if (sender is not UserControl block)
{
return;
}
double deltaX = currentPosition.X - startControlDragPoint.X; // 计算X轴方向的偏移量
double deltaY = currentPosition.Y - startControlDragPoint.Y; // 计算Y轴方向的偏移量
double newLeft = Canvas.GetLeft(block) + deltaX; // 新的左边距
double newTop = Canvas.GetTop(block) + deltaY; // 新的上边距
//Console.WriteLine((Canvas.GetLeft(block), Canvas.GetTop(block)));
// 限制控件不超出FlowChartCanvas的边界
if (newLeft >= 0 && newLeft + block.ActualWidth <= FlowChartCanvas.ActualWidth)
{
Canvas.SetLeft(block, newLeft);
}
if (newTop >= 0 && newTop + block.ActualHeight <= FlowChartCanvas.ActualHeight)
{
Canvas.SetTop(block, newTop);
}
UpdateConnections(block);
}
startControlDragPoint = currentPosition; // 更新起始点位置
}
}
private void ChangeViewerObjOfNode(NodeControlBase nodeControl)
{
var node = nodeControl?.ViewModel?.Node;
//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;
var instance = node.GetFlowData();
ViewObjectViewer.LoadObjectInformation(key, instance);
ChangeViewerObj(key, instance);
}
}
public void ChangeViewerObj(string key, object instance)
{
if (ViewObjectViewer.MonitorObj is null)
{
FlowEnvironment.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI
return;
}
if (instance is null)
{
return;
}
if (key.Equals(ViewObjectViewer.MonitorKey) == true)
{
ViewObjectViewer.RefreshObjectTree(instance);
return;
}
else
{
FlowEnvironment.SetMonitorObjState(ViewObjectViewer.MonitorKey,false); // 取消对旧节点的监视
FlowEnvironment.SetMonitorObjState(key, true); // 通知环境,该节点的数据更新后需要传到UI
}
}
#region UI连接控件操作
///
/// 控件的鼠标左键松开事件,结束拖动操作,创建连线
///
private void Block_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsControlDragging)
{
IsControlDragging = false;
((UIElement)sender).ReleaseMouseCapture(); // 释放鼠标捕获
}
if (IsConnecting)
{
var formNodeGuid = startConnectNodeControl?.ViewModel.Node.Guid;
var toNodeGuid = (sender as NodeControlBase)?.ViewModel.Node.Guid;
if (string.IsNullOrEmpty(formNodeGuid) || string.IsNullOrEmpty(toNodeGuid))
{
return;
}
FlowEnvironment.ConnectNode(formNodeGuid, toNodeGuid, currentConnectionType);
}
/*else if (IsConnecting)
{
bool isRegion = false;
NodeControlBase? targetBlock;
if (sender is ActionNodeControl)
{
targetBlock = sender as ActionNodeControl; // 动作
}
else if (sender is ActionRegionControl)
{
targetBlock = sender as ActionRegionControl; // 组合动作
isRegion = true;
}
else if (sender is ConditionNodeControl)
{
targetBlock = sender as ConditionNodeControl; // 条件
}
else if (sender is ConditionRegionControl)
{
targetBlock = sender as ConditionRegionControl; // 组合条件
isRegion = true;
}
else if (sender is FlipflopNodeControl)
{
targetBlock = sender as FlipflopNodeControl; // 触发器
}
else if (sender is ExpOpNodeControl)
{
targetBlock = sender as ExpOpNodeControl; // 触发器
}
else
{
targetBlock = null;
}
if (targetBlock == null)
{
return;
}
if (startConnectBlock != null && targetBlock != null && startConnectBlock != targetBlock)
{
var connection = new Connection { Start = startConnectBlock, End = targetBlock, Type = currentConnectionType };
if (currentConnectionType == ConnectionType.IsSucceed)
{
startConnectBlock.ViewModel.Node.SucceedBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.IsFail)
{
startConnectBlock.ViewModel.Node.FailBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.IsError)
{
startConnectBlock.ViewModel.Node.ErrorBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.Upstream)
{
startConnectBlock.ViewModel.Node.UpstreamBranch.Add(targetBlock.ViewModel.Node);
}
// 保存连接关系
BsControl.Draw(FlowChartCanvas, connection);
ConfigureLineContextMenu(connection);
targetBlock.ViewModel.Node.PreviousNodes.Add(startConnectBlock.ViewModel.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯)
connections.Add(connection);
}
EndConnection();
}*/
}
///
/// 主窗口的KeyDown事件处理,用于在连接操作中按下Esc键取消连接。
///
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Escape && IsConnecting)
{
this.KeyDown -= MainWindow_KeyDown;
EndConnection();
}
}
///
/// 结束连接操作,清理状态并移除虚线。
///
private void EndConnection()
{
IsConnecting = false;
startConnectNodeControl = null;
// 移除虚线
if (currentLine != null)
{
FlowChartCanvas.Children.Remove(currentLine);
currentLine = null;
}
}
///
/// 更新与指定控件相关的所有连接的位置。
///
private void UpdateConnections(UserControl block)
{
foreach (var connection in Connections)
{
if (connection.Start == block || connection.End == block)
{
connection.Refresh();
//connection.RemoveFromCanvas();
//BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
}
}
}
#endregion
#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 > 1.5) 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 画布中框选节点控件动作
///
/// 在画布中尝试选取控件
///
///
///
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
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; // 防止事件传播影响其他控件
return;
// 如果正在选取状态,再次点击画布时自动确定选取范围,否则进入选取状态
if (IsSelectControl)
{
IsSelectControl = false;
// 释放鼠标捕获
FlowChartCanvas.ReleaseMouseCapture();
// 隐藏选取矩形(如果需要保持选取状态显示,可以删除此行)
SelectionRectangle.Visibility = Visibility.Collapsed;
// 处理选取区域内的元素(例如,获取选取范围内的控件)
Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle),
Canvas.GetTop(SelectionRectangle),
SelectionRectangle.Width,
SelectionRectangle.Height);
// 在此处处理选取的逻辑
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)
{
selectNodeControls.Add(control);
}
}
}
SelectedNode();// 选择之后需要执行的操作
}
else
{
// 进入选取状态
IsSelectControl = true;
// 开始选取时,记录鼠标起始点
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();
}
e.Handled = true; // 防止事件传播影响其他控件
}
///
/// 在画布中释放鼠标按下,结束选取状态
///
///
///
private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsSelectControl)
{
// 松开鼠标时判断是否为拖动操作
if (IsSelectDragging)
{
// 完成拖动框选
CompleteSelection();
}
// 释放鼠标捕获
FlowChartCanvas.ReleaseMouseCapture();
}
e.Handled = true;
}
/// 完成选取操作
///
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)
{
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?.Node?.Guid;
if (!string.IsNullOrEmpty(guid))
{
FlowEnvironment.RemoteNode(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.Selected();
// 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.CancelSelect();
nodeControl.BorderBrush = Brushes.Black;
nodeControl.BorderThickness = new Thickness(0);
if (nodeControl.ViewModel.Node.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.Refresh();
}
});
}
#region Plan A 群组对齐
public void AlignControlsWithGrouping(List selectNodeControls, double proximityThreshold = 50, double spacing = 10)
{
if (selectNodeControls is null || selectNodeControls.Count < 2)
return;
// 按照控件的相对位置进行分组
var horizontalGroups = GroupByProximity(selectNodeControls, proximityThreshold, isHorizontal: true);
var verticalGroups = GroupByProximity(selectNodeControls, proximityThreshold, isHorizontal: false);
// 对每个水平群组进行垂直对齐
foreach (var group in horizontalGroups)
{
double avgY = group.Average(c => Canvas.GetTop(c)); // 计算Y坐标平均值
foreach (var control in group)
{
Canvas.SetTop(control, avgY); // 对齐Y坐标
}
}
// 对每个垂直群组进行水平对齐
foreach (var group in verticalGroups)
{
double avgX = group.Average(c => Canvas.GetLeft(c)); // 计算X坐标平均值
foreach (var control in group)
{
Canvas.SetLeft(control, avgX); // 对齐X坐标
}
}
}
// 基于控件间的距离来分组,按水平或垂直方向
private List> GroupByProximity(List controls, double proximityThreshold, bool isHorizontal)
{
var groups = new List>();
foreach (var control in controls)
{
bool addedToGroup = false;
// 尝试将控件加入现有的群组
foreach (var group in groups)
{
if (IsInProximity(group, control, proximityThreshold, isHorizontal))
{
group.Add(control);
addedToGroup = true;
break;
}
}
// 如果没有加入任何群组,创建新群组
if (!addedToGroup)
{
groups.Add(new List { control });
}
}
return groups;
}
// 判断控件是否接近某个群组
private bool IsInProximity(List group, NodeControlBase control, double proximityThreshold, bool isHorizontal)
{
foreach (var existingControl in group)
{
double distance = isHorizontal
? Math.Abs(Canvas.GetTop(existingControl) - Canvas.GetTop(control)) // 垂直方向的距离
: Math.Abs(Canvas.GetLeft(existingControl) - Canvas.GetLeft(control)); // 水平方向的距离
if (distance <= proximityThreshold)
{
return true;
}
}
return false;
}
#endregion
#region Plan B 规划对齐
public void AlignControlsWithDynamicProgramming(List selectNodeControls, double spacing = 10)
{
if (selectNodeControls is null || selectNodeControls.Count < 2)
return;
int n = selectNodeControls.Count;
double[] dp = new double[n];
int[] split = new int[n];
// 初始化动态规划数组
for (int i = 1; i < n; i++)
{
dp[i] = double.MaxValue;
for (int j = 0; j < i; j++)
{
double cost = CalculateAlignmentCost(selectNodeControls, j, i, spacing);
if (dp[j] + cost < dp[i])
{
dp[i] = dp[j] + cost;
split[i] = j;
}
}
}
// 回溯找到最优的对齐方式
AlignWithSplit(selectNodeControls, split, n - 1, spacing);
}
// 计算从控件[j]到控件[i]的对齐代价,并考虑控件的大小和间距
private double CalculateAlignmentCost(List controls, int start, int end, double spacing)
{
double totalWidth = 0;
double totalHeight = 0;
for (int i = start; i <= end; i++)
{
totalWidth += controls[i].ActualWidth;
totalHeight += controls[i].ActualHeight;
}
// 水平和垂直方向代价计算,包括控件大小和间距
double widthCost = totalWidth + (end - start) * spacing;
double heightCost = totalHeight + (end - start) * spacing;
// 返回较小的代价,表示更优的对齐方式
return Math.Min(widthCost, heightCost);
}
// 根据split数组调整控件位置,确保控件不重叠
private void AlignWithSplit(List controls, int[] split, int end, double spacing)
{
if (end <= 0)
return;
AlignWithSplit(controls, split, split[end], spacing);
// 从split[end]到end的控件进行对齐操作
double currentX = Canvas.GetLeft(controls[split[end]]);
double currentY = Canvas.GetTop(controls[split[end]]);
for (int i = split[end] + 1; i <= end; i++)
{
// 水平或垂直对齐,确保控件之间有间距
if (currentX + controls[i].ActualWidth + spacing <= Canvas.GetLeft(controls[end]))
{
Canvas.SetLeft(controls[i], currentX + controls[i].ActualWidth + spacing);
currentX += controls[i].ActualWidth + spacing;
}
else
{
Canvas.SetTop(controls[i], currentY + controls[i].ActualHeight + spacing);
currentY += controls[i].ActualHeight + spacing;
}
}
}
#endregion
public enum AlignMode
{
///
/// 水平对齐
///
Horizontal,
///
/// 垂直对齐
///
Vertical,
///
/// 水平中心对齐
///
HorizontalCenter,
///
/// 垂直中心对齐
///
VerticalCenter,
///
/// 规划对齐
///
Planning,
///
/// 群组对齐
///
Grouping,
}
public void AlignControlsWithGrouping(List selectNodeControls, AlignMode alignMode, double proximityThreshold = 50, double spacing = 10)
{
if (selectNodeControls is null || selectNodeControls.Count < 2)
return;
switch (alignMode)
{
case AlignMode.Horizontal:
AlignHorizontally(selectNodeControls, spacing);// AlignToCenter
break;
case AlignMode.Vertical:
AlignVertically(selectNodeControls, spacing);
break;
case AlignMode.HorizontalCenter:
AlignToCenter(selectNodeControls, isHorizontal: false, spacing);
break;
case AlignMode.VerticalCenter:
AlignToCenter(selectNodeControls, isHorizontal: true, spacing);
break;
case AlignMode.Planning:
AlignControlsWithDynamicProgramming(selectNodeControls, spacing);
break;
case AlignMode.Grouping:
AlignControlsWithGrouping(selectNodeControls, proximityThreshold, spacing);
break;
}
}
// 垂直对齐并避免重叠
private void AlignHorizontally(List controls, double spacing)
{
double avgY = controls.Average(c => Canvas.GetTop(c)); // 计算Y坐标平均值
double currentY = avgY;
foreach (var control in controls.OrderBy(c => Canvas.GetTop(c))) // 按Y坐标排序对齐
{
Canvas.SetTop(control, currentY);
currentY += control.ActualHeight + spacing; // 保证控件之间有足够的垂直间距
}
}
// 水平对齐并避免重叠
private void AlignVertically(List controls, double spacing)
{
double avgX = controls.Average(c => Canvas.GetLeft(c)); // 计算X坐标平均值
double currentX = avgX;
foreach (var control in controls.OrderBy(c => Canvas.GetLeft(c))) // 按X坐标排序对齐
{
Canvas.SetLeft(control, currentX);
currentX += control.ActualWidth + spacing; // 保证控件之间有足够的水平间距
}
}
// 按中心点对齐
private void AlignToCenter(List controls, bool isHorizontal, double spacing)
{
double avgCenter = isHorizontal
? controls.Average(c => Canvas.GetLeft(c) + c.ActualWidth / 2) // 水平中心点
: controls.Average(c => Canvas.GetTop(c) + c.ActualHeight / 2); // 垂直中心点
foreach (var control in controls)
{
if (isHorizontal)
{
double left = avgCenter - control.ActualWidth / 2;
Canvas.SetLeft(control, left);
}
else
{
double top = avgCenter - control.ActualHeight / 2;
Canvas.SetTop(control, top);
}
}
}
#endregion
#region 静态方法:创建节点,创建菜单子项,获取区域
private static TControl CreateNodeControl(NodeModelBase model)
where TControl : NodeControlBase
where TViewModel : NodeControlViewModelBase
{
if (model is null)
{
throw new Exception("无法创建节点控件");
}
var viewModel = Activator.CreateInstance(typeof(TViewModel), [model]);
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
if (controlObj is TControl control)
{
return control;
}
else
{
throw new Exception("无法创建节点控件");
}
}
private static TControl CreateNodeControl(MethodDetails? methodDetails = null)
where TNode : NodeModelBase
where TControl : NodeControlBase
where TViewModel : NodeControlViewModelBase
{
var nodeObj = Activator.CreateInstance(typeof(TNode));
var nodeBase = nodeObj as NodeModelBase;
if (nodeBase is null)
{
throw new Exception("无法创建节点控件");
}
nodeBase.Guid = Guid.NewGuid().ToString();
if (methodDetails != null)
{
var md = methodDetails.Clone();
nodeBase.DisplayName = md.MethodTips;
nodeBase.MethodDetails = md;
}
var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel]);
if (controlObj is TControl control)
{
return control;
}
else
{
throw new Exception("无法创建节点控件");
}
}
///
/// 创建菜单子项
///
///
///
///
public static MenuItem CreateMenuItem(string header, RoutedEventHandler handler)
{
var menuItem = new MenuItem { Header = header };
menuItem.Click += handler;
return menuItem;
}
///
/// 穿透元素获取区域容器
///
///
///
///
private static T GetParentOfType(DependencyObject element) where T : DependencyObject
{
while (element != null)
{
if (element is T)
{
return element as T;
}
element = VisualTreeHelper.GetParent(element);
}
return null;
}
#endregion
#region 节点数、IOC视图管理
private void JudgmentFlipFlopNode(NodeControlBase nodeControl)
{
if (nodeControl is FlipflopNodeControl flipflopControl) // 判断是否为触发器
{
var nodeModel = flipflopControl?.ViewModel?.Node;
int count = 0;
foreach (var ct in NodeStaticConfig.ConnectionTypes)
{
count += nodeModel.PreviousNodes[ct].Count;
}
if (count == 0)
{
NodeTreeViewer.AddGlobalFlipFlop(FlowEnvironment, nodeModel); // 添加到全局触发器树树视图
}
else
{
NodeTreeViewer.RemoteGlobalFlipFlop(nodeModel); // 从全局触发器树树视图中移除
}
}
}
void LoadIOCObjectViewer()
{
}
#endregion
///
/// 卸载DLL文件,清空当前项目
///
///
///
private void UnloadAllButton_Click(object sender, RoutedEventArgs e)
{
FlowEnvironment.ClearAll();
}
///
/// 卸载DLL文件,清空当前项目
///
///
///
private void UnloadAllAssemblies()
{
DllStackPanel.Children.Clear();
FlowChartCanvas.Children.Clear();
Connections.Clear();
NodeControls.Clear();
currentLine = null;
startConnectNodeControl = null;
MessageBox.Show("所有DLL已卸载。", "信息", MessageBoxButton.OK, MessageBoxImage.Information);
}
///
/// 运行测试
///
///
///
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
{
logWindow?.Show();
await FlowEnvironment.StartAsync(); // 快
//await Task.Run( FlowEnvironment.StartAsync); // 上下文多次切换的场景中慢了1/10,定时器精度丢失
//await Task.Factory.StartNew(FlowEnvironment.StartAsync); // 慢了1/5,定时器精度丢失
}
///
/// 退出
///
///
///
private void ButtonDebugFlipflopNode_Click(object sender, RoutedEventArgs e)
{
FlowEnvironment?.Exit(); // 在运行平台上点击了退出
}
///
/// 从选定的节点开始运行
///
///
///
private async void ButtonStartFlowInSelectNode_Click(object sender, RoutedEventArgs e)
{
if(selectNodeControls.Count == 0)
{
Console.WriteLine("请至少选择一个节点");
}
else if (selectNodeControls.Count > 1)
{
Console.WriteLine("请只选择一个节点");
}
else
{
await this.FlowEnvironment.StartFlowInSelectNodeAsync(selectNodeControls[0].ViewModel.Node.Guid);
}
}
///
/// 保存为项目文件
///
///
///
private void ButtonSaveFile_Click(object sender, RoutedEventArgs e)
{
var projectData = FlowEnvironment.SaveProject();
projectData.Basic = new Basic
{
Canvas = new FlowCanvas
{
Lenght = FlowChartCanvas.Width,
Width = FlowChartCanvas.Height,
ViewX = translateTransform.X,
ViewY = translateTransform.Y,
ScaleX = scaleTransform.ScaleX,
ScaleY = scaleTransform.ScaleY,
},
Versions = "1",
};
foreach(var node in projectData.Nodes)
{
if(NodeControls.TryGetValue(node.Guid,out var nodeControl))
{
Point positionRelativeToParent = nodeControl.TranslatePoint(new Point(0, 0), FlowChartCanvas);
node.Position = new Position(positionRelativeToParent.X, positionRelativeToParent.Y);
}
}
var isPass = SaveContentToFile(out string savePath, out Action? savaProjectFile);
if(!isPass)
{
return;
}
string librarySavePath = System.IO.Path.GetDirectoryName(savePath);
Console.WriteLine(savePath);
for (int index = 0; index < projectData.Librarys.Length; index++)
{
Library.Entity.Library? library = projectData.Librarys[index];
try
{
string targetPath = System.IO.Path.Combine(librarySavePath, System.IO.Path.GetFileName(library.Path));
//Console.WriteLine("targetPath:" + targetPath);
string sourceFile = new Uri(library.Path).LocalPath;
//Console.WriteLine("sourceFile:" + sourceFile);
// 复制文件到目标目录
File.Copy(sourceFile, targetPath, true);
// 获取相对路径
string relativePath = System.IO.Path.GetRelativePath(savePath, targetPath);
//Console.WriteLine("Relative Path: " + relativePath);
projectData.Librarys[index].Path = relativePath;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
//WriteLog($"DLL复制失败:{dll.CodeBase} \r\n错误:{ex}\r\n");
}
}
JObject projectJsonData = JObject.FromObject(projectData);
savaProjectFile?.Invoke(savePath, projectJsonData.ToString());
}
public static bool SaveContentToFile(out string savePath, out Action? savaProjectFile)
{
// 创建一个新的保存文件对话框
SaveFileDialog saveFileDialog = new()
{
Filter = "DynamicNodeFlow Files (*.dnf)|*.dnf",
DefaultExt = "dnf",
FileName = "project.dnf"
};
// 显示保存文件对话框
bool? result = saveFileDialog.ShowDialog();
// 如果用户选择了文件并点击了保存按钮
if (result == true)
{
savePath = saveFileDialog.FileName;
savaProjectFile = File.WriteAllText;
return true;
}
savePath = string.Empty;
savaProjectFile = null;
return false;
}
public static string GetRelativePath(string baseDirectory, string fullPath)
{
Uri baseUri = new(baseDirectory + System.IO.Path.DirectorySeparatorChar);
Uri fullUri = new(fullPath);
Uri relativeUri = baseUri.MakeRelativeUri(fullUri);
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
}
private void Window_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
}
///
/// 按键监听。esc取消操作
///
///
///
private void Window_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
e.Handled = true; // 禁止默认的Tab键行为
}
if (e.KeyStates == Keyboard.GetKeyStates(Key.Escape))
//if (Keyboard.Modifiers == ModifierKeys.Shift)
{
IsConnecting = false;
IsControlDragging = false;
IsCanvasDragging = false;
EndConnection();
SelectionRectangle.Visibility = Visibility.Collapsed;
CancelSelectNode();
}
}
///
/// 对象装箱测试
///
///
///
private void ButtonTestExpObj_Click(object sender, RoutedEventArgs e)
{
string jsonString =
"""
{
"Name": "张三",
"Age": 24,
"Address": {
"City": "北京",
"PostalCode": "10000"
}
}
""";
var externalData = new Dictionary
{
{ "Name", "John" },
{ "Age", 30 },
{ "Addresses", new List>
{
new Dictionary
{
{ "Street", "123 Main St" },
{ "City", "New York" }
},
new Dictionary
{
{ "Street", "456 Another St" },
{ "City", "Los Angeles" }
}
}
}
};
if (!ObjDynamicCreateHelper.TryResolve(externalData, "RootType",out var result))
{
Console.WriteLine("赋值过程中有错误,请检查属性名和类型!");
}
ObjDynamicCreateHelper.PrintObjectProperties(result);
Console.WriteLine( );
var exp = "@set .Addresses[1].Street = qwq";
var data = SerinExpressionEvaluator.Evaluate(exp, result, out bool isChange);
exp = "@get .Addresses[1].Street";
data = SerinExpressionEvaluator.Evaluate(exp,result, out isChange);
Console.WriteLine($"{exp} => {data}");
}
///
/// 重置画布
///
///
///
private void ButtonResetCanvas_Click(object sender, RoutedEventArgs e)
{
translateTransform.X = 0;
translateTransform.Y = 0;
}
}
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
public static class BsControl
{
public static Connection Draw(Canvas canvas, Connection connection)
{
connection.Canvas = canvas;
UpdateBezierLineInDragging(canvas, connection);
//MakeDraggable(canvas, connection, connection.Start);
//MakeDraggable(canvas, connection, connection.End);
if (connection.BezierPath is null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection.ArrowPath is null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.ArrowPath, -1);
canvas.Children.Add(connection.ArrowPath);
}
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
return connection;
}
private static bool isUpdating = false; // 是否正在更新线条显示
// 拖动时重新绘制
public static void UpdateBezierLineInDragging(Canvas canvas, Connection connection)
{
if (isUpdating)
return;
isUpdating = true;
canvas.Dispatcher.InvokeAsync(() =>
{
if (connection is not null && connection.BezierPath is null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
//Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection is not null && connection.ArrowPath is null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetLineColor(connection.Type), Fill = BezierLineDrawer.GetLineColor(connection.Type), StrokeThickness = 1 };
//Canvas.SetZIndex(connection.ArrowPath, -1);
canvas.Children.Add(connection.ArrowPath);
}
BezierLineDrawer.UpdateBezierLine(canvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
isUpdating = false;
});
}
// private static Point clickPosition; // 当前点击事件
// private static bool isDragging = false; // 是否正在移动控件
//private static void MakeDraggable(Canvas canvas, Connection connection, UIElement element)
//{
// if (connection.IsSetEven)
// {
// return;
// }
// element.MouseLeftButtonDown += (sender, e) =>
// {
// isDragging = true;
// //clickPosition = e.GetPosition(element);
// //element.CaptureMouse();
// };
// element.MouseLeftButtonUp += (sender, e) =>
// {
// isDragging = false;
// //element.ReleaseMouseCapture();
// };
// element.MouseMove += (sender, e) =>
// {
// if (isDragging)
// {
// if (VisualTreeHelper.GetParent(element) is Canvas canvas)
// {
// Point currentPosition = e.GetPosition(canvas);
// double newLeft = currentPosition.X - clickPosition.X;
// double newTop = currentPosition.Y - clickPosition.Y;
// Canvas.SetLeft(element, newLeft);
// Canvas.SetTop(element, newTop);
// UpdateBezierLine(canvas, connection);
// }
// }
// };
//}
}
public class Connection
{
public ConnectionType Type { get; set; }
public Canvas Canvas { get; set; }// 贝塞尔曲线所在画布
public System.Windows.Shapes.Path BezierPath { get; set; }// 贝塞尔曲线路径
public System.Windows.Shapes.Path ArrowPath { get; set; } // 箭头路径
public required NodeControlBase Start { get; set; } // 起始
public required NodeControlBase End { get; set; } // 结束
public void RemoveFromCanvas()
{
Canvas.Children.Remove(BezierPath); // 移除线
Canvas.Children.Remove(ArrowPath); // 移除线
}
///
/// 重新绘制
///
public void Refresh()
{
BezierLineDrawer.UpdateBezierLine(Canvas, Start, End, BezierPath, ArrowPath);
}
}
public static class BezierLineDrawer
{
public enum Localhost
{
Left,
Right,
Top,
Bottom,
}
///
/// 绘制曲线
///
/// 所在画布
/// 起始控件
/// 终点控件
/// 曲线
/// 箭头
public static void UpdateBezierLine(Canvas canvas,
FrameworkElement startElement,
FrameworkElement endElement,
System.Windows.Shapes.Path bezierPath,
System.Windows.Shapes.Path arrowPath)
{
Point startPoint = startElement.TranslatePoint(new Point(startElement.ActualWidth / 2, startElement.ActualHeight / 2), canvas);
Point endPoint = CalculateEndpointOutsideElement(endElement, canvas, startPoint, out Localhost localhost);
// 根据终点位置决定起点位置 (位于控件的边缘)
startPoint = CalculateEdgePoint(startElement, localhost, canvas);
PathFigure pathFigure = new PathFigure { StartPoint = startPoint };
BezierSegment bezierSegment;
if (localhost == Localhost.Left || localhost == Localhost.Right)
{
bezierSegment = new BezierSegment
{
Point1 = new Point((startPoint.X + endPoint.X) / 2, startPoint.Y),
Point2 = new Point((startPoint.X + endPoint.X) / 2, endPoint.Y),
Point3 = endPoint,
};
}
else // if (localhost == Localhost.Top || localhost == Localhost.Bottom)
{
bezierSegment = new BezierSegment
{
Point1 = new Point(startPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point2 = new Point(endPoint.X, (startPoint.Y + endPoint.Y) / 2),
Point3 = endPoint,
};
}
var minZ = canvas.Children.OfType()//linq语句,取Zindex的最大值
.Select(x => Grid.GetZIndex(x))
.Min();
Grid.SetZIndex(bezierPath, minZ - 1);
// Canvas.SetZIndex(bezierPath, 0);
pathFigure.Segments.Add(bezierSegment);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(pathFigure);
bezierPath.Data = pathGeometry;
Point arrowStartPoint = CalculateBezierTangent(startPoint, bezierSegment.Point3, bezierSegment.Point2, endPoint);
UpdateArrowPath(endPoint, arrowStartPoint, arrowPath);
}
private static Point CalculateBezierTangent(Point startPoint, Point controlPoint1, Point controlPoint2, Point endPoint)
{
double t = 11; // 末端点
// 计算贝塞尔曲线在 t = 1 处的一阶导数
double dx = 3 * Math.Pow(1 - t, 2) * (controlPoint1.X - startPoint.X) +
6 * (1 - t) * t * (controlPoint2.X - controlPoint1.X) +
3 * Math.Pow(t, 2) * (endPoint.X - controlPoint2.X);
double dy = 3 * Math.Pow(1 - t, 2) * (controlPoint1.Y - startPoint.Y) +
6 * (1 - t) * t * (controlPoint2.Y - controlPoint1.Y) +
3 * Math.Pow(t, 2) * (endPoint.Y - controlPoint2.Y);
// 返回切线向量
return new Point(dx, dy);
}
// 绘制箭头
private static void UpdateArrowPath(Point endPoint,
Point controlPoint,
System.Windows.Shapes.Path arrowPath)
{
double arrowLength = 10;
double arrowWidth = 5;
Vector direction = endPoint - controlPoint;
direction.Normalize();
Point arrowPoint1 = endPoint + direction * arrowLength + new Vector(-direction.Y, direction.X) * arrowWidth;
Point arrowPoint2 = endPoint + direction * arrowLength + new Vector(direction.Y, -direction.X) * arrowWidth;
PathFigure arrowFigure = new PathFigure { StartPoint = endPoint };
arrowFigure.Segments.Add(new LineSegment(arrowPoint1, true));
arrowFigure.Segments.Add(new LineSegment(arrowPoint2, true));
arrowFigure.Segments.Add(new LineSegment(endPoint, true));
PathGeometry arrowGeometry = new PathGeometry();
arrowGeometry.Figures.Add(arrowFigure);
arrowPath.Data = arrowGeometry;
}
// 计算起点位于控件边缘的四个中心点之一
private static Point CalculateEdgePoint(FrameworkElement element, Localhost localhost, Canvas canvas)
{
Point point = new Point();
switch (localhost)
{
case Localhost.Right:
point = new Point(0, element.ActualHeight / 2); // 左边中心
break;
case Localhost.Left:
point = new Point(element.ActualWidth, element.ActualHeight / 2); // 右边中心
break;
case Localhost.Bottom:
point = new Point(element.ActualWidth / 2, 0); // 上边中心
break;
case Localhost.Top:
point = new Point(element.ActualWidth / 2, element.ActualHeight); // 下边中心
break;
}
// 计算角落
//switch (localhost)
//{
// case Localhost.Right:
// point = new Point(0, element.ActualHeight / 2); // 左边中心
// break;
// case Localhost.Left:
// point = new Point(element.ActualWidth, element.ActualHeight / 2); // 右边中心
// break;
// case Localhost.Bottom:
// point = new Point(element.ActualWidth / 2, 0); // 上边中心
// break;
// case Localhost.Top:
// point = new Point(element.ActualWidth / 2, element.ActualHeight); // 下边中心
// break;
//}
// 将相对控件的坐标转换到画布中的全局坐标
return element.TranslatePoint(point, canvas);
}
// 计算终点落点位置
private static Point CalculateEndpointOutsideElement(FrameworkElement element, Canvas canvas, Point startPoint, out Localhost localhost)
{
Point centerPoint = element.TranslatePoint(new Point(element.ActualWidth / 2, element.ActualHeight / 2), canvas);
Vector direction = centerPoint - startPoint;
direction.Normalize();
var tx = centerPoint.X - startPoint.X;
var ty = startPoint.Y - centerPoint.Y;
localhost = (tx < ty, Math.Abs(tx) > Math.Abs(ty)) switch
{
(true, true) => Localhost.Right,
(true, false) => Localhost.Bottom,
(false, true) => Localhost.Left,
(false, false) => Localhost.Top,
};
double halfWidth = element.ActualWidth / 2 + 10;
double halfHeight = element.ActualHeight / 2 + 10;
#region 固定中位
//if (localhost == Localhost.Left)
//{
// centerPoint.X -= halfWidth;
//}
//else if (localhost == Localhost.Right)
//{
// centerPoint.X -= -halfWidth;
//}
//else if (localhost == Localhost.Top)
//{
// centerPoint.Y -= halfHeight;
//}
//else if (localhost == Localhost.Bottom)
//{
// centerPoint.Y -= -halfHeight;
//}
#endregion
#region 落点自由移动
double margin = 0;
if (localhost == Localhost.Left)
{
centerPoint.X -= halfWidth;
centerPoint.Y -= direction.Y / (1 + Math.Abs(direction.X)) * halfHeight - margin;
}
else if (localhost == Localhost.Right)
{
centerPoint.X -= -halfWidth;
centerPoint.Y -= direction.Y / (1 + Math.Abs(direction.X)) * halfHeight - margin;
}
else if (localhost == Localhost.Top)
{
centerPoint.Y -= halfHeight;
centerPoint.X -= direction.X / (1 + Math.Abs(direction.Y)) * halfWidth - margin;
}
else if (localhost == Localhost.Bottom)
{
centerPoint.Y -= -halfHeight;
centerPoint.X -= direction.X / (1 + Math.Abs(direction.Y)) * halfWidth - margin;
}
#endregion
return centerPoint;
}
public static SolidColorBrush GetLineColor(ConnectionType currentConnectionType)
{
return currentConnectionType switch
{
ConnectionType.IsSucceed => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10")),
ConnectionType.IsFail => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905")),
ConnectionType.IsError => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FE1343")),
ConnectionType.Upstream => new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
_ => throw new Exception(),
};
}
}
#endregion
}