From 81206ffbd5365255f8662ae5c3871ecba6e7ee2e Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Wed, 7 Aug 2024 21:17:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=9A=84=E7=BA=BF=EF=BC=8C=E6=94=B9=E4=B8=BA=E8=B4=9D=E5=A1=9E?= =?UTF-8?q?=E5=B0=94=E6=9B=B2=E7=BA=BF=EF=BC=88=E5=B8=A6=E7=AE=AD=E5=A4=B4?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WorkBench/App.xaml.cs | 14 +- WorkBench/MainWindow.xaml | 1 - WorkBench/MainWindow.xaml.cs | 612 +++++++++++------- WorkBench/Node/View/ActionNodeControl.xaml | 2 - WorkBench/Node/View/ActionNodeControl.xaml.cs | 3 + WorkBench/Node/View/ActionRegionControl.xaml | 4 +- WorkBench/Node/View/ConditionNodeControl.xaml | 4 +- .../Node/View/ConditionRegionControl.xaml | 4 +- WorkBench/Node/View/DllControlControl.xaml | 2 - WorkBench/Node/View/ExpOpNodeControl.xaml | 4 +- WorkBench/Node/View/FlipflopNodeControl.xaml | 4 +- 11 files changed, 399 insertions(+), 255 deletions(-) diff --git a/WorkBench/App.xaml.cs b/WorkBench/App.xaml.cs index 142fa5b..3a9107d 100644 --- a/WorkBench/App.xaml.cs +++ b/WorkBench/App.xaml.cs @@ -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(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(content); + // App.FileDataPath = System.IO.Path.GetDirectoryName(filePath); + //} } } diff --git a/WorkBench/MainWindow.xaml b/WorkBench/MainWindow.xaml index 888d1d1..4257bd4 100644 --- a/WorkBench/MainWindow.xaml +++ b/WorkBench/MainWindow.xaml @@ -82,7 +82,6 @@ Background="#F2EEE8" Width="1000" Height="1000" AllowDrop="True" - MouseLeftButtonDown="FlowChartCanvas_MouseLeftButtonDown" Drop="FlowChartCanvas_Drop" DragOver="FlowChartCanvas_DragOver"/> diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index c8e323b..d828b09 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -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 /// /// 表示两个节点之间的连接关系(UI层面) /// - 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 - /// - /// 从Canvas中移除连接线 - /// - /// - public void RemoveFromCanvas(Canvas canvas) - { - canvas.Children.Remove(Line); // 移除线 - _animationStoryboard?.Stop(); // 停止动画 - } + // /// + // /// 从Canvas中移除连接线 + // /// + // /// + // public void RemoveFromCanvas(Canvas canvas) + // { + // canvas.Children.Remove(Line); // 移除线 + // _animationStoryboard?.Stop(); // 停止动画 + // } - /// - /// 开始动画 - /// - public void StartAnimation() - { - // 停止现有的动画 - _animationStoryboard?.Stop(); + // /// + // /// 开始动画 + // /// + // 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(); + // } - /// - /// 停止动画 - /// - 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; - } - } - } + // /// + // /// 停止动画 + // /// + // 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; + // } + // } + //} /// @@ -190,7 +195,7 @@ namespace Serein.WorkBench /// /// 标记是否正在进行连接操作 /// - private bool IsConnecting; + private bool IsConnecting { get; set; } /// /// 标记是否正在拖动控件 /// @@ -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 } /// - /// 配置节点/区域连接的线 + /// 配置节点/区域连接的右键菜单 /// /// - 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; + } /// @@ -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 /// private void FlowChartCanvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - // 关闭所有连线的动画效果 - foreach (var connection in connections) - { - connection.StopAnimation(); - } + //// 关闭所有连线的动画效果 + //foreach (var connection in connections) + //{ + // connection.StopAnimation(); + //} } /// @@ -1152,26 +1139,6 @@ namespace Serein.WorkBench e.Handled = true; } - - /// - /// 处理控件的鼠标左键松开事件,启动或停止连接的动画效果 - /// - /// - /// - 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(); // 开启动画 - } - } - } - } - /// /// 控件的鼠标左键按下事件,启动拖动操作。 /// @@ -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); - }*/ + } /// /// 移除控件连接关系 @@ -1652,15 +1545,6 @@ namespace Serein.WorkBench } } - /// - /// 开启以该控件为起点的动画效果 - /// - /// - private void AnimateConnection(Line line) - { - var connectionToAnimate = connections.FirstOrDefault(c => c.Line == line); - connectionToAnimate?.StartAnimation(); - } /// /// 设为起点 /// @@ -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; - - } - - }*/ } /// /// 树形结构展开类型的成员 @@ -1720,9 +1591,9 @@ namespace Serein.WorkBench /// 删除该连线 /// /// - 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 } } \ No newline at end of file diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index f0db5e5..4cc9b6f 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -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" > diff --git a/WorkBench/Node/View/ActionNodeControl.xaml.cs b/WorkBench/Node/View/ActionNodeControl.xaml.cs index 62c7b1b..419a059 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml.cs +++ b/WorkBench/Node/View/ActionNodeControl.xaml.cs @@ -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(); + } diff --git a/WorkBench/Node/View/ActionRegionControl.xaml b/WorkBench/Node/View/ActionRegionControl.xaml index 2c9a87e..a9d8636 100644 --- a/WorkBench/Node/View/ActionRegionControl.xaml +++ b/WorkBench/Node/View/ActionRegionControl.xaml @@ -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"> diff --git a/WorkBench/Node/View/ConditionNodeControl.xaml b/WorkBench/Node/View/ConditionNodeControl.xaml index 7fe6c7b..4b737ab 100644 --- a/WorkBench/Node/View/ConditionNodeControl.xaml +++ b/WorkBench/Node/View/ConditionNodeControl.xaml @@ -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"> diff --git a/WorkBench/Node/View/ConditionRegionControl.xaml b/WorkBench/Node/View/ConditionRegionControl.xaml index ee79e4a..6b62ae2 100644 --- a/WorkBench/Node/View/ConditionRegionControl.xaml +++ b/WorkBench/Node/View/ConditionRegionControl.xaml @@ -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"> diff --git a/WorkBench/Node/View/DllControlControl.xaml b/WorkBench/Node/View/DllControlControl.xaml index 253c855..2d311c5 100644 --- a/WorkBench/Node/View/DllControlControl.xaml +++ b/WorkBench/Node/View/DllControlControl.xaml @@ -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" > diff --git a/WorkBench/Node/View/ExpOpNodeControl.xaml b/WorkBench/Node/View/ExpOpNodeControl.xaml index f58996d..f84b1e8 100644 --- a/WorkBench/Node/View/ExpOpNodeControl.xaml +++ b/WorkBench/Node/View/ExpOpNodeControl.xaml @@ -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"> diff --git a/WorkBench/Node/View/FlipflopNodeControl.xaml b/WorkBench/Node/View/FlipflopNodeControl.xaml index c5dc0bc..39ecf29 100644 --- a/WorkBench/Node/View/FlipflopNodeControl.xaml +++ b/WorkBench/Node/View/FlipflopNodeControl.xaml @@ -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">