mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-03-20 00:06:45 +08:00
准备区分节点、参数、返回值的连接,做个备份
This commit is contained in:
93
Workbench/Node/Junction/JunctionCode.cs
Normal file
93
Workbench/Node/Junction/JunctionCode.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Utils;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
|
||||
|
||||
public abstract class JunctionControlBase : UserControl
|
||||
{
|
||||
|
||||
public double _MyWidth = 20;
|
||||
public double _MyHeight = 20;
|
||||
|
||||
protected JunctionControlBase()
|
||||
{
|
||||
//this.Width = 20;
|
||||
//this.Height = 20;
|
||||
this.MouseDown += ControlPointBase_MouseDown;
|
||||
this.MouseMove += ControlPointBase_MouseMove; ;
|
||||
}
|
||||
#region 控件属性,所在的节点
|
||||
public static readonly DependencyProperty NodeGuidProperty =
|
||||
DependencyProperty.Register("NodeGuid", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
|
||||
|
||||
/// <summary>
|
||||
/// 所在的节点
|
||||
/// </summary>
|
||||
public string NodeGuid
|
||||
{
|
||||
get { return (string)GetValue(NodeGuidProperty); }
|
||||
set { SetValue(NodeGuidProperty, value.ToString()); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region 控件属性,连接器类型
|
||||
public static readonly DependencyProperty JunctionTypeProperty =
|
||||
DependencyProperty.Register("JunctionType", typeof(string), typeof(JunctionControlBase), new PropertyMetadata(default(string)));
|
||||
|
||||
public JunctionType JunctionType
|
||||
{
|
||||
get { return EnumHelper.ConvertEnum<JunctionType>(GetValue(JunctionTypeProperty).ToString()); }
|
||||
set { SetValue(JunctionTypeProperty, value.ToString()); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
public abstract void Render();
|
||||
private void ControlPointBase_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (GlobalJunctionData.MyGlobalData is null) return;
|
||||
GlobalJunctionData.MyGlobalData.ChangingJunction = this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在碰撞点上按下鼠标控件开始进行移动
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
private void ControlPointBase_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
GlobalJunctionData.MyGlobalData = new ConnectingData();
|
||||
var myDataType = GlobalJunctionData.MyGlobalData;
|
||||
myDataType.StartJunction = this;
|
||||
var canvas = MainWindow.GetParentOfType<Canvas>(this);
|
||||
myDataType.StartPoint = this.TranslatePoint(new Point(this.Width /2 , this.Height /2 ), canvas);
|
||||
myDataType.VirtualLine = new MyLine(canvas, new Line // 虚拟线
|
||||
{
|
||||
Stroke = Brushes.OldLace,
|
||||
StrokeThickness = 2
|
||||
});
|
||||
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
69
Workbench/Node/Junction/JunctionData.cs
Normal file
69
Workbench/Node/Junction/JunctionData.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
#region Model,不科学的全局变量
|
||||
public class MyLine
|
||||
{
|
||||
public MyLine(Canvas canvas, Line line)
|
||||
{
|
||||
Canvas = canvas;
|
||||
VirtualLine = line;
|
||||
canvas?.Children.Add(line);
|
||||
}
|
||||
|
||||
public Canvas Canvas { get; set; }
|
||||
public Line VirtualLine { get; set; }
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Canvas?.Children.Remove(VirtualLine);
|
||||
}
|
||||
}
|
||||
|
||||
public class ConnectingData
|
||||
{
|
||||
public JunctionControlBase StartJunction { get; set; }
|
||||
public JunctionControlBase ChangingJunction { get; set; }
|
||||
public Point StartPoint { get; set; }
|
||||
public MyLine VirtualLine { get; set; }
|
||||
}
|
||||
|
||||
public static class GlobalJunctionData
|
||||
{
|
||||
private static ConnectingData? myGlobalData;
|
||||
|
||||
public static ConnectingData? MyGlobalData
|
||||
{
|
||||
get => myGlobalData;
|
||||
set
|
||||
{
|
||||
if (myGlobalData == null)
|
||||
{
|
||||
myGlobalData = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static bool IsCreatingConnection => myGlobalData is not null;
|
||||
|
||||
public static bool CanCreate => myGlobalData?.ChangingJunction.Equals(myGlobalData?.StartJunction) == false;
|
||||
|
||||
public static void OK()
|
||||
{
|
||||
myGlobalData?.VirtualLine.Remove();
|
||||
myGlobalData = null;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
237
Workbench/Node/Junction/NodeJunctionViewBase.cs
Normal file
237
Workbench/Node/Junction/NodeJunctionViewBase.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using Serein.Workbench.Node.View;
|
||||
using System.Windows.Controls;
|
||||
using Serein.Library;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
|
||||
public abstract class NodeJunctionViewBase : ContentControl, IDisposable
|
||||
{
|
||||
public NodeJunctionViewBase()
|
||||
{
|
||||
var transfromGroup = new TransformGroup();
|
||||
transfromGroup.Children.Add(_Translate);
|
||||
RenderTransform = transfromGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 每个连接器都有一个唯一标识符(Guid),用于标识连接器。
|
||||
/// </summary>
|
||||
public Guid Guid
|
||||
{
|
||||
get => (Guid)GetValue(GuidProperty);
|
||||
set => SetValue(GuidProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty GuidProperty = DependencyProperty.Register(
|
||||
nameof(Guid),
|
||||
typeof(Guid),
|
||||
typeof(NodeJunctionViewBase), // NodeConnectorContent
|
||||
new PropertyMetadata(Guid.Empty));
|
||||
|
||||
/// <summary>
|
||||
/// 连接器当前的连接数,表示有多少条 NodeLink 连接到此连接器。该属性为只读。
|
||||
/// </summary>
|
||||
public int ConnectedCount
|
||||
{
|
||||
get => (int)GetValue(ConnectedCountProperty);
|
||||
private set => SetValue(ConnectedCountPropertyKey, value);
|
||||
}
|
||||
public static readonly DependencyPropertyKey ConnectedCountPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(ConnectedCount),
|
||||
typeof(int),
|
||||
typeof(NodeJunctionViewBase), // NodeConnectorContent
|
||||
new PropertyMetadata(0));
|
||||
|
||||
public static readonly DependencyProperty ConnectedCountProperty = ConnectedCountPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 布尔值,指示此连接器是否有任何连接。
|
||||
/// </summary>
|
||||
public bool IsConnected
|
||||
{
|
||||
get => (bool)GetValue(IsConnectedProperty);
|
||||
private set => SetValue(IsConnectedPropertyKey, value);
|
||||
}
|
||||
public static readonly DependencyPropertyKey IsConnectedPropertyKey = DependencyProperty.RegisterReadOnly(
|
||||
nameof(IsConnected),
|
||||
typeof(bool),
|
||||
typeof(NodeJunctionViewBase), // NodeConnectorContent
|
||||
new PropertyMetadata(false));
|
||||
|
||||
public static readonly DependencyProperty IsConnectedProperty = IsConnectedPropertyKey.DependencyProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
|
||||
/// </summary>
|
||||
public Brush Stroke
|
||||
{
|
||||
get => (Brush)GetValue(StrokeProperty);
|
||||
set => SetValue(StrokeProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
|
||||
nameof(Stroke),
|
||||
typeof(Brush),
|
||||
typeof(NodeJunctionViewBase), // NodeConnectorContent
|
||||
new FrameworkPropertyMetadata(Brushes.Blue));
|
||||
|
||||
/// <summary>
|
||||
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
|
||||
/// </summary>
|
||||
public double StrokeThickness
|
||||
{
|
||||
get => (double)GetValue(StrokeThicknessProperty);
|
||||
set => SetValue(StrokeThicknessProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
|
||||
nameof(StrokeThickness),
|
||||
typeof(double),
|
||||
typeof(NodeJunctionViewBase), // NodeConnectorContent
|
||||
new FrameworkPropertyMetadata(1.0));
|
||||
|
||||
/// <summary>
|
||||
/// 这些属性控制连接器的外观(颜色、边框厚度、填充颜色)。
|
||||
/// </summary>
|
||||
public Brush Fill
|
||||
{
|
||||
get => (Brush)GetValue(FillProperty);
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
|
||||
nameof(Fill),
|
||||
typeof(Brush),
|
||||
typeof(NodeJunctionViewBase),// NodeConnectorContent
|
||||
new FrameworkPropertyMetadata(Brushes.Gray));
|
||||
|
||||
/// <summary>
|
||||
/// 指示该连接器是否可以与其他连接器进行连接。
|
||||
/// </summary>
|
||||
public bool CanConnect
|
||||
{
|
||||
get => (bool)GetValue(CanConnectProperty);
|
||||
set => SetValue(CanConnectProperty, value);
|
||||
}
|
||||
public static readonly DependencyProperty CanConnectProperty = DependencyProperty.Register(
|
||||
nameof(CanConnect),
|
||||
typeof(bool),
|
||||
typeof(NodeJunctionViewBase),// NodeConnectorContent
|
||||
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
|
||||
private Point _Position = new Point();
|
||||
/// <summary>
|
||||
/// 该连接器的当前坐标(位置)。
|
||||
/// </summary>
|
||||
public Point Position
|
||||
{
|
||||
get => _Position;
|
||||
set => UpdatePosition(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// (重要数据)表示连接器所属的节点。
|
||||
/// </summary>
|
||||
public NodeModelBase NodeModel { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 该连接器所连接的所有 NodeLink 的集合。
|
||||
/// </summary>
|
||||
public IEnumerable<ConnectionControl> NodeLinks => _NodeLinks;
|
||||
List<ConnectionControl> _NodeLinks = new List<ConnectionControl>();
|
||||
|
||||
protected abstract FrameworkElement ConnectorControl { get; }
|
||||
TranslateTransform _Translate = new TranslateTransform();
|
||||
void UpdatePosition(Point pos)
|
||||
{
|
||||
_Position = pos;
|
||||
_Translate.X = _Position.X;
|
||||
_Translate.Y = _Position.Y;
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 NodeLink 添加到连接器,并更新 ConnectedCount 和 IsConnected。
|
||||
/// </summary>
|
||||
/// <param name="nodeLink"></param>
|
||||
public void Connect(ConnectionControl nodeLink)
|
||||
{
|
||||
_NodeLinks.Add(nodeLink);
|
||||
ConnectedCount = _NodeLinks.Count;
|
||||
IsConnected = ConnectedCount > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开与某个 NodeLink 的连接,更新连接状态。
|
||||
/// </summary>
|
||||
/// <param name="nodeLink"></param>
|
||||
public void Disconnect(ConnectionControl nodeLink)
|
||||
{
|
||||
_NodeLinks.Remove(nodeLink);
|
||||
ConnectedCount = _NodeLinks.Count;
|
||||
IsConnected = ConnectedCount > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取连接器相对于指定 Canvas 的位置。
|
||||
/// </summary>
|
||||
/// <param name="canvas"></param>
|
||||
/// <param name="xScaleOffset"></param>
|
||||
/// <param name="yScaleOffset"></param>
|
||||
/// <returns></returns>
|
||||
public Point GetContentPosition(Canvas canvas, double xScaleOffset = 0.5, double yScaleOffset = 0.5)
|
||||
{
|
||||
// it will be shifted Control position if not called UpdateLayout().
|
||||
ConnectorControl.UpdateLayout();
|
||||
var transformer = ConnectorControl.TransformToVisual(canvas);
|
||||
|
||||
var x = ConnectorControl.ActualWidth * xScaleOffset;
|
||||
var y = ConnectorControl.ActualHeight * yScaleOffset;
|
||||
return transformer.Transform(new Point(x, y));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新与此连接器相连的所有 NodeLink 的位置。这个方法是抽象的,要求子类实现。
|
||||
/// </summary>
|
||||
/// <param name="canvas"></param>
|
||||
public abstract void UpdateLinkPosition(Canvas canvas);
|
||||
|
||||
/// <summary>
|
||||
/// 用于检查此连接器是否可以与另一个连接器相连接,要求子类实现。
|
||||
/// </summary>
|
||||
/// <param name="connector"></param>
|
||||
/// <returns></returns>
|
||||
public abstract bool CanConnectTo(NodeJunctionViewBase connector);
|
||||
|
||||
/// <summary>
|
||||
/// 释放连接器相关的资源,包括样式、绑定和已连接的 NodeLink
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
// You need to clear Style.
|
||||
// Because implemented on style for binding.
|
||||
Style = null;
|
||||
|
||||
// Clear binding for subscribing source changed event from old control.
|
||||
// throw exception about visual tree ancestor different if you not clear binding.
|
||||
BindingOperations.ClearAllBindings(this);
|
||||
|
||||
var nodeLinks = _NodeLinks.ToArray();
|
||||
|
||||
// it must instance to nodeLinks because change node link collection in NodeLink Dispose.
|
||||
foreach (var nodeLink in nodeLinks)
|
||||
{
|
||||
// nodeLink.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
54
Workbench/Node/Junction/View/ArgJunctionControl.cs
Normal file
54
Workbench/Node/Junction/View/ArgJunctionControl.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Serein.Library;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
public class ArgJunctionControl : JunctionControlBase
|
||||
{
|
||||
public ArgJunctionControl()
|
||||
{
|
||||
base.JunctionType = JunctionType.ArgData;
|
||||
Render();
|
||||
}
|
||||
|
||||
#region 控件属性,对应的参数
|
||||
public static readonly DependencyProperty ArgIndexProperty =
|
||||
DependencyProperty.Register("ArgIndex", typeof(int), typeof(ArgJunctionControl), new PropertyMetadata(default(int)));
|
||||
|
||||
/// <summary>
|
||||
/// 所在的节点
|
||||
/// </summary>
|
||||
public int ArgIndex
|
||||
{
|
||||
get { return (int)GetValue(ArgIndexProperty); }
|
||||
set { SetValue(ArgIndexProperty, value); }
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
if(double.IsNaN(base.Width))
|
||||
{
|
||||
base.Width = base._MyWidth;
|
||||
}
|
||||
if (double.IsNaN(base.Height))
|
||||
{
|
||||
base.Height = base._MyHeight;
|
||||
}
|
||||
|
||||
|
||||
var ellipse = new Ellipse
|
||||
{
|
||||
Width = base.Width,
|
||||
Height = base.Height,
|
||||
Fill = Brushes.Orange,
|
||||
ToolTip = "入参"
|
||||
};
|
||||
Content = ellipse;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
39
Workbench/Node/Junction/View/ExecuteJunctionControl.cs
Normal file
39
Workbench/Node/Junction/View/ExecuteJunctionControl.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Serein.Library;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
public class ExecuteJunctionControl : JunctionControlBase
|
||||
{
|
||||
//public override JunctionType JunctionType { get; } = JunctionType.Execute;
|
||||
public ExecuteJunctionControl()
|
||||
{
|
||||
base.JunctionType = JunctionType.Execute;
|
||||
Render();
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
if (double.IsNaN(base.Width))
|
||||
{
|
||||
base.Width = base._MyWidth;
|
||||
}
|
||||
if (double.IsNaN(base.Height))
|
||||
{
|
||||
base.Height = base._MyHeight;
|
||||
}
|
||||
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Width = base.Width,
|
||||
Height = base.Height,
|
||||
Fill = Brushes.Green,
|
||||
ToolTip = "方法执行"
|
||||
};
|
||||
Content = rect;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
Workbench/Node/Junction/View/NextStepJunctionControl.cs
Normal file
38
Workbench/Node/Junction/View/NextStepJunctionControl.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Serein.Library;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
public class NextStepJunctionControl : JunctionControlBase
|
||||
{
|
||||
//public override JunctionType JunctionType { get; } = JunctionType.NextStep;
|
||||
public NextStepJunctionControl()
|
||||
{
|
||||
base.JunctionType = JunctionType.NextStep;
|
||||
Render();
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
if (double.IsNaN(base.Width))
|
||||
{
|
||||
base.Width = base._MyWidth;
|
||||
}
|
||||
if (double.IsNaN(base.Height))
|
||||
{
|
||||
base.Height = base._MyHeight;
|
||||
}
|
||||
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Width = base.Width,
|
||||
Height = base.Height,
|
||||
Fill = Brushes.Blue,
|
||||
ToolTip = "下一个方法值"
|
||||
};
|
||||
Content = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Workbench/Node/Junction/View/ResultJunctionControl.cs
Normal file
39
Workbench/Node/Junction/View/ResultJunctionControl.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Serein.Library;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
public class ResultJunctionControl : JunctionControlBase
|
||||
{
|
||||
//public override JunctionType JunctionType { get; } = JunctionType.ReturnData;
|
||||
|
||||
public ResultJunctionControl()
|
||||
{
|
||||
base.JunctionType = JunctionType.ReturnData;
|
||||
Render();
|
||||
}
|
||||
|
||||
public override void Render()
|
||||
{
|
||||
if (double.IsNaN(base.Width))
|
||||
{
|
||||
base.Width = base._MyWidth;
|
||||
}
|
||||
if (double.IsNaN(base.Height))
|
||||
{
|
||||
base.Height = base._MyHeight;
|
||||
}
|
||||
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Width = base.Width,
|
||||
Height = base.Height,
|
||||
Fill = Brushes.Red,
|
||||
ToolTip = "返回值"
|
||||
};
|
||||
Content = rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
Workbench/Node/NodeControlBase.cs
Normal file
80
Workbench/Node/NodeControlBase.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Workbench.Node.ViewModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 节点控件基类(控件)
|
||||
/// </summary>
|
||||
public abstract class NodeControlBase : UserControl, IDynamicFlowNode
|
||||
{
|
||||
public NodeControlViewModelBase ViewModel { get; set; }
|
||||
|
||||
|
||||
protected NodeControlBase()
|
||||
{
|
||||
this.Background = Brushes.Transparent;
|
||||
}
|
||||
protected NodeControlBase(NodeControlViewModelBase viewModelBase)
|
||||
{
|
||||
ViewModel = viewModelBase;
|
||||
this.Background = Brushes.Transparent;
|
||||
this.DataContext = viewModelBase;
|
||||
SetBinding();
|
||||
}
|
||||
|
||||
|
||||
public void SetBinding()
|
||||
{
|
||||
// 绑定 Canvas.Left
|
||||
Binding leftBinding = new Binding("X")
|
||||
{
|
||||
Source = ViewModel.NodeModel.Position, // 如果 X 属性在当前 DataContext 中
|
||||
Mode = BindingMode.TwoWay
|
||||
};
|
||||
BindingOperations.SetBinding(this, Canvas.LeftProperty, leftBinding);
|
||||
|
||||
// 绑定 Canvas.Top
|
||||
Binding topBinding = new Binding("Y")
|
||||
{
|
||||
Source = ViewModel.NodeModel.Position, // 如果 Y 属性在当前 DataContext 中
|
||||
Mode = BindingMode.TwoWay
|
||||
};
|
||||
BindingOperations.SetBinding(this, Canvas.TopProperty, topBinding);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//public class FLowNodeObObservableCollection<T> : ObservableCollection<T>
|
||||
//{
|
||||
|
||||
// public void AddRange(IEnumerable<T> items)
|
||||
// {
|
||||
// foreach (var item in items)
|
||||
// {
|
||||
// this.Items.Add(item);
|
||||
// }
|
||||
// OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add));
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
320
Workbench/Node/View/ConnectionControl.cs
Normal file
320
Workbench/Node/View/ConnectionControl.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
using Serein.Library;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Workbench.Extension;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Color = System.Windows.Media.Color;
|
||||
using ColorConverter = System.Windows.Media.ColorConverter;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Serein.Workbench.Node.View
|
||||
{
|
||||
#region 连接点相关代码
|
||||
|
||||
|
||||
|
||||
public class ConnectionModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 起始节点
|
||||
/// </summary>
|
||||
public NodeModelBase StartNode { get; set; }
|
||||
/// <summary>
|
||||
/// 目标节点
|
||||
/// </summary>
|
||||
public NodeModelBase EndNode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 来源于起始节点的(控制点)类型
|
||||
/// </summary>
|
||||
public JunctionType JoinTypeOfStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接到目标节点的(控制点)类型
|
||||
/// </summary>
|
||||
public JunctionType JoinTypeOfEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接类型
|
||||
/// </summary>
|
||||
public ConnectionType Type { get; set; }
|
||||
}
|
||||
|
||||
|
||||
public interface IJunctionNode
|
||||
{
|
||||
string BoundNodeGuid { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接点
|
||||
/// </summary>
|
||||
public class JunctionNode : IJunctionNode
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接点类型
|
||||
/// </summary>
|
||||
public JunctionType JunctionType { get; }
|
||||
/// <summary>
|
||||
/// 对应的视图对象
|
||||
/// </summary>
|
||||
public NodeModelBase NodeModel { get; set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public string BoundNodeGuid { get => NodeModel.Guid; }
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 有1个Execute
|
||||
* 有1个NextStep
|
||||
* 有0~65535个入参 ushort
|
||||
* 有1个ReturnData(void方法返回null)
|
||||
*
|
||||
* Execute: // 执行这个方法
|
||||
* 只接受 NextStep 的连接
|
||||
* ArgData:
|
||||
* 互相之间不能连接,只能接受 Execute、ReturnData 的连接
|
||||
* Execute:表示从 Execute所在节点 获取数据
|
||||
* ReturnData: 表示从对应节点获取数据
|
||||
* ReturnData:
|
||||
* 只能发起主动连接,且只能连接到 ArgData
|
||||
* NextStep
|
||||
* 只能连接连接 Execute
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 连接控件,表示控件的连接关系
|
||||
/// </summary>
|
||||
public class ConnectionControl : Shape
|
||||
{
|
||||
private readonly IFlowEnvironment environment;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化连接控件
|
||||
/// </summary>
|
||||
/// <param name="Canvas"></param>
|
||||
/// <param name="Type"></param>
|
||||
public ConnectionControl(IFlowEnvironment environment,
|
||||
Canvas Canvas,
|
||||
ConnectionType Type,
|
||||
NodeControlBase Start,
|
||||
NodeControlBase End)
|
||||
{
|
||||
this.environment = environment;
|
||||
this.Canvas = Canvas;
|
||||
this.Type = Type;
|
||||
this.Start = Start;
|
||||
this.End = End;
|
||||
|
||||
InitElementPoint();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 所在的画布
|
||||
/// </summary>
|
||||
public Canvas Canvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 连接类型
|
||||
/// </summary>
|
||||
public ConnectionType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 起始控件
|
||||
/// </summary>
|
||||
public NodeControlBase Start { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束控件
|
||||
/// </summary>
|
||||
public NodeControlBase End { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 配置连接曲线的右键菜单
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
private void ConfigureLineContextMenu(ConnectionControl connection)
|
||||
{
|
||||
var contextMenu = new ContextMenu();
|
||||
contextMenu.Items.Add(MainWindow.CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
|
||||
connection.ContextMenu = contextMenu;
|
||||
connection.ContextMenu = contextMenu;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除该连线
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
private void DeleteConnection(ConnectionControl connection)
|
||||
{
|
||||
var connectionToRemove = connection;
|
||||
if (connectionToRemove is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 获取起始节点与终止节点,消除映射关系
|
||||
var fromNodeGuid = connectionToRemove.Start.ViewModel.NodeModel.Guid;
|
||||
var toNodeGuid = connectionToRemove.End.ViewModel.NodeModel.Guid;
|
||||
environment.RemoveConnectAsync(fromNodeGuid, toNodeGuid, connection.Type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除
|
||||
/// </summary>
|
||||
public void RemoveFromCanvas()
|
||||
{
|
||||
Canvas.Children.Remove(this); // 移除线
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新绘制
|
||||
/// </summary>
|
||||
public void AddOrRefreshLine()
|
||||
{
|
||||
this.InvalidateVisual();
|
||||
}
|
||||
|
||||
public void InitElementPoint()
|
||||
{
|
||||
leftCenterOfEndLocation = new Point(0, End.ActualHeight / 2); // 目标节点选择左侧边缘中心
|
||||
rightCenterOfStartLocation = new Point(Start.ActualWidth, Start.ActualHeight / 2); // 起始节点选择右侧边缘中心
|
||||
brush = GetLineColor(Type); // 线条颜色
|
||||
hitVisiblePen = new Pen(Brushes.Transparent, 1.0); // 初始化碰撞检测线
|
||||
hitVisiblePen.Freeze(); // Freeze以提高性能
|
||||
visualPen = new Pen(brush, 1.0); // 默认可视化Pen
|
||||
visualPen.Freeze(); // Freeze以提高性能
|
||||
ConfigureLineContextMenu(this); // 设置连接右键事件
|
||||
linkSize = 4; // 整线条粗细
|
||||
Canvas.Children.Add(this); // 添加线
|
||||
Grid.SetZIndex(this, -9999999); // 置底
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件重绘事件
|
||||
/// </summary>
|
||||
/// <param name="drawingContext"></param>
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
RefreshPoint(Canvas, this.Start, this.End); // 刷新坐标
|
||||
DrawBezierCurve(drawingContext, startPoint, endPoint, linkSize, brush); // 刷新线条显示位置
|
||||
}
|
||||
|
||||
private readonly StreamGeometry streamGeometry = new StreamGeometry();
|
||||
private Point rightCenterOfStartLocation; // 目标节点选择左侧边缘中心
|
||||
private Point leftCenterOfEndLocation; // 起始节点选择右侧边缘中心
|
||||
private Pen hitVisiblePen; // 初始化碰撞检测线
|
||||
private Pen visualPen; // 默认可视化Pen
|
||||
private Point startPoint; // 连接线的起始节点
|
||||
private Point endPoint; // 连接线的终点
|
||||
private Brush brush; // 线条颜色
|
||||
double linkSize; // 根据缩放比例调整线条粗细
|
||||
protected override Geometry DefiningGeometry => streamGeometry;
|
||||
|
||||
#region 工具方法
|
||||
|
||||
public void RefreshPoint(Canvas canvas, FrameworkElement startElement, FrameworkElement endElement)
|
||||
{
|
||||
endPoint = endElement.TranslatePoint(leftCenterOfEndLocation, canvas); // 计算终点位置
|
||||
startPoint = startElement.TranslatePoint(rightCenterOfStartLocation, canvas); // 获取起始节点的中心位置
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据连接类型指定颜色
|
||||
/// </summary>
|
||||
/// <param name="currentConnectionType"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
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(),
|
||||
};
|
||||
}
|
||||
private Point c0, c1; // 用于计算贝塞尔曲线控制点逻辑
|
||||
private Vector axis = new Vector(1, 0);
|
||||
private Vector startToEnd;
|
||||
private void DrawBezierCurve(DrawingContext drawingContext,
|
||||
Point start,
|
||||
Point end,
|
||||
double linkSize,
|
||||
Brush brush,
|
||||
bool isHitTestVisible = false,
|
||||
double strokeThickness = 1.0,
|
||||
bool isMouseOver = false,
|
||||
double dashOffset = 0.0)
|
||||
{
|
||||
// 控制点的计算逻辑
|
||||
double power = 8 * 8; // 控制贝塞尔曲线的“拉伸”强度
|
||||
|
||||
// 计算轴向向量与起点到终点的向量
|
||||
//var axis = new Vector(1, 0);
|
||||
startToEnd = (end.ToVector() - start.ToVector()).NormalizeTo();
|
||||
|
||||
// 计算拉伸程度k,拉伸与水平夹角正相关
|
||||
var k = 1 - Math.Pow(Math.Max(0, axis.DotProduct(startToEnd)), 10.0);
|
||||
|
||||
// 如果起点x大于终点x,增加额外的偏移量,避免重叠
|
||||
var bias = start.X > end.X ? Math.Abs(start.X - end.X) * 0.25 : 0;
|
||||
|
||||
// 控制点的实际计算
|
||||
c0 = new Point(+(power + bias) * k + start.X, start.Y);
|
||||
c1 = new Point(-(power + bias) * k + end.X, end.Y);
|
||||
|
||||
// 准备StreamGeometry以用于绘制曲线
|
||||
streamGeometry.Clear();
|
||||
using (var context = streamGeometry.Open())
|
||||
{
|
||||
context.BeginFigure(start, true, false); // 曲线起点
|
||||
context.BezierTo(c0, c1, end, true, false); // 画贝塞尔曲线
|
||||
}
|
||||
|
||||
drawingContext.DrawGeometry(null, visualPen, streamGeometry);
|
||||
|
||||
// 绘制碰撞检测线
|
||||
//if (true)
|
||||
//{
|
||||
// //hitVisiblePen = new Pen(Brushes.Transparent, linkSize + strokeThickness);
|
||||
// //hitVisiblePen.Freeze();
|
||||
// drawingContext.DrawGeometry(null, hitVisiblePen, streamGeometry);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
|
||||
|
||||
//}
|
||||
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user