重写了节点的view、viewmodel关系,实现了对画布元素的选取功能,重构了底层依赖,添加了对net .Framework4.6.1以上的Framework类库支持

This commit is contained in:
fengjiayi
2024-09-12 20:32:54 +08:00
parent ec6e09ced1
commit f286fc644a
120 changed files with 91218 additions and 761 deletions

View File

@@ -146,7 +146,7 @@ namespace Serein.WorkBench
Shutdown(); // 关闭应用程序
}
}
else if (1 == 1)
else if (1 == 11)
{
string filePath = @"F:\临时\project\U9 project.dnf";
//string filePath = @"D:\Project\C#\DynamicControl\SereinFlow\.Output\Debug\net8.0-windows7.0\U9 project.dnf";

View File

@@ -93,6 +93,10 @@
x:Name="FlowChartCanvas"
Background="#D9FFEA"
AllowDrop="True"
Width="1000"
Height="700"
MouseLeftButtonDown ="FlowChartCanvas_MouseLeftButtonDown"
MouseLeftButtonUp="FlowChartCanvas_MouseLeftButtonUp"
MouseDown="FlowChartCanvas_MouseDown"
MouseMove="FlowChartCanvas_MouseMove"
MouseUp="FlowChartCanvas_MouseUp"
@@ -100,6 +104,14 @@
Drop="FlowChartCanvas_Drop"
DragOver="FlowChartCanvas_DragOver">
<Rectangle x:Name="SelectionRectangle"
Stroke="Blue"
StrokeThickness="2"
Fill="LightBlue"
Opacity="0.5"
Panel.ZIndex="999999"
Visibility="Collapsed"/>
<!-- Top-Left Thumb -->
<!--<Thumb x:Name="TopLeftThumb"
Width="10" Height="10"

View File

@@ -1,26 +1,25 @@
using Microsoft.Win32;
using Newtonsoft.Json.Linq;
using Serein.Library.IOC;
using Serein.Library.Attributes;
using Serein.Library.Utils;
using Serein.NodeFlow;
using Serein.NodeFlow.Model;
using Serein.NodeFlow.Tool;
using Serein.WorkBench.Node.View;
using Serein.WorkBench.Node.ViewModel;
using Serein.WorkBench.Themes;
using Serein.WorkBench.tool;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Cryptography.Xml;
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 System.Windows.Media.Media3D;
using System.Windows.Shapes;
using System.Xml.Linq;
using DataObject = System.Windows.DataObject;
namespace Serein.WorkBench
@@ -126,7 +125,7 @@ namespace Serein.WorkBench
/// <summary>
/// 一种轻量的IOC容器
/// </summary>
private ServiceContainer ServiceContainer { get; } = new ServiceContainer();
private SereinIoc ServiceContainer { get; } = new SereinIoc();
/// <summary>
/// 全局捕获Console输出事件打印在这个窗体里面
@@ -160,6 +159,20 @@ namespace Serein.WorkBench
/// </summary>
private readonly List<SingleFlipflopNode> flipflopNodes = [];
/// <summary>
/// 流程启动器
/// </summary>
private NodeFlowStarter nodeFlowStarter;
#region
/// <summary>
/// 当前选取的控件
/// </summary>
private readonly List<NodeControlBase> selectControls = [];
/// <summary>
/// 记录拖动开始时的鼠标位置
/// </summary>
@@ -185,6 +198,10 @@ namespace Serein.WorkBench
/// </summary>
private bool IsConnecting;
/// <summary>
/// 标记是否正在尝试选取控件
/// </summary>
private bool IsSelectControl;
/// <summary>
/// 标记是否正在拖动控件
/// </summary>
private bool IsControlDragging;
@@ -203,12 +220,10 @@ namespace Serein.WorkBench
/// <summary>
/// 平移画布
/// </summary>
private TranslateTransform translateTransform;
private TranslateTransform translateTransform;
#endregion
/// <summary>
/// 流程起点
/// </summary>
private NodeFlowStarter nodeFlowStarter;
public MainWindow()
@@ -259,10 +274,10 @@ namespace Serein.WorkBench
LoadDll(nf); // 加载DLL
LoadNodeControls(nf); // 加载节点
var startNode = nodeControls.FirstOrDefault(item => item.Node.Guid.Equals(nf.startNode));
var startNode = nodeControls.FirstOrDefault(control => control.ViewModel.Node.Guid.Equals(nf.startNode));
if (startNode != null)
{
startNode.Node.IsStart = true;
startNode.ViewModel.Node.IsStart = true;
SetIsStartBlock(startNode);
}
}
@@ -332,7 +347,7 @@ namespace Serein.WorkBench
}
else if (regionControl is ActionRegionControl actionRegionControl)
{
actionRegionControl.AddAction(nodeControl);
//actionRegionControl.AddAction(nodeControl);
}
}
else
@@ -396,22 +411,22 @@ namespace Serein.WorkBench
{
if (connectionType == ConnectionType.IsSucceed)
{
fromNode.Node.SucceedBranch.Add(toNode.Node);
fromNode.ViewModel.Node.SucceedBranch.Add(toNode.ViewModel.Node);
}
else if (connectionType == ConnectionType.IsFail)
{
fromNode.Node.FailBranch.Add(toNode.Node);
fromNode.ViewModel.Node.FailBranch.Add(toNode.ViewModel.Node);
}
else if (connectionType == ConnectionType.IsError)
{
fromNode.Node.ErrorBranch.Add(toNode.Node);
fromNode.ViewModel.Node.ErrorBranch.Add(toNode.ViewModel.Node);
}
else if (connectionType == ConnectionType.Upstream)
{
fromNode.Node.UpstreamBranch.Add(toNode.Node);
fromNode.ViewModel.Node.UpstreamBranch.Add(toNode.ViewModel.Node);
}
var connection = new Connection { Start = fromNode, End = toNode, Type = connectionType };
toNode.Node.PreviousNodes.Add(fromNode.Node);
toNode.ViewModel.Node.PreviousNodes.Add(fromNode.ViewModel.Node);
BsControl.Draw(FlowChartCanvas, connection);
ConfigureLineContextMenu(connection);
connections.Add(connection);
@@ -465,19 +480,19 @@ namespace Serein.WorkBench
NodeControlBase control = nodeInfo.type switch
{
$"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl<SingleActionNode, ActionNodeControl>(md),
$"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => CreateNodeControl<SingleFlipflopNode, FlipflopNodeControl>(md),
$"{NodeSpaceName}.{nameof(SingleActionNode)}" => CreateNodeControl<SingleActionNode, ActionNodeControl,ActionNodeControlViewModel>(md),
$"{NodeSpaceName}.{nameof(SingleFlipflopNode)}" => CreateNodeControl<SingleFlipflopNode, FlipflopNodeControl,FlipflopNodeControlViewModel>(md),
$"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateNodeControl<SingleConditionNode, ConditionNodeControl>(), // 条件表达式控件
$"{NodeSpaceName}.{nameof(SingleExpOpNode)}" => CreateNodeControl<SingleExpOpNode, ExpOpNodeControl>(), // 操作表达式控件
$"{NodeSpaceName}.{nameof(SingleConditionNode)}" => CreateNodeControl<SingleConditionNode, ConditionNodeControl,ConditionNodeControlViewModel>(), // 条件表达式控件
$"{NodeSpaceName}.{nameof(SingleExpOpNode)}" => CreateNodeControl<SingleExpOpNode, ExpOpNodeControl,ExpOpNodeViewModel>(), // 操作表达式控件
$"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateNodeControl<CompositeActionNode, ActionRegionControl>(),
$"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateNodeControl<CompositeConditionNode, ConditionRegionControl>(),
//$"{NodeSpaceName}.{nameof(CompositeActionNode)}" => CreateNodeControl<CompositeActionNode, ActionRegionControl>(),
$"{NodeSpaceName}.{nameof(CompositeConditionNode)}" => CreateNodeControl<CompositeConditionNode, ConditionRegionControl, ConditionRegionNodeControlViewModel>(),
_ => throw new NotImplementedException($"非预期的节点类型{nodeInfo.type}"),
};
// 如果是触发器,则需要添加到集合中
if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode flipflopNode)
if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.ViewModel.Node is SingleFlipflopNode flipflopNode)
{
var guid = flipflopNode.Guid;
if (!flipflopNodes.Exists(it => it.Guid.Equals(guid)))
@@ -485,22 +500,22 @@ namespace Serein.WorkBench
flipflopNodes.Add(flipflopNode);
}
}
var node = control.Node;
var node = control.ViewModel.Node;
if (node != null)
{
node.Guid = nodeInfo.guid;
for (int i = 0; i < nodeInfo.parameterData.Length; i++)
{
Parameterdata? pd = nodeInfo.parameterData[i];
if (control is ConditionNodeControl conditionNodeControl)
if (control is ConditionNodeControl conditionNodeControl && conditionNodeControl.ViewModel is ConditionNodeControlViewModel conditionNodeControlViewModel)
{
conditionNodeControl.ViewModel.IsCustomData = pd.state;
conditionNodeControl.ViewModel.CustomData = pd.value;
conditionNodeControl.ViewModel.Expression = pd.expression;
conditionNodeControlViewModel.IsCustomData = pd.state;
conditionNodeControlViewModel.CustomData = pd.value;
conditionNodeControlViewModel.Expression = pd.expression;
}
else if (control is ExpOpNodeControl expOpNodeControl)
else if (control is ExpOpNodeControl expOpNodeControl && expOpNodeControl.ViewModel is ExpOpNodeViewModel expOpNodeViewModel)
{
expOpNodeControl.ViewModel.Expression = pd.expression;
expOpNodeViewModel.Expression = pd.expression;
}
else
{
@@ -529,9 +544,10 @@ namespace Serein.WorkBench
#region
private static TControl CreateNodeControl<TNode, TControl>(MethodDetails? methodDetails = null)
private static TControl CreateNodeControl<TNode, TControl,TViewModel>(MethodDetails? methodDetails = null)
where TNode : NodeBase
where TControl : NodeControlBase
where TViewModel : NodeControlViewModelBase
{
var nodeObj = Activator.CreateInstance(typeof(TNode));
var nodeBase = nodeObj as NodeBase;
@@ -551,7 +567,8 @@ namespace Serein.WorkBench
nodeBase.MethodDetails = md;
}
var controlObj = Activator.CreateInstance(typeof(TControl), [nodeObj] );
var viewModel = Activator.CreateInstance(typeof(TViewModel), [nodeObj]);
var controlObj = Activator.CreateInstance(typeof(TControl), [viewModel] );
if(controlObj is TControl control)
{
return control;
@@ -570,7 +587,7 @@ namespace Serein.WorkBench
{
var contextMenu = new ContextMenu();
if (nodeControl.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
if (nodeControl.ViewModel.Node?.MethodDetails?.ReturnType is Type returnType && returnType != typeof(void))
{
contextMenu.Items.Add(CreateMenuItem("查看返回类型", (s, e) =>
{
@@ -673,39 +690,37 @@ namespace Serein.WorkBench
try
{
Assembly assembly = Assembly.LoadFrom(dllPath); // 加载DLL文件
Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型
List<Type> scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute<DynamicFlowAttribute>()?.Scan == true ).ToList();
//List<(bool, Type)> scanTypes = scanTypesTemp.Select(t => (t.GetCustomAttribute<Serein.Library.Framework.NodeFlow.Tool.DynamicFlowAttribute>()?.Scan == true, t)).ToList();
//bool isNetFramework = false;
//if(scanTypes.Count == 0)
//{
// scanTypes = assembly.GetTypes().Where(t => t.GetCustomAttribute<Library.Framework.NodeFlow.Tool.DynamicFlowAttribute>()?.Scan == true).ToList();
// if(scanTypes.Count == 0)
// {
// return;
// }
// else
// {
// isNetFramework = true;
// }
//}
loadedAssemblies.Add(assembly); // 将加载的程序集添加到列表中
loadedAssemblyPaths.Add(dllPath); // 记录加载的DLL路径
Type[] types = assembly.GetTypes(); // 获取程序集中的所有类型
List<MethodDetails> conditionMethods = [];
List<MethodDetails> actionMethods = [];
List<MethodDetails> flipflopMethods = [];
/* // 遍历类型,根据接口分类
foreach (Type type in types)
{
if (typeof(ICondition).IsAssignableFrom(type) && type.IsClass)
{
conditionTypes.Add(type); // 条件类型
}
if (typeof(IAction).IsAssignableFrom(type) && type.IsClass)
{
actionTypes.Add(type); // 动作类型
}
if (typeof(IState).IsAssignableFrom(type) && type.IsClass)
{
stateTypes.Add(type); // 状态类型
}
}*/
var scanTypes = assembly.GetTypes()
.Where(t => t.GetCustomAttribute<DynamicFlowAttribute>()?.Scan == true).ToList();
foreach (var type in scanTypes)
// foreach ((bool isNetFramework,Type type) item in scanTypes)
foreach (var item in scanTypes)
{
//加载DLL
var dict = DelegateGenerator.GenerateMethodDetails(ServiceContainer, type);
var dict = DelegateGenerator.GenerateMethodDetails(ServiceContainer, item, false);
foreach (var detail in dict)
{
@@ -713,33 +728,22 @@ namespace Serein.WorkBench
DllMethodDetails.TryAdd(detail.Key, detail.Value);
// 根据 DynamicType 分类
switch (detail.Value.MethodDynamicType)
switch (detail.Value.MethodDynamicType.ToString())
{
case DynamicNodeType.Condition:
case nameof(Serein.Library.Enums.NodeType.Condition):
conditionMethods.Add(detail.Value);
break;
case DynamicNodeType.Action:
case nameof(Serein.Library.Enums.NodeType.Action):
actionMethods.Add(detail.Value);
break;
case DynamicNodeType.Flipflop:
case nameof(Serein.Library.Enums.NodeType.Flipflop):
flipflopMethods.Add(detail.Value);
break;
//case DynamicNodeType.Init:
// initMethods.Add(detail.Value);
// break;
//case DynamicNodeType.Loading:
// loadingMethods.Add(detail.Value);
// break;
//case DynamicNodeType.Exit:
// exitMethods.Add(detail.Value);
// break;
}
DictMethodDetail.TryAdd(detail.Key, detail.Value);
// 将委托缓存到全局字典
DelegateCache.GlobalDicDelegates.TryAdd(detail.Key, detail.Value.MethodDelegate);
//globalDicDelegates.TryAdd(kvp.Key, kvp.Value.MethodDelegate);
}
}
@@ -941,13 +945,16 @@ namespace Serein.WorkBench
if (nodeControl != null)
{
// 尝试放置节点
// 首先判断是否为区域,如果是,则尝试将节点放置在区域中
if (TryPlaceNodeInRegion(nodeControl, dropPosition, e))
{
return;
}
PlaceNodeOnCanvas(nodeControl, dropPosition);
else
{
// 放置在画布上
PlaceNodeOnCanvas(nodeControl, dropPosition);
}
}
e.Handled = true;
@@ -963,7 +970,7 @@ namespace Serein.WorkBench
return droppedType switch
{
Type when typeof(ConditionRegionControl).IsAssignableFrom(droppedType)
=> CreateNodeControl<CompositeConditionNode, ConditionRegionControl>(), // 条件区域
=> CreateNodeControl<CompositeConditionNode, ConditionRegionControl, ConditionRegionNodeControlViewModel>(), // 条件区域
//Type when typeof(CompositeActionNode).IsAssignableFrom(droppedType)
// => CreateNodeControl<CompositeActionNode,ActionRegionControl>(), // 动作区域
@@ -982,9 +989,9 @@ namespace Serein.WorkBench
return droppedType switch
{
Type when typeof(ConditionNodeControl).IsAssignableFrom(droppedType)
=> CreateNodeControl<SingleConditionNode, ConditionNodeControl>(), // 条件控件
=> CreateNodeControl<SingleConditionNode, ConditionNodeControl,ConditionNodeControlViewModel>(), // 条件控件
Type when typeof(ExpOpNodeControl).IsAssignableFrom(droppedType)
=> CreateNodeControl<SingleExpOpNode, ExpOpNodeControl>(), // 操作表达式控件
=> CreateNodeControl<SingleExpOpNode, ExpOpNodeControl,ExpOpNodeViewModel>(), // 操作表达式控件
_ => throw new NotImplementedException("非预期的基础节点类型"),
};
}
@@ -1000,13 +1007,13 @@ namespace Serein.WorkBench
NodeControlBase control = methodDetails.MethodDynamicType switch
{
//DynamicNodeType.Condition => CreateNodeControl(typeof(SingleConditionNode), methodDetails), // 单个条件控件
DynamicNodeType.Action => CreateNodeControl<SingleActionNode, ActionNodeControl>(methodDetails),// 单个动作控件
DynamicNodeType.Flipflop => CreateNodeControl<SingleFlipflopNode, FlipflopNodeControl>(methodDetails), // 单个动作控件
Serein.Library.Enums.NodeType.Action => CreateNodeControl<SingleActionNode, ActionNodeControl, ActionNodeControlViewModel>(methodDetails),// 单个动作控件
Serein.Library.Enums.NodeType.Flipflop => CreateNodeControl<SingleFlipflopNode, FlipflopNodeControl, FlipflopNodeControlViewModel>(methodDetails), // 单个动作控件
_ => throw new NotImplementedException("非预期的Dll节点类型"),
};
// 如果是触发器,则需要添加到集合中
if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode flipflopNode)
if (control is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.ViewModel.Node is SingleFlipflopNode flipflopNode)
{
var guid = flipflopNode.Guid;
if (!flipflopNodes.Exists(it => it.Guid.Equals(guid)))
@@ -1032,45 +1039,19 @@ namespace Serein.WorkBench
if (hitTestResult != null && hitTestResult.VisualHit is UIElement hitElement)
{
var data = e.Data.GetData(MouseNodeType.BaseNodeType);
if(data is null)
{
return false;
}
if (data == typeof(ConditionNodeControl))
{
ConditionRegionControl conditionRegion = GetParentOfType<ConditionRegionControl>(hitElement);
if (conditionRegion != null)
{
conditionRegion.AddCondition(nodeControl);
return true;
}
}
//if (e.Data.GetData(MouseNodeType.DllNodeType) is MethodDetails methodDetails)
//{
// if (methodDetails.MethodDynamicType == DynamicNodeType.Condition)
// {
// ConditionRegionControl conditionRegion = GetParentOfType<ConditionRegionControl>(hitElement);
// if (conditionRegion != null)
// {
// conditionRegion.AddCondition(nodeControl);
// return true;
// }
// }
// else if (methodDetails.MethodDynamicType == DynamicNodeType.Action)
// {
// ActionRegionControl actionRegion = GetParentOfType<ActionRegionControl>(hitElement);
// if (actionRegion != null)
// {
// actionRegion.AddAction(nodeControl);
// return true;
// }
// }
//}
}
return false;
}
@@ -1098,7 +1079,7 @@ namespace Serein.WorkBench
/// <summary>
/// 获得目标类型的父类
/// 判断当前元素是否是泛型类型
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="element"></param>
@@ -1120,19 +1101,7 @@ namespace Serein.WorkBench
}
/// <summary>
/// 鼠标左键按下事件,关闭所有连接的动画效果
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
//// 关闭所有连线的动画效果
//foreach (var connection in connections)
//{
// connection.StopAnimation();
//}
}
/// <summary>
/// 拖动效果,根据拖放数据是否为指定类型设置拖放效果
@@ -1284,13 +1253,102 @@ namespace Serein.WorkBench
}
/// <summary>
/// 在画布中按下鼠标左键
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
{
IsSelectControl = true;
// 开始选取时,记录鼠标起始点
startPoint = e.GetPosition(FlowChartCanvas);
// 初始化选取矩形的位置和大小
Canvas.SetLeft(SelectionRectangle, startPoint.X);
Canvas.SetTop(SelectionRectangle, startPoint.Y);
SelectionRectangle.Width = 0;
SelectionRectangle.Height = 0;
// 显示选取矩形
SelectionRectangle.Visibility = Visibility.Visible;
// 捕获鼠标以便在鼠标移动到Canvas外部时仍能处理事件
FlowChartCanvas.CaptureMouse();
}
}
/// <summary>
/// 在画布中释放鼠标按下,结束选取状态
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (IsSelectControl)
{
IsSelectControl = false;
// 释放鼠标捕获
FlowChartCanvas.ReleaseMouseCapture();
// 隐藏选取矩形(如果需要保持选取状态,可以删除此行)
SelectionRectangle.Visibility = Visibility.Collapsed;
// 处理选取区域内的元素(例如,获取选取范围内的控件)
Rect selectionArea = new Rect(Canvas.GetLeft(SelectionRectangle),
Canvas.GetTop(SelectionRectangle),
SelectionRectangle.Width,
SelectionRectangle.Height);
selectControls.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)
{
selectControls.Add(control);
}
}
}
Console.WriteLine($"一共选取了{selectControls.Count}个控件");
}
}
/// <summary>
/// 鼠标在画布移动。
/// 选择控件状态下,调整选择框大小
/// 连接状态下,实时更新连接线的终点位置。
/// 移动画布状态下,移动画布。
/// </summary>
private void FlowChartCanvas_MouseMove(object sender, MouseEventArgs e)
{
if (IsSelectControl && e.LeftButton == MouseButtonState.Pressed)
{
// 获取当前鼠标位置
Point currentPoint = e.GetPosition(FlowChartCanvas);
// 更新选取矩形的位置和大小
double x = Math.Min(currentPoint.X, startPoint.X);
double y = Math.Min(currentPoint.Y, startPoint.Y);
double width = Math.Abs(currentPoint.X - startPoint.X);
double height = Math.Abs(currentPoint.Y - startPoint.Y);
Canvas.SetLeft(SelectionRectangle, x);
Canvas.SetTop(SelectionRectangle, y);
SelectionRectangle.Width = width;
SelectionRectangle.Height = height;
}
if (IsConnecting)
{
Point position = e.GetPosition(FlowChartCanvas);
@@ -1384,26 +1442,26 @@ namespace Serein.WorkBench
if (currentConnectionType == ConnectionType.IsSucceed)
{
startConnectBlock.Node.SucceedBranch.Add(targetBlock.Node);
startConnectBlock.ViewModel.Node.SucceedBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.IsFail)
{
startConnectBlock.Node.FailBranch.Add(targetBlock.Node);
startConnectBlock.ViewModel.Node.FailBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.IsError)
{
startConnectBlock.Node.ErrorBranch.Add(targetBlock.Node);
startConnectBlock.ViewModel.Node.ErrorBranch.Add(targetBlock.ViewModel.Node);
}
else if (currentConnectionType == ConnectionType.Upstream)
{
startConnectBlock.Node.UpstreamBranch.Add(targetBlock.Node);
startConnectBlock.ViewModel.Node.UpstreamBranch.Add(targetBlock.ViewModel.Node);
}
// 保存连接关系
BsControl.Draw(FlowChartCanvas, connection);
ConfigureLineContextMenu(connection);
targetBlock.Node.PreviousNodes.Add(startConnectBlock.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯)
targetBlock.ViewModel.Node.PreviousNodes.Add(startConnectBlock.ViewModel.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯)
connections.Add(connection);
}
EndConnection();
@@ -1461,7 +1519,7 @@ namespace Serein.WorkBench
/// <param name="nodeControl"></param>
private void DeleteBlock(NodeControlBase nodeControl)
{
if (nodeControl.Node.IsStart)
if (nodeControl.ViewModel.Node.IsStart)
{
if (nodeControls.Count > 1)
{
@@ -1470,10 +1528,10 @@ namespace Serein.WorkBench
}
flowStartBlock = null;
}
var RemoveEonnections = connections.Where(c => c.Start.Node.Guid.Equals(nodeControl.Node.Guid)
|| c.End.Node.Guid.Equals(nodeControl.Node.Guid)).ToList();
var RemoveEonnections = connections.Where(c => c.Start.ViewModel.Node.Guid.Equals(nodeControl.ViewModel.Node.Guid)
|| c.End.ViewModel.Node.Guid.Equals(nodeControl.ViewModel.Node.Guid)).ToList();
Remove(RemoveEonnections, nodeControl.Node);
Remove(RemoveEonnections, nodeControl.ViewModel.Node);
// 删除控件
FlowChartCanvas.Children.Remove(nodeControl);
nodeControls.Remove(nodeControl);
@@ -1494,8 +1552,8 @@ namespace Serein.WorkBench
var tempArr = connections.ToArray();
foreach (var connection in tempArr)
{
var startNode = connection.Start.Node;
var endNode = connection.End.Node;
var startNode = connection.Start.ViewModel.Node;
var endNode = connection.End.ViewModel.Node;
bool IsStartInThisConnection = false;
// 要删除的节点targetNode在连接关系中是否为起点
// 如果是,则需要从 targetNode 中删除子节点。
@@ -1570,19 +1628,21 @@ namespace Serein.WorkBench
if (nodeControl == null) { return; }
if (flowStartBlock != null)
{
flowStartBlock.Node.IsStart = false;
flowStartBlock.ViewModel.Node.IsStart = false;
flowStartBlock.BorderBrush = Brushes.Black;
flowStartBlock.BorderThickness = new Thickness(0);
}
nodeControl.Node.IsStart = true;
nodeControl.ViewModel.Node.IsStart = true;
nodeControl.BorderBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"));
nodeControl.BorderThickness = new Thickness(2);
flowStartBlock = nodeControl;
}
/// <summary>
/// 树形结构展开类型的成员
/// 查看返回类型(树形结构展开类型的成员
/// </summary>
/// <param name="type"></param>
private void DisplayReturnTypeTreeViewer(Type type)
@@ -1614,8 +1674,8 @@ namespace Serein.WorkBench
return;
}
// 获取起始节点与终止节点,消除映射关系
var StartNode = connectionToRemove.Start.Node;
var EndNode = connectionToRemove.End.Node;
var StartNode = connectionToRemove.Start.ViewModel.Node;
var EndNode = connectionToRemove.End.ViewModel.Node;
if (connectionToRemove.Type == ConnectionType.IsSucceed)
{
@@ -1687,95 +1747,6 @@ namespace Serein.WorkBench
FlowChartCanvas.Height = height;
}
#region
// 画布缩放时调整画布大小(已弃用)
private void AdjustCanvasSizeToContainer()
{
// 获取当前缩放后的画布大小
double newWidth = FlowChartCanvas.Width * scaleTransform.ScaleX;
double newHeight = FlowChartCanvas.Height * scaleTransform.ScaleY;
// 设置画布大小使其至少与ScrollViewer的可视区域大小相同
FlowChartCanvas.Width = Math.Max(FlowChartStackPanel.Width, newWidth);
FlowChartCanvas.Height = Math.Max(FlowChartStackPanel.Height, newHeight);
}
// 窗体尺寸发生变化时调整画布大小(已弃用)
private void AdjustCanvasSize()
{
// 获取 ScrollViewer 可视区域的宽度和高度
double scrollViewerWidth = FlowChartStackPanel.Width * scaleTransform.ScaleX;
double scrollViewerHeight = FlowChartStackPanel.Height * scaleTransform.ScaleY;
// 调整 Canvas 大小
if (scrollViewerWidth > 0 && scrollViewerHeight > 0)
{
FlowChartCanvas.Width = scrollViewerWidth;
FlowChartCanvas.Height = scrollViewerHeight;
}
}
// 随着平移拖动动态调整画布大小(弃用)
private void AdjustCanvasSizeAndContent(double deltaX, double deltaY)
{
// 获取画布的边界框
Rect transformedBounds = FlowChartCanvas.RenderTransform.TransformBounds(new Rect(FlowChartCanvas.RenderSize));
Debug.WriteLine($"deltaX : {deltaX},{deltaY}");
Debug.WriteLine($" LTRP : {transformedBounds.Left},{transformedBounds.Top},{transformedBounds.Right},{transformedBounds.Bottom}");
// 检查画布的左边缘是否超出视图
if (deltaX > 0 && transformedBounds.Left > 0)
{
double offsetX = transformedBounds.Left;
FlowChartCanvas.Width += offsetX;
translateTransform.X -= offsetX;
Debug.Print($" offsetX : {offsetX}");
// 移动所有控件的位置
foreach (UIElement child in FlowChartCanvas.Children)
{
Canvas.SetLeft(child, Canvas.GetLeft(child) + offsetX);
}
}
// 检查画布的上边缘是否超出视图
//if (transformedBounds.Top > 0)
if (deltaY > 0 & transformedBounds.Top > 0)
{
double offsetY = transformedBounds.Top;
FlowChartCanvas.Height += offsetY;
translateTransform.Y -= offsetY;
Debug.Print($" offsetY : {offsetY}");
// 移动所有控件的位置
foreach (UIElement child in FlowChartCanvas.Children)
{
Canvas.SetTop(child, Canvas.GetTop(child) + offsetY);
}
}
var size = 50;
// 检查画布的右边缘是否超出当前宽度
if (transformedBounds.Right + size < FlowChartStackPanel.ActualWidth)
{
double extraWidth = FlowChartStackPanel.ActualWidth - transformedBounds.Right;
this.FlowChartCanvas.Width += extraWidth;
}
// 检查画布的下边缘是否超出当前高度
if (transformedBounds.Bottom + size < FlowChartStackPanel.ActualHeight)
{
double extraHeight = FlowChartStackPanel.ActualHeight - transformedBounds.Bottom;
this.FlowChartCanvas.Height += extraHeight;
}
}
#endregion
#region
//private void Thumb_DragDelta_TopLeft(object sender, DragDeltaEventArgs e)
@@ -1913,7 +1884,7 @@ namespace Serein.WorkBench
private async void ButtonDebugRun_Click(object sender, RoutedEventArgs e)
{
logWindow?.Show();
var nodes = nodeControls.Select(it => it.Node).ToList();
var nodes = nodeControls.Select(control => control.ViewModel.Node).ToList();
var methodDetails = DictMethodDetail.Values.ToList();
nodeFlowStarter ??= new NodeFlowStarter(ServiceContainer, methodDetails);
await nodeFlowStarter.RunAsync(nodes);
@@ -1942,19 +1913,19 @@ namespace Serein.WorkBench
try
{
// 生成节点信息
var nodeInfos = nodeControls.Select(item =>
var nodeInfos = nodeControls.Select(control =>
{
var node = item.Node;
Point positionRelativeToParent = item.TranslatePoint(new Point(0, 0), FlowChartCanvas);
var trueNodes = item.Node.SucceedBranch.Select(item => item.Guid); // 真分支
var falseNodes = item.Node.FailBranch.Select(item => item.Guid);// 假分支
var upstreamNodes = item.Node.UpstreamBranch.Select(item => item.Guid);// 上游分支
var node = control.ViewModel.Node;
Point positionRelativeToParent = control.TranslatePoint(new Point(0, 0), FlowChartCanvas);
var trueNodes = control.ViewModel.Node.SucceedBranch.Select(item => item.Guid); // 真分支
var falseNodes = control.ViewModel.Node.FailBranch.Select(item => item.Guid);// 假分支
var upstreamNodes = control.ViewModel.Node.UpstreamBranch.Select(item => item.Guid);// 上游分支
// 常规节点的参数信息
List<Parameterdata> parameterData = [];
if (node?.MethodDetails?.ExplicitDatas is not null
&& (node.MethodDetails.MethodDynamicType == DynamicNodeType.Action
|| node.MethodDetails.MethodDynamicType == DynamicNodeType.Flipflop))
&& (node.MethodDetails.MethodDynamicType == Serein.Library.Enums.NodeType.Action
|| node.MethodDetails.MethodDynamicType == Serein.Library.Enums.NodeType.Flipflop))
{
parameterData = node.MethodDetails
.ExplicitDatas
@@ -2022,7 +1993,7 @@ namespace Serein.WorkBench
.Select(region =>
{
WriteLog(region.GetType().ToString() + "\r\n");
if (region is ConditionRegionControl && region.Node is CompositeConditionNode conditionRegion) // 条件区域控件
if (region is ConditionRegionControl && region.ViewModel.Node is CompositeConditionNode conditionRegion) // 条件区域控件
{
List<object> childNodes = [];
var tmpChildNodes = conditionRegion.ConditionNodes;
@@ -2046,11 +2017,11 @@ namespace Serein.WorkBench
}
return new
{
guid = region.Node.Guid,
guid = region.ViewModel.Node.Guid,
childNodes = childNodes
};
}
else if (region is ActionRegionControl && region.Node is CompositeActionNode actionRegion) // 动作区域控件
else if (region is ActionRegionControl && region.ViewModel.Node is CompositeActionNode actionRegion) // 动作区域控件
{
//WriteLog(region.Node.GetType().ToString() + "\r\n");
@@ -2076,7 +2047,7 @@ namespace Serein.WorkBench
}
return new
{
guid = region.Node.Guid,
guid = region.ViewModel.Node.Guid,
childNodes = childNodes
};
}
@@ -2129,7 +2100,7 @@ namespace Serein.WorkBench
["versions"] = "1",
},
["library"] = JArray.FromObject(dlls),
["startNode"] = flowStartBlock == null ? "" : flowStartBlock.Node.Guid,
["startNode"] = flowStartBlock == null ? "" : flowStartBlock.ViewModel.Node.Guid,
["nodes"] = JArray.FromObject(nodeInfos),
["regions"] = JArray.FromObject(regionObjs),
};
@@ -2245,6 +2216,8 @@ namespace Serein.WorkBench
{
// AdjustCanvasSize();
}
}
#region UI层面上显示为 线

View File

@@ -10,16 +10,10 @@ namespace Serein.WorkBench.Node.View
/// </summary>
public partial class ActionNodeControl : NodeControlBase
{
private readonly ActionNodeControlViewModel actionNodeControlViewModel;
public ActionNodeControl(SingleActionNode node) : base(node)
public ActionNodeControl(ActionNodeControlViewModel viewModel):base(viewModel)
{
Node = node;
actionNodeControlViewModel = new ActionNodeControlViewModel(node);
DataContext = actionNodeControlViewModel;
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -13,18 +13,21 @@ namespace Serein.WorkBench.Node.View
{
private Point _dragStartPoint;
private new readonly CompositeActionNode Node;
//private new readonly CompositeActionNode Node;
public ActionRegionControl() : base()
//public override NodeControlViewModel ViewModel { get ; set ; }
public ActionRegionControl() : base(null)
{
InitializeComponent();
}
public ActionRegionControl(CompositeActionNode node) : base(node)
{
InitializeComponent();
Node = node;
base.Name = "动作组合节点";
}
//public ActionRegionControl(CompositeActionNode node)
//{
// InitializeComponent();
// //ViewModel = new NodeControlViewModel(node);
// DataContext = ViewModel;
// base.Name = "动作组合节点";
//}
public void AddAction(NodeControlBase node, bool isTask = false)
{
@@ -34,8 +37,8 @@ namespace Serein.WorkBench.Node.View
Margin = new Thickness(10, 2, 0, 0),
Tag = node.MethodDetails,
};*/
Node?.AddNode((SingleActionNode)node.Node);
ActionsListBox.Items.Add(node);
/// Node?.AddNode((SingleActionNode)node.ViewModel.Node);
// ActionsListBox.Items.Add(node);
}
/* public async Task ExecuteActions(DynamicContext context)

View File

@@ -8,24 +8,19 @@ namespace Serein.WorkBench.Node.View
/// </summary>
public partial class ConditionNodeControl : NodeControlBase
{
public ConditionNodeControlViewModel ViewModel { get; }
public ConditionNodeControl() : base()
{
ViewModel = new (new ());
// 窗体初始化需要
ViewModel = new ConditionNodeControlViewModel (new SingleConditionNode());
DataContext = ViewModel;
InitializeComponent();
}
public ConditionNodeControl(SingleConditionNode node) : base(node)
public ConditionNodeControl(ConditionNodeControlViewModel viewModel):base(viewModel)
{
Node = node;
ViewModel = new ConditionNodeControlViewModel(node);
DataContext = ViewModel;
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -6,14 +6,9 @@
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
MaxWidth="300">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
<StackPanel>
<DockPanel Margin="2,2,2,5">
<!--<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>-->
<TextBlock Text="条件区域" FontWeight="Bold" HorizontalAlignment="Left" FontSize="14" Margin="0,1,0,0"/>
<Button Content="编辑" FontWeight="Bold" HorizontalAlignment="Right"/>
</DockPanel>

View File

@@ -1,4 +1,5 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
@@ -12,26 +13,29 @@ namespace Serein.WorkBench.Node.View
public partial class ConditionRegionControl : NodeControlBase
{
private Point _dragStartPoint;
public ConditionRegionControl() : base()
{
InitializeComponent();
}
public ConditionRegionControl(CompositeConditionNode node) : base(node)
public ConditionRegionControl(ConditionRegionNodeControlViewModel viewModel) : base(viewModel)
{
Node = node;
DataContext = viewModel;
InitializeComponent();
}
/// <summary>
/// 添加条件控件
/// </summary>
/// <param name="condition"></param>
public void AddCondition(NodeControlBase node)
{
((CompositeConditionNode)Node).AddNode((SingleConditionNode)node.Node);
((CompositeConditionNode)ViewModel.Node).AddNode((SingleConditionNode)node.ViewModel.Node);
this.Width += node.Width;
this.Height += node.Height;
@@ -46,27 +50,27 @@ namespace Serein.WorkBench.Node.View
}
// Mouse event handlers for dragging
private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_dragStartPoint = e.GetPosition(null);
}
//private void TypeText_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
//{
// _dragStartPoint = e.GetPosition(null);
//}
private void TypeText_MouseMove(object sender, MouseEventArgs e)
{
Point mousePos = e.GetPosition(null);
Vector diff = _dragStartPoint - mousePos;
//private void TypeText_MouseMove(object sender, MouseEventArgs e)
//{
// Point mousePos = e.GetPosition(null);
// Vector diff = _dragStartPoint - mousePos;
if (e.LeftButton == MouseButtonState.Pressed &&
(Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
{
if (sender is TextBlock typeText)
{
var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag);
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}
// if (e.LeftButton == MouseButtonState.Pressed &&
// (Math.Abs(diff.X) > SystemParameters.MinimumHorizontalDragDistance ||
// Math.Abs(diff.Y) > SystemParameters.MinimumVerticalDragDistance))
// {
// if (sender is TextBlock typeText)
// {
// var dragData = new DataObject(MouseNodeType.RegionType, typeText.Tag);
// DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
// }
// }
//}
@@ -86,8 +90,7 @@ namespace Serein.WorkBench.Node.View
DragDrop.DoDragDrop(typeText, dragData, DragDropEffects.Move);
}
}
}
*/
}*/
}
}

View File

@@ -8,21 +8,17 @@ namespace Serein.WorkBench.Node.View
/// </summary>
public partial class ExpOpNodeControl : NodeControlBase
{
public ExpOpNodeViewModel ViewModel { get; }
public ExpOpNodeControl()
public ExpOpNodeControl() : base()
{
ViewModel = new (new());
// 窗体初始化需要
ViewModel = new ExpOpNodeViewModel(new SingleExpOpNode());
DataContext = ViewModel;
InitializeComponent();
}
public ExpOpNodeControl(SingleExpOpNode node):base(node)
public ExpOpNodeControl(ExpOpNodeViewModel viewModel) :base(viewModel)
{
Node = node;
ViewModel = new(node);
DataContext = ViewModel;
DataContext = viewModel;
InitializeComponent();
}
}
}

View File

@@ -8,31 +8,10 @@ namespace Serein.WorkBench.Node.View
/// </summary>
public partial class FlipflopNodeControl : NodeControlBase
{
private readonly FlipflopNodeControlViewModel viewModel;
public FlipflopNodeControl(SingleFlipflopNode node) : base(node)
public FlipflopNodeControl(FlipflopNodeControlViewModel viewModel) : base(viewModel)
{
Node = node;
viewModel = new FlipflopNodeControlViewModel(node);
DataContext = viewModel;
InitializeComponent();
}
//private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
//{
// var comboBox = sender as ComboBox;
// if (comboBox == null)
// {
// return;
// }
// var selectedExplicitData = comboBox.DataContext as ExplicitData;
// if (selectedExplicitData == null)
// {
// return;
// }
// Console.WriteLine (selectedExplicitData.DataValue, "Selected Value");
//}
}
}

View File

@@ -15,27 +15,42 @@ namespace Serein.WorkBench.Node.View
/// </summary>
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
{
public NodeBase Node { get; set; }
public NodeControlViewModelBase ViewModel { get; set; }
protected NodeControlBase()
{
this.Background = Brushes.Transparent;
}
protected NodeControlBase(NodeBase node)
protected NodeControlBase(NodeControlViewModelBase viewModelBase)
{
ViewModel = viewModelBase;
this.Background = Brushes.Transparent;
Node = node;
}
}
public abstract class NodeControlViewModel : INotifyPropertyChanged
public abstract class NodeControlViewModelBase : INotifyPropertyChanged
{
public NodeControlViewModelBase(NodeBase node)
{
this.Node = node;
MethodDetails = this.Node.MethodDetails;
}
/// <summary>
/// 对应的节点实体类
/// </summary>
public NodeBase Node { get; set; }
public MethodDetails methodDetails;
/// <summary>
/// 表示节点控件是否被选中
/// </summary>
public bool IsSelect { get; set; } = false;
private MethodDetails methodDetails;
public MethodDetails MethodDetails

View File

@@ -3,26 +3,13 @@ using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ActionNodeControlViewModel : NodeControlViewModel
public class ActionNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleActionNode node;
public ActionNodeControlViewModel(SingleActionNode node)
public ActionNodeControlViewModel(SingleActionNode node):base(node)
{
this.node = node;
MethodDetails = node.MethodDetails;
//if (node.MethodDetails.ExplicitDatas.Length == 0)
//{
// // 没有显式项
// IsExistExplicitData = false;
// ExplicitDatas = [];
//}
//else
//{
// explicitDatas = node.MethodDetails.ExplicitDatas;
// //ExplicitDatas = node.MethodDetails.ExplicitDatas;
// IsExistExplicitData = true;
//}
}
}
}

View File

@@ -3,7 +3,7 @@ using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ConditionNodeControlViewModel : NodeControlViewModel
public class ConditionNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleConditionNode singleConditionNode;
@@ -32,10 +32,9 @@ namespace Serein.WorkBench.Node.ViewModel
set { singleConditionNode.Expression = value; OnPropertyChanged(); }
}
public ConditionNodeControlViewModel(SingleConditionNode node)
public ConditionNodeControlViewModel(SingleConditionNode node) : base(node)
{
this.singleConditionNode = node;
MethodDetails = node.MethodDetails;
IsCustomData = false;
CustomData = "";
Expression = "PASS";

View File

@@ -0,0 +1,18 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.View;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Serein.WorkBench.Node.ViewModel
{
public class ConditionRegionNodeControlViewModel : NodeControlViewModelBase
{
public ConditionRegionNodeControlViewModel(CompositeConditionNode node):base(node)
{
}
}
}

View File

@@ -3,7 +3,7 @@ using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class ExpOpNodeViewModel: NodeControlViewModel
public class ExpOpNodeViewModel: NodeControlViewModelBase
{
public readonly SingleExpOpNode node;
@@ -18,7 +18,7 @@ namespace Serein.WorkBench.Node.ViewModel
}
public ExpOpNodeViewModel(SingleExpOpNode node)
public ExpOpNodeViewModel(SingleExpOpNode node) : base(node)
{
this.node = node;
}

View File

@@ -3,13 +3,12 @@ using Serein.WorkBench.Node.View;
namespace Serein.WorkBench.Node.ViewModel
{
public class FlipflopNodeControlViewModel : NodeControlViewModel
public class FlipflopNodeControlViewModel : NodeControlViewModelBase
{
private readonly SingleFlipflopNode node;
public FlipflopNodeControlViewModel(SingleFlipflopNode node)
public FlipflopNodeControlViewModel(SingleFlipflopNode node) : base(node)
{
this.node = node;
MethodDetails = node.MethodDetails;
}
}
}

View File

@@ -35,7 +35,9 @@
</ItemGroup>
<ItemGroup>
<!--<ProjectReference Include="..\DynamicControl\DynamicControl.csproj" />-->
<ProjectReference Include="..\Library.Core\Serein.Library.Core.csproj" />
<ProjectReference Include="..\Library.Framework\Serein.Library.Framework.csproj" />
<ProjectReference Include="..\Library\Serein.Library.csproj" />
<ProjectReference Include="..\NodeFlow\Serein.NodeFlow.csproj" />
</ItemGroup>