优化了连接的线,改为贝塞尔曲线(带箭头)

This commit is contained in:
fengjiayi
2024-08-07 21:17:19 +08:00
parent 2ca5b3120b
commit 81206ffbd5
11 changed files with 399 additions and 255 deletions

View File

@@ -151,13 +151,13 @@ namespace Serein.WorkBench
Shutdown(); // 关闭应用程序
}
}
//else if (1 == 1)
//{
// string filePath = @"F:\临时\project\wat project.dnf";
// string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
// FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
// App.FileDataPath = System.IO.Path.GetDirectoryName(filePath);
//}
//else if (1 == 1)
//{
// string filePath = @"F:\临时\project\wat project.dnf";
// string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容
// FData = JsonConvert.DeserializeObject<SereinOutputFileData>(content);
// App.FileDataPath = System.IO.Path.GetDirectoryName(filePath);
//}
}
}

View File

@@ -82,7 +82,6 @@
Background="#F2EEE8"
Width="1000" Height="1000"
AllowDrop="True"
MouseLeftButtonDown="FlowChartCanvas_MouseLeftButtonDown"
Drop="FlowChartCanvas_Drop"
DragOver="FlowChartCanvas_DragOver"/>
</ScrollViewer>

View File

@@ -8,15 +8,20 @@ using Serein.WorkBench.Node.View;
using Serein.WorkBench.Themes;
using Serein.WorkBench.tool;
using System.Collections.Concurrent;
using System.Configuration;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.IO;
using System.Reflection;
using System.Threading.Tasks.Dataflow;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using static Serein.WorkBench.MainWindow;
namespace Serein.WorkBench
{
@@ -32,79 +37,79 @@ namespace Serein.WorkBench
/// <summary>
/// 表示两个节点之间的连接关系UI层面
/// </summary>
public class Connection
{
public required NodeControlBase Start { get; set; } // 起始TextBlock
public required NodeControlBase End { get; set; } // 结束TextBlock
public required Line Line { get; set; } // 连接的线
public ConnectionType Type { get; set; } // 连接的线是否为真分支或者假分支
//public class Connection
//{
// public required NodeControlBase Start { get; set; } // 起始TextBlock
// public required NodeControlBase End { get; set; } // 结束TextBlock
// public required Line Line { get; set; } // 连接的线
// public ConnectionType Type { get; set; } // 连接的线是否为真分支或者假分支
private Storyboard? _animationStoryboard; // 动画Storyboard
// private Storyboard? _animationStoryboard; // 动画Storyboard
/// <summary>
/// 从Canvas中移除连接线
/// </summary>
/// <param name="canvas"></param>
public void RemoveFromCanvas(Canvas canvas)
{
canvas.Children.Remove(Line); // 移除线
_animationStoryboard?.Stop(); // 停止动画
}
// /// <summary>
// /// 从Canvas中移除连接线
// /// </summary>
// /// <param name="canvas"></param>
// public void RemoveFromCanvas(Canvas canvas)
// {
// canvas.Children.Remove(Line); // 移除线
// _animationStoryboard?.Stop(); // 停止动画
// }
/// <summary>
/// 开始动画
/// </summary>
public void StartAnimation()
{
// 停止现有的动画
_animationStoryboard?.Stop();
// /// <summary>
// /// 开始动画
// /// </summary>
// public void StartAnimation()
// {
// // 停止现有的动画
// _animationStoryboard?.Stop();
// 计算线条的长度
double length = Math.Sqrt(Math.Pow(Line.X2 - Line.X1, 4) + Math.Pow(Line.Y2 - Line.Y1, 4));
double dashLength = length / 200;
// // 计算线条的长度
// double length = Math.Sqrt(Math.Pow(Line.X2 - Line.X1, 4) + Math.Pow(Line.Y2 - Line.Y1, 4));
// double dashLength = length / 200;
// 创建新的 DoubleAnimation 反转方向
var animation = new DoubleAnimation
{
From = dashLength,
To = 0,
Duration = TimeSpan.FromSeconds(0.5),
RepeatBehavior = RepeatBehavior.Forever
};
// // 创建新的 DoubleAnimation 反转方向
// var animation = new DoubleAnimation
// {
// From = dashLength,
// To = 0,
// Duration = TimeSpan.FromSeconds(0.5),
// RepeatBehavior = RepeatBehavior.Forever
// };
// 设置线条的样式
Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
: Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
: Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
: new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4"));
Line.StrokeDashArray = [dashLength, dashLength];
// // 设置线条的样式
// Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
// : Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
// : Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
// : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4"));
// Line.StrokeDashArray = [dashLength, dashLength];
// 创建新的 Storyboard
_animationStoryboard = new Storyboard();
_animationStoryboard.Children.Add(animation);
Storyboard.SetTarget(animation, Line);
Storyboard.SetTargetProperty(animation, new PropertyPath(Line.StrokeDashOffsetProperty));
// // 创建新的 Storyboard
// _animationStoryboard = new Storyboard();
// _animationStoryboard.Children.Add(animation);
// Storyboard.SetTarget(animation, Line);
// Storyboard.SetTargetProperty(animation, new PropertyPath(Line.StrokeDashOffsetProperty));
// 开始动画
_animationStoryboard.Begin();
}
// // 开始动画
// _animationStoryboard.Begin();
// }
/// <summary>
/// 停止动画
/// </summary>
public void StopAnimation()
{
if (_animationStoryboard != null)
{
_animationStoryboard.Stop();
Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
: Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
: Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
: new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4"));
Line.StrokeDashArray = null;
}
}
}
// /// <summary>
// /// 停止动画
// /// </summary>
// public void StopAnimation()
// {
// if (_animationStoryboard != null)
// {
// _animationStoryboard.Stop();
// Line.Stroke = Type == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
// : Type == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
// : Type == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
// : new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4"));
// Line.StrokeDashArray = null;
// }
// }
//}
/// <summary>
@@ -190,7 +195,7 @@ namespace Serein.WorkBench
/// <summary>
/// 标记是否正在进行连接操作
/// </summary>
private bool IsConnecting;
private bool IsConnecting { get; set; }
/// <summary>
/// 标记是否正在拖动控件
/// </summary>
@@ -374,25 +379,6 @@ namespace Serein.WorkBench
{
if (fromNode != null && toNode != null && fromNode != toNode)
{
var line = new Line
{
IsHitTestVisible = true,
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")),
StrokeThickness = 2,
X1 = Canvas.GetLeft(fromNode) + fromNode.ActualWidth / 2,
Y1 = Canvas.GetTop(fromNode) + fromNode.ActualHeight / 2,
X2 = Canvas.GetLeft(toNode) + toNode.ActualWidth / 2,
Y2 = Canvas.GetTop(toNode) + toNode.ActualHeight / 2
};
ConfigureLineContextMenu(line);
FlowChartCanvas.Children.Add(line);
if (connectionType == ConnectionType.IsSucceed)
{
fromNode.Node.SucceedBranch.Add(toNode.Node);
@@ -409,11 +395,12 @@ namespace Serein.WorkBench
{
fromNode.Node.UpstreamBranch.Add(toNode.Node);
}
var connection = new Connection { Start = fromNode, End = toNode, Type = connectionType };
toNode.Node.PreviousNodes.Add(fromNode.Node);
connections.Add(new Connection { Start = fromNode, End = toNode, Line = line, Type = connectionType });
DraggableControl.CreateLinx(FlowChartCanvas, connection);
ConfigureLineContextMenu(connection);
connections.Add(connection);
}
EndConnection();
}
@@ -599,15 +586,16 @@ namespace Serein.WorkBench
}
/// <summary>
/// 配置节点/区域连接的线
/// 配置节点/区域连接的右键菜单
/// </summary>
/// <param name="line"></param>
private void ConfigureLineContextMenu(Line line)
private void ConfigureLineContextMenu(Connection connection)
{
var contextMenu = new ContextMenu();
contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(line)));
contextMenu.Items.Add(CreateMenuItem("连线动画", (s, e) => AnimateConnection(line)));
line.ContextMenu = contextMenu;
contextMenu.Items.Add(CreateMenuItem("删除连线", (s, e) => DeleteConnection(connection)));
connection.ArrowPath.ContextMenu = contextMenu;
connection.BezierPath.ContextMenu = contextMenu;
}
/// <summary>
@@ -619,7 +607,6 @@ namespace Serein.WorkBench
nodeControl.MouseLeftButtonDown += Block_MouseLeftButtonDown;
nodeControl.MouseMove += Block_MouseMove;
nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp;
nodeControl.MouseLeftButtonUp += Block_MouseLeftButtonUp_Animation;
}
#endregion
@@ -1125,11 +1112,11 @@ namespace Serein.WorkBench
/// <param name="e"></param>
private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 关闭所有连线的动画效果
foreach (var connection in connections)
{
connection.StopAnimation();
}
//// 关闭所有连线的动画效果
//foreach (var connection in connections)
//{
// connection.StopAnimation();
//}
}
/// <summary>
@@ -1152,26 +1139,6 @@ namespace Serein.WorkBench
e.Handled = true;
}
/// <summary>
/// 处理控件的鼠标左键松开事件,启动或停止连接的动画效果
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Block_MouseLeftButtonUp_Animation(object sender, MouseButtonEventArgs e)
{
if (sender is UserControl clickedBlock)
{
foreach (var connection in connections)
{
if (connection.Start == clickedBlock)
{
connection.StartAnimation(); // 开启动画
}
}
}
}
/// <summary>
/// 控件的鼠标左键按下事件,启动拖动操作。
/// </summary>
@@ -1279,9 +1246,8 @@ namespace Serein.WorkBench
return;
}*/
currentConnectionType = connectionType;
IsConnecting = true;
currentConnectionType = connectionType;
startConnectBlock = startBlock;
// 确保起点和终点位置的正确顺序
@@ -1335,7 +1301,6 @@ namespace Serein.WorkBench
}
else if (IsConnecting)
{
bool isRegion = false;
NodeControlBase? targetBlock;
@@ -1377,33 +1342,8 @@ namespace Serein.WorkBench
if (startConnectBlock != null && targetBlock != null && startConnectBlock != targetBlock)
{
// 创建连接线
Line line = new()
{
IsHitTestVisible = true,
Stroke = currentConnectionType == ConnectionType.IsSucceed ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#04FC10"))
: currentConnectionType == ConnectionType.IsFail ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#F18905"))
: currentConnectionType == ConnectionType.IsError ? new SolidColorBrush((Color)ColorConverter.ConvertFromString("#AB616B"))
: new SolidColorBrush((Color)ColorConverter.ConvertFromString("#4A82E4")),
StrokeThickness = 2,
X1 = Canvas.GetLeft(startConnectBlock) + startConnectBlock.ActualWidth / 2,
Y1 = Canvas.GetTop(startConnectBlock) + startConnectBlock.ActualHeight / 2,
X2 = Canvas.GetLeft(targetBlock) + targetBlock.ActualWidth / 2,
Y2 = Canvas.GetTop(targetBlock) + targetBlock.ActualHeight / 2
};
// 添加右键菜单删除连线
ContextMenu lineContextMenu = new();
MenuItem deleteLineMenuItem = new() { Header = "删除连线" };
deleteLineMenuItem.Click += (s, args) => DeleteConnection(line);
MenuItem animateLineMenuItem = new() { Header = "连线动画" };
animateLineMenuItem.Click += (s, args) => AnimateConnection(line);
lineContextMenu.Items.Add(deleteLineMenuItem);
lineContextMenu.Items.Add(animateLineMenuItem);
line.ContextMenu = lineContextMenu;
FlowChartCanvas.Children.Add(line);
var connection = new Connection { Start = startConnectBlock, End = targetBlock, Type = currentConnectionType };
if (currentConnectionType == ConnectionType.IsSucceed)
{
startConnectBlock.Node.SucceedBranch.Add(targetBlock.Node);
@@ -1421,10 +1361,12 @@ namespace Serein.WorkBench
startConnectBlock.Node.UpstreamBranch.Add(targetBlock.Node);
}
targetBlock.Node.PreviousNodes.Add(startConnectBlock.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯)
// 保存连接关系
connections.Add(new Connection { Start = startConnectBlock, End = targetBlock, Line = line, Type = currentConnectionType });
DraggableControl.CreateLinx(FlowChartCanvas, connection);
ConfigureLineContextMenu(connection);
targetBlock.Node.PreviousNodes.Add(startConnectBlock.Node); // 将当前发起连接的节点,添加到被连接的节点的上一节点队列。(用于回溯)
connections.Add(connection);
}
EndConnection();
}
@@ -1470,10 +1412,7 @@ namespace Serein.WorkBench
{
if (connection.Start == block || connection.End == block)
{
connection.Line.X1 = Canvas.GetLeft(connection.Start) + connection.Start.ActualWidth / 2;
connection.Line.Y1 = Canvas.GetTop(connection.Start) + connection.Start.ActualHeight / 2;
connection.Line.X2 = Canvas.GetLeft(connection.End) + connection.End.ActualWidth / 2;
connection.Line.Y2 = Canvas.GetTop(connection.End) + connection.End.ActualHeight / 2;
BezierLineDrawer.UpdateBezierLine(FlowChartCanvas, connection.Start, connection.End, connection.BezierPath, connection.ArrowPath);
}
}
}
@@ -1520,57 +1459,11 @@ namespace Serein.WorkBench
|| c.End.Node.Guid.Equals(nodeControl.Node.Guid)).ToList();
Remove(RemoveEonnections, nodeControl.Node);
// 获取起点是该控件的所有连线(获取该节点的子节点)
//var IsStartConnections = connections.Where(c => c.Start == nodeControl).ToList();
//// 获取终点是该控件的所有连线(获取该节点的父节点)
//var EndConnectionsToRemove = connections.Where(c => c.End == nodeControl).ToList();
// 删除控件
FlowChartCanvas.Children.Remove(nodeControl);
nodeControls.Remove(nodeControl);
/*foreach (var connection in StartConnectionsToRemove)
{
var StartNode = node.Node;
var EndNode = connection.End.Node;
if (connection.TorF)
{
StartNode.TrueBranchNextNodes.Remove(EndNode);
}
else
{
StartNode.FalseBranchNextNodes.Remove(EndNode);
}
EndNode.PreviousNodes.Remove(StartNode);
connection.RemoveFromCanvas(FlowChartCanvas);
connections.Remove(connection);
if(node is FlipflopNodeControl flipflopNodeControl && flipflopNodeControl.Node is SingleFlipflopNode singleFlipflop)
{
flipflopNodes.Remove(singleFlipflop);
}
}*/
/*foreach (var connection in EndConnectionsToRemove)
{
var StartNode = connection.Start.Node;
var EndNode = node.Node;
if (connection.TorF)
{
StartNode.TrueBranchNextNodes.Remove(EndNode);
}
else
{
StartNode.FalseBranchNextNodes.Remove(EndNode);
}
EndNode.PreviousNodes.Remove(StartNode);
connection.RemoveFromCanvas(FlowChartCanvas);
connections.Remove(connection);
}*/
}
/// <summary>
/// 移除控件连接关系
@@ -1652,15 +1545,6 @@ namespace Serein.WorkBench
}
}
/// <summary>
/// 开启以该控件为起点的动画效果
/// </summary>
/// <param name="line"></param>
private void AnimateConnection(Line line)
{
var connectionToAnimate = connections.FirstOrDefault(c => c.Line == line);
connectionToAnimate?.StartAnimation();
}
/// <summary>
/// 设为起点
/// </summary>
@@ -1681,19 +1565,6 @@ namespace Serein.WorkBench
nodeControl.BorderThickness = new Thickness(2);
flowStartBlock = nodeControl;
/* foreach (var node in _nodeControls)
{
if(node == setNode)
{
}
else
{
node.Node.IsStart = false;
}
}*/
}
/// <summary>
/// 树形结构展开类型的成员
@@ -1720,9 +1591,9 @@ namespace Serein.WorkBench
/// 删除该连线
/// </summary>
/// <param name="line"></param>
private void DeleteConnection(Line line)
private void DeleteConnection(Connection connection)
{
var connectionToRemove = connections.FirstOrDefault(c => c.Line == line);
var connectionToRemove = connection;
if (connectionToRemove == null)
{
return;
@@ -1744,7 +1615,7 @@ namespace Serein.WorkBench
StartNode.ErrorBranch.Remove(EndNode);
}
EndNode.PreviousNodes.Remove(StartNode);
@@ -2126,7 +1997,292 @@ namespace Serein.WorkBench
return Uri.UnescapeDataString(relativeUri.ToString().Replace('/', System.IO.Path.DirectorySeparatorChar));
}
#region UI层面上显示为 线
public static class DraggableControl
{
public static Connection CreateLinx(Canvas canvas, Connection connection)
{
UpdateBezierLine(canvas, connection);
//MakeDraggable(canvas, connection, connection.Start);
//MakeDraggable(canvas, connection, connection.End);
if (connection.BezierPath == null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection.ArrowPath == null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(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 UpdateBezierLine(Canvas canvas, Connection connection)
{
if (isUpdating)
return;
isUpdating = true;
canvas.Dispatcher.InvokeAsync(() =>
{
if (connection != null && connection.BezierPath == null)
{
connection.BezierPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), StrokeThickness = 1 };
//Canvas.SetZIndex(connection.BezierPath, -1);
canvas.Children.Add(connection.BezierPath);
}
if (connection != null && connection.ArrowPath == null)
{
connection.ArrowPath = new System.Windows.Shapes.Path { Stroke = BezierLineDrawer.GetStroke(connection.Type), Fill = BezierLineDrawer.GetStroke(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 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; } // 结束
private Storyboard? _animationStoryboard; // 动画Storyboard
public void RemoveFromCanvas(Canvas canvas)
{
canvas.Children.Remove(BezierPath); // 移除线
canvas.Children.Remove(ArrowPath); // 移除线
_animationStoryboard?.Stop(); // 停止动画
}
}
public 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);
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,
};
}
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 = 10.0; // 末端点
// 计算贝塞尔曲线在 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 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 + 6;
double halfHeight = element.ActualHeight / 2 + 6;
double margin = 0;
if (localhost == Localhost.Left)
{
centerPoint.X -= halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Right)
{
centerPoint.X += halfWidth;
centerPoint.Y -= direction.Y / Math.Abs(direction.X) * halfHeight - margin;
}
else if (localhost == Localhost.Top)
{
centerPoint.Y -= halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
else if (localhost == Localhost.Bottom)
{
centerPoint.Y += halfHeight;
centerPoint.X -= direction.X / Math.Abs(direction.Y) * halfWidth - margin;
}
return centerPoint;
}
public static SolidColorBrush GetStroke(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
}
}

View File

@@ -6,8 +6,6 @@
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<Grid>
<Grid.ToolTip>

View File

@@ -1,5 +1,7 @@
using Serein.NodeFlow.Model;
using Serein.WorkBench.Node.ViewModel;
using System.Runtime.CompilerServices;
using System.Windows.Controls;
namespace Serein.WorkBench.Node.View
{
@@ -15,6 +17,7 @@ namespace Serein.WorkBench.Node.View
actionNodeControlViewModel = new ActionNodeControlViewModel(node);
DataContext = actionNodeControlViewModel;
InitializeComponent();
}

View File

@@ -3,9 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
xmlns:local="clr-namespace:Serein.WorkBench.Node.View">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Padding="10">
<StackPanel>

View File

@@ -5,9 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
xmlns:themes="clr-namespace:Serein.WorkBench.Themes">
<!--d:DataContext="{d:DesignInstance Type=vm:ConditionNodeControlViewModel}"-->
<UserControl.Resources>

View File

@@ -3,9 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
xmlns:local="clr-namespace:Serein.WorkBench.Node.View">
<Grid>
<Border BorderBrush="Black" BorderThickness="1" Padding="10">

View File

@@ -4,8 +4,6 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
>
<DockPanel>
<StackPanel DockPanel.Dock="Top" >

View File

@@ -3,9 +3,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
xmlns:local="clr-namespace:Serein.WorkBench.Node.View">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>

View File

@@ -5,9 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Serein.WorkBench.Node.View"
xmlns:vm="clr-namespace:Serein.WorkBench.Node.ViewModel"
xmlns:themes="clr-namespace:Serein.WorkBench.Themes"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="1400">
xmlns:themes="clr-namespace:Serein.WorkBench.Themes">
<UserControl.Resources>
<vm:TypeToStringConverter x:Key="TypeToStringConverter"/>