简介

参考Using WPF to Visualize a Graph with Circular Dependencies的基础上写了一个WPF画箭头的库。

效果图如下:

使用的XAML代码如下:

<Window x:Class="WPFArrows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:arrow="clr-namespace:WPFArrows.Arrows"
Title="MainWindow"
Width="525"
Height="350">
<Canvas>
<arrow:ArrowLine Stroke="Black"
StartPoint="10,10"
EndPoint="100,100" />
<arrow:ArrowLineWithText ArrowEnds="Both"
IsTextUp="True"
Stroke="Blue"
StrokeDashArray="5,3"
Text="推导出"
TextAlignment="Center"
StartPoint="110,110"
EndPoint="180,180" />
<arrow:ArrowQuadraticBezier ControlPoint="200,100"
Stroke="Yellow"
StartPoint="250,180"
EndPoint="500,20" />
<arrow:AdjustableArrowBezierCurve ControlPoint1="230,200"
ControlPoint2="300,300"
ShowControl="True"
Stroke="Black"
StartPoint="200,200"
EndPoint="500,300" />
</Canvas>
</Window>

类关系

形状绘制原理

我们常用的形状,如Rectangle、Ellipse、Line、Path等,都继承自Shape类,类关系如下:

(图像摘自<<WPF编程宝典>>)

而具体Shape类是如何绘制形状的呢?我们转到Shape的定义,发现其中有一个虚方法

// 摘要:
// Gets a value that represents the System.Windows.Media.Geometry of the System.Windows.Shapes.Shape.
//
// 返回结果:
// The System.Windows.Media.Geometry of the System.Windows.Shapes.Shape.
protected abstract Geometry DefiningGeometry { get; }

使 用工具(我用的是ILSpy)反汇编Shape类所在的PresentationFramework.dll的源码,就会发现 DefiningGeometry是最重要的方法,在MeasureOverride、ArrangeOverride、OnRender都会间接调用该 方法。

在Line类中,重载后的方法内容如下:

Point startPoint = new Point(this.X1, this.Y1);
Point endPoint = new Point(this.X2, this.Y2);
this._lineGeometry = new LineGeometry(startPoint, endPoint);

即直接返回了一个LineGeometry的新实例。

在其余各类中,原理与Line类中一样。

各个类介绍

ArrowBase

ArrowBase是箭头的基类,继承自Shape类。

在ArrowBase中,重载了DefiningGeometry方法,如下:

protected override Geometry DefiningGeometry
{
get
{
_figureConcrete.StartPoint = StartPoint; //清空具体形状,避免重复添加
_figureConcrete.Segments.Clear();
var segements = FillFigure();
if (segements != null)
{
foreach (var segement in segements)
{
_figureConcrete.Segments.Add(segement);
}
} //绘制开始处的箭头
if ((ArrowEnds & ArrowEnds.Start) == ArrowEnds.Start)
{
CalculateArrow(_figureStart, GetStartArrowEndPoint(), StartPoint);
} // 绘制结束处的箭头
if ((ArrowEnds & ArrowEnds.End) == ArrowEnds.End)
{
CalculateArrow(_figureEnd, GetEndArrowStartPoint(), GetEndArrowEndPoint());
} return _wholeGeometry;
}
}

在其中_figureConcrete是用来保存具体形状的PathFigure,其余几个受保护的方法定义如下:

/// <summary>
/// 获取具体形状的各个组成部分
/// </summary>
protected abstract PathSegmentCollection FillFigure(); /// <summary>
/// 获取开始箭头处的结束点
/// </summary>
/// <returns>开始箭头处的结束点</returns>
protected abstract Point GetStartArrowEndPoint(); /// <summary>
/// 获取结束箭头处的开始点
/// </summary>
/// <returns>结束箭头处的开始点</returns>
protected abstract Point GetEndArrowStartPoint(); /// <summary>
/// 获取结束箭头处的结束点
/// </summary>
/// <returns>结束箭头处的结束点</returns>
protected abstract Point GetEndArrowEndPoint();

在ArrowBase中,一个重要的方法是计算箭头的方法:

/// <summary>
/// 计算两个点之间的有向箭头
/// </summary>
/// <param name="pathfig">箭头所在的形状</param>
/// <param name="startPoint">开始点</param>
/// <param name="endPoint">结束点</param>
/// <returns>计算好的形状</returns>
private void CalculateArrow(PathFigure pathfig, Point startPoint, Point endPoint)
{
var polyseg = pathfig.Segments[] as PolyLineSegment;
if (polyseg != null)
{
var matx = new Matrix();
Vector vect = startPoint - endPoint;
//获取单位向量
vect.Normalize();
vect *= ArrowLength;
//旋转夹角的一半
matx.Rotate(ArrowAngle / );
//计算上半段箭头的点
pathfig.StartPoint = endPoint + vect * matx; polyseg.Points.Clear();
polyseg.Points.Add(endPoint); matx.Rotate(-ArrowAngle);
//计算下半段箭头的点
polyseg.Points.Add(endPoint + vect * matx);
} pathfig.IsClosed = IsArrowClosed;
}

ArrowLine

ArrowLine是带箭头的直线,该类非常简单,重载了ArrowBase中定义的相关方法

/// <summary>
/// 两点之间带箭头的直线
/// </summary>
public class ArrowLine:ArrowBase
{
#region Fields /// <summary>
/// 线段
/// </summary>
private readonly LineSegment _lineSegment=new LineSegment(); #endregion Fields #region Properties /// <summary>
/// 结束点
/// </summary>
public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register(
"EndPoint", typeof(Point), typeof(ArrowLine),
new FrameworkPropertyMetadata(default(Point), FrameworkPropertyMetadataOptions.AffectsMeasure)); /// <summary>
/// 结束点
/// </summary>
public Point EndPoint
{
get { return (Point) GetValue(EndPointProperty); }
set { SetValue(EndPointProperty, value); }
} #endregion Properties #region Protected Methods /// <summary>
/// 填充Figure
/// </summary>
protected override PathSegmentCollection FillFigure()
{
_lineSegment.Point = EndPoint;
return new PathSegmentCollection
{
_lineSegment
};
} /// <summary>
/// 获取开始箭头处的结束点
/// </summary>
/// <returns>开始箭头处的结束点</returns>
protected override Point GetStartArrowEndPoint()
{
return EndPoint;
} /// <summary>
/// 获取结束箭头处的开始点
/// </summary>
/// <returns>结束箭头处的开始点</returns>
protected override Point GetEndArrowStartPoint()
{
return StartPoint;
} /// <summary>
/// 获取结束箭头处的结束点
/// </summary>
/// <returns>结束箭头处的结束点</returns>
protected override Point GetEndArrowEndPoint()
{
return EndPoint;
} #endregion Protected Methods }
}

ArrowLineWithText

ArrowLineWithText,可在直线上方或下方显示文字,继承自ArrowLine。所做的主要工作就是重载渲染事件,使其绘制文字

/// <summary>
/// 重载渲染事件
/// </summary>
/// <param name="drawingContext">绘图上下文</param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext); if (ShowText&&(Text != null))
{
var txt = Text.Trim();
var startPoint = StartPoint;
if (!string.IsNullOrEmpty(txt))
{
var vec = EndPoint - StartPoint;
var angle = GetAngle(StartPoint, EndPoint); //使用旋转变换,使其与线平行
var transform = new RotateTransform(angle) { CenterX = StartPoint.X, CenterY = StartPoint.Y };
drawingContext.PushTransform(transform); var defaultTypeface = new Typeface(SystemFonts.StatusFontFamily, SystemFonts.StatusFontStyle,
SystemFonts.StatusFontWeight, new FontStretch());
var formattedText = new FormattedText(txt, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
defaultTypeface, SystemFonts.StatusFontSize, Brushes.Black)
{
//文本最大宽度为线的宽度
MaxTextWidth = vec.Length,
//设置文本对齐方式
TextAlignment = TextAlignment
}; var offsetY = StrokeThickness;
if (IsTextUp)
{
//计算文本的行数
double textLineCount = formattedText.Width/formattedText.MaxTextWidth;
if (textLineCount < )
{
//怎么也得有一行
textLineCount = ;
}
//计算朝上的偏移
offsetY = -formattedText.Height*textLineCount -StrokeThickness;
}
startPoint = startPoint +new Vector(,offsetY);
drawingContext.DrawText(formattedText, startPoint);
drawingContext.Pop();
}
}

ArrowBezierCurve和ArrowQuadraticBezier

ArrowBezierCurve和ArrowQuadraticBezier代码与ArrowLine基本相似,只是添加了控制点的依赖属性。分别表示贝塞尔曲线和二次贝塞尔曲线,代码从略。

AdjustableArrowQuadraticBezier

AdjustableArrowQuadraticBezier表示可调整的二次贝塞尔曲线。根据鼠标按住控制点(通过重载渲染绘制)的移动来更新控制点,从而起到调整的作用。主要重载了鼠标按下、鼠标移动、鼠标释放、渲染等方法。

/// <summary>
/// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseDown"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
/// </summary>
/// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。此事件数据报告有关按下的鼠标按钮和已处理状态的详细信息。
/// </param>
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e); if (ShowControl&&(e.LeftButton == MouseButtonState.Pressed))
{
CaptureMouse();
Point pt = e.GetPosition(this);
Vector slide = pt - ControlPoint;
//在控制点的圆圈之内
if (slide.Length < EllipseRadius)
{
_isPressedControlPoint = true;
}
}
} /// <summary>
/// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseUp"/> 路由事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
/// </summary>
/// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。事件数据将报告已释放了鼠标按钮。
/// </param>
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
ReleaseMouseCapture();
_isPressedControlPoint = false;
} /// <summary>
/// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseMove"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
/// </summary>
/// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseEventArgs"/>。
/// </param>
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if ((ShowControl)&&(e.LeftButton == MouseButtonState.Pressed) && (_isPressedControlPoint))
{
//更新控制点
ControlPoint = e.GetPosition(this);
}
} /// <summary>
/// 在派生类中重写时,会参与由布局系统控制的呈现操作。调用此方法时,不直接使用此元素的呈现指令,而是将其保留供布局和绘制在以后异步使用。
/// </summary>
/// <param name="drawingContext">特定元素的绘制指令。此上下文是为布局系统提供的。
/// </param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext); if (ShowControl)
{
drawingContext.DrawLine(_linePen, StartPoint, ControlPoint);
drawingContext.DrawEllipse(_ellipseBrush, _ellipsePen, ControlPoint, EllipseRadius, EllipseRadius);
}
}

AdjustableArrowBezierCurve

AdjustableArrowBezierCurve为可调整的贝塞尔曲线,代码与AdjustableArrowQuadraticBezier相似,只是从一个控制点变成两个控制点。代码从略。

代码

博客园:WPFArrows

GitHub:WPFArrows

WPF画箭头的更多相关文章

  1. 菱形实现气泡Bubble,菱形画箭头,菱形画三角形

    菱形实现气泡Bubble,菱形画箭头,菱形画三角形 >>>>>>>>>>>>>>>>>>&g ...

  2. android 使用Canvas画箭头

    public class MyCanvas extends View{        private Canvas myCanvas;    private Paint myPaint=new Pai ...

  3. Swift实时画箭头的实现

    iOS上实现画箭头,如果是指定了坐标点,那是很简单的,但如果需要做到实时绘制,就需要计算一下了 需求: 在白板上,根据手势落下点和移动点,实时绘制一条箭头直线(如下图) 实现代码: /// 获取箭头的 ...

  4. SVG 使用marker画箭头(一)

    一.使用Marker画箭头 1.定义一个箭头的marker引用 <defs> <marker id='markerArrow' markerWidth='13' markerHeig ...

  5. WPF 画线动画效果实现

    原文:WPF 画线动画效果实现 弄了将近三天才搞定的,真是艰辛的实现. 看了很多博客,都太高深了,而且想要实现的功能都太强大了,结果基础部分一直实现不了,郁闷啊~ 千辛万苦终于找到了一个Demo,打开 ...

  6. canvas画箭头demo

    效果图: 代码: <!DOCTYPE html> <html> <title>canvas画箭头demo</title> <body> &l ...

  7. D2D画箭头的例子

    原文:D2D画箭头的例子 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sunnyloves/article/details/50830102 用处 ...

  8. 如何用CorelDRAW画箭头?

    CorelDRAW,简称为cdr,是一款专业的矢量绘图软件,在设计界也是常用的专业设计之一,在日常的设计工作中,我们常常需要绘制一些特殊的图形,比如箭头.很多对cdr不是特别熟练的小伙伴不知道如何用c ...

  9. 在matlab 画箭头

    [转载]在matlab 画箭头 原文地址:在matlab 画箭头作者:纯情小郎君 完整见链接http://www.mathworks.com/matlabcentral/fx_files/14056/ ...

随机推荐

  1. cisco和h3c网络设备中一次性打印全部配置信息

    cisco的是全页打印配置信息的命令: #terminal length 0 #show run 华为和h3c的是: >screen-length 0 temporary >display ...

  2. Delphi32位程序拷贝system32目录中文件解决方法!

    源码下载:http://download.csdn.net/detail/sunylat/9740352 unit Unit1; interface uses Winapi.Windows, Wina ...

  3. css3动画(animation)效果3-正方体合成

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  4. jQuery实现ie浏览器兼容placeholder效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. 【English】20190430

    Network security网络安全[ˈnetwɜːrk] [sɪˈkjʊrəti]  Teradata Generic Security Service 通用安全服务[dʒəˈnerɪk] [s ...

  6. codeforces785E

    http://codeforces.com/contest/785/problem/E 一道经典的求逆序对的题目,可以用树状数组套平衡树解决 平衡树需要支持插入一个数,删除一个数,找比 x 小的数的个 ...

  7. TensorFlow支持GPU配置问题

    目录 Tensorflow-GPU 环境条件 现有硬件 现有软件 硬件要求 软件要求 步骤 0.Visual studio 1.下载安装显卡驱动 2.下载对应版本 CUDA 3.安装配置 cuDNN ...

  8. docker-compose命令

    转自:https://www.jianshu.com/p/2217cfed29d7 先来看一份 docker-compose.yml 文件,不用管这是干嘛的,只是有个格式方便后文解说: version ...

  9. 深度学习(tensorflow) —— 自己数据集读取opencv

    先来看一下我们的目录: dataset1 和creat_dataset.py 属于同一目录 mergeImg1 和mergeImg2 为Dataset1的两子目录(两类为例子)目录中存储图像等文件 核 ...

  10. software installing

    <1>.Apache防火墙配置 firewall-cmd --add-service=http firewall-cmd --add-service=https 防火墙通过80和443端口 ...