效果(代码在最下面)
只需要如下引用即可
BsControl.Draw(canvas, startControl, endControl);
补充说明:
canvas:控件所在的画布
startControl:贝塞尔曲线起点
endControl:贝塞尔曲线终点
PS:不懂的可以来B站问我。https://space.bilibili.com/33526379/dynamichttps://space.bilibili.com/33526379/dynamic
另外,欢迎支持我的个人开源项目:GitHub - fhhyyp/serein-flow: 一款基于WPF(Dotnet 8)的流程可视化编辑器(需二次开发) ----- a process visual editor based on WPF (Dotnet 8) (secondary development required)一款基于WPF(Dotnet 8)的流程可视化编辑器(需二次开发) ----- a process visual editor based on WPF (Dotnet 8) (secondary development required) - fhhyyp/serein-flowhttps://github.com/fhhyyp/serein-flow
以下是绘制贝塞尔曲线的代码:
#region 创建两个控件之间的连接关系,在UI层面上显示为 带箭头指向的贝塞尔曲线
public static class BsControl
{
public static Connection Draw(Canvas canvas, FrameworkElement sele, FrameworkElement eele)
{
Connection cc = new()
{
Start = sele,
End = eele,
};
UpBsPath(canvas, cc);
SetEvent(canvas, cc, cc.Start);
SetEvent(canvas, cc, cc.End);
cc.IsSet = true;
return cc;
}
private static Point clickEvent; // 当前点击事件
private static bool isD = false; // 是否正在移动控件
private static bool isUp = false; // 是否正在更新线条显示
private static void SetEvent(Canvas canvas, Connection cc, UIElement uiele)
{
if (cc.IsSet) return;
uiele.MouseLeftButtonDown += (sender, e) => { isD = true; clickEvent = e.GetPosition(uiele); uiele.CaptureMouse(); };
uiele.MouseMove += (sender, e) =>
{
if (isD && VisualTreeHelper.GetParent(uiele) is Canvas canvas)
{
var cp = e.GetPosition(canvas);
var nl = cp.X - clickEvent.X;
var nt = cp.Y - clickEvent.Y;
Canvas.SetLeft(uiele, nl); Canvas.SetTop(uiele, nt); UpBsPath(canvas, cc);
}
};
uiele.MouseLeftButtonUp += (sender, e) => { isD = false; uiele.ReleaseMouseCapture(); };
}
// 拖动时重新绘制
public static void UpBsPath(Canvas canvas, Connection cc)
{
if (isUp) return;
isUp = true;
canvas.Dispatcher.InvokeAsync(() =>
{
if (cc != null && cc.BsPath == null)
{
cc.BsPath = new Path { Stroke = Brushes.Teal, StrokeThickness = 1 };
Canvas.SetZIndex(cc.BsPath, -1);
canvas.Children.Add(cc.BsPath);
}
if (cc != null && cc.ApPath == null)
{
cc.ApPath = new Path { Stroke = Brushes.Teal, Fill = Brushes.Teal, StrokeThickness = 1 };
Canvas.SetZIndex(cc.ApPath, -1);
canvas.Children.Add(cc.ApPath);
}
BezierLineDrawer.UpLine(canvas, cc.Start, cc.End, cc.BsPath, cc.ApPath);
isUp = false;
});
}
}
public class Connection
{
public bool IsSet { get; set; }
public Path BsPath { get; set; }// 贝塞尔曲线路径
public Path ApPath { get; set; } // 箭头路径
public required FrameworkElement Start { get; set; } // 起始
public required FrameworkElement End { get; set; } // 结束
public void Remove(Canvas canvas)
{
canvas.Children.Remove(BsPath); // 移除线
canvas.Children.Remove(ApPath); // 移除箭头
}
}
public class BezierLineDrawer
{
// 绘制曲线
public static void UpLine(Canvas canvas, FrameworkElement sele, FrameworkElement eele, Path bp, Path aptah)
{
Point sp = sele.TranslatePoint(new Point(sele.ActualWidth / 2, sele.ActualHeight / 2), canvas);
Point ep = PointPosition(eele, canvas, sp, out bool isSpin);
PathFigure pf = new PathFigure { StartPoint = sp };
BezierSegment bs;
bs = new BezierSegment
{
Point1 = isSpin ? new Point((sp.X + ep.X) / 2, sp.Y) : new Point(sp.X, (sp.Y + ep.Y) / 2),
Point2 = isSpin ? new Point((sp.X + ep.X) / 2, ep.Y) : new Point(ep.X, (sp.Y + ep.Y) / 2),
Point3 = ep,
};
pf.Segments.Add(bs);
PathGeometry pg = new PathGeometry();
pg.Figures.Add(pf);
bp.Data = pg;
Point asp = Diff(sp, bs.Point3, bs.Point2, ep);
UpAp(ep, asp, aptah);
}
// 计算曲线在 t 处的一阶导数,返回切线向量
private static Point Diff(Point sp, Point cp1, Point cp2, Point ep)
{
double t = 10.0;
double dx = 3 * Math.Pow(1 - t, 2) * (cp1.X - sp.X) +
6 * (1 - t) * t * (cp2.X - cp1.X) +
3 * Math.Pow(t, 2) * (ep.X - cp2.X);
double dy = 3 * Math.Pow(1 - t, 2) * (cp1.Y - sp.Y) +
6 * (1 - t) * t * (cp2.Y - cp1.Y) +
3 * Math.Pow(t, 2) * (ep.Y - cp2.Y);
return new Point(dx, dy);
}
// 绘制箭头
private static void UpAp(Point ep, Point cp,Path aptah)
{
double alen = 10;
double awit = 5;
Vector d = ep - cp;
d.Normalize();
Point ap1 = ep + d * alen + new Vector(-d.Y, d.X) * awit;
Point ap2 = ep + d * alen + new Vector(d.Y, -d.X) * awit;
PathFigure af = new() { StartPoint = ep };
af.Segments.Add(new LineSegment(ap1, true));
af.Segments.Add(new LineSegment(ap2, true));
af.Segments.Add(new LineSegment(ep, true));
PathGeometry ag = new PathGeometry();
ag.Figures.Add(af);
aptah.Data = ag;
}
// 计算终点落点位置
private static Point PointPosition(FrameworkElement e, Canvas canvas, Point sp, out bool isSpin)
{
Point cp = e.TranslatePoint(new Point(e.ActualWidth / 2, e.ActualHeight / 2), canvas);
Vector d = cp - sp;
d.Normalize();
var x = cp.X - sp.X;
var y = sp.Y - cp.Y;
isSpin = Math.Abs(x) > Math.Abs(y);
cp.X -= (cp.X - sp.X < sp.Y - cp.Y, isSpin) switch
{
(false, true) => e.ActualWidth / 2,
(true, true) => e.ActualWidth / - 2,
(false, false) => d.X / Math.Abs(d.Y) * e.ActualWidth / 2,
(true, false) => d.X / Math.Abs(d.Y) * e.ActualWidth / 2,
};
cp.Y -= (cp.X - sp.X < sp.Y - cp.Y, isSpin) switch
{
(false, true) => d.Y / Math.Abs(d.X) * e.ActualHeight / 2,
(true, true) => d.Y / Math.Abs(d.X) * e.ActualHeight / 2,
(false, false) => e.ActualHeight / 2,
(true, false) => e.ActualHeight / -2,
};
return cp;
}
}
#endregion