简介

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

效果图如下:

使用的XAML代码如下:

  1. <Window x:Class="WPFArrows.MainWindow"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:arrow="clr-namespace:WPFArrows.Arrows"
  5. Title="MainWindow"
  6. Width="525"
  7. Height="350">
  8. <Canvas>
  9. <arrow:ArrowLine Stroke="Black"
  10. StartPoint="10,10"
  11. EndPoint="100,100" />
  12. <arrow:ArrowLineWithText ArrowEnds="Both"
  13. IsTextUp="True"
  14. Stroke="Blue"
  15. StrokeDashArray="5,3"
  16. Text="推导出"
  17. TextAlignment="Center"
  18. StartPoint="110,110"
  19. EndPoint="180,180" />
  20. <arrow:ArrowQuadraticBezier ControlPoint="200,100"
  21. Stroke="Yellow"
  22. StartPoint="250,180"
  23. EndPoint="500,20" />
  24. <arrow:AdjustableArrowBezierCurve ControlPoint1="230,200"
  25. ControlPoint2="300,300"
  26. ShowControl="True"
  27. Stroke="Black"
  28. StartPoint="200,200"
  29. EndPoint="500,300" />
  30. </Canvas>
  31. </Window>

类关系

形状绘制原理

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

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

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

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

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

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

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

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

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

各个类介绍

ArrowBase

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

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

  1. protected override Geometry DefiningGeometry
  2. {
  3. get
  4. {
  5. _figureConcrete.StartPoint = StartPoint;
  6.  
  7. //清空具体形状,避免重复添加
  8. _figureConcrete.Segments.Clear();
  9. var segements = FillFigure();
  10. if (segements != null)
  11. {
  12. foreach (var segement in segements)
  13. {
  14. _figureConcrete.Segments.Add(segement);
  15. }
  16. }
  17.  
  18. //绘制开始处的箭头
  19. if ((ArrowEnds & ArrowEnds.Start) == ArrowEnds.Start)
  20. {
  21. CalculateArrow(_figureStart, GetStartArrowEndPoint(), StartPoint);
  22. }
  23.  
  24. // 绘制结束处的箭头
  25. if ((ArrowEnds & ArrowEnds.End) == ArrowEnds.End)
  26. {
  27. CalculateArrow(_figureEnd, GetEndArrowStartPoint(), GetEndArrowEndPoint());
  28. }
  29.  
  30. return _wholeGeometry;
  31. }
  32. }

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

  1. /// <summary>
  2. /// 获取具体形状的各个组成部分
  3. /// </summary>
  4. protected abstract PathSegmentCollection FillFigure();
  5.  
  6. /// <summary>
  7. /// 获取开始箭头处的结束点
  8. /// </summary>
  9. /// <returns>开始箭头处的结束点</returns>
  10. protected abstract Point GetStartArrowEndPoint();
  11.  
  12. /// <summary>
  13. /// 获取结束箭头处的开始点
  14. /// </summary>
  15. /// <returns>结束箭头处的开始点</returns>
  16. protected abstract Point GetEndArrowStartPoint();
  17.  
  18. /// <summary>
  19. /// 获取结束箭头处的结束点
  20. /// </summary>
  21. /// <returns>结束箭头处的结束点</returns>
  22. protected abstract Point GetEndArrowEndPoint();

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

  1. /// <summary>
  2. /// 计算两个点之间的有向箭头
  3. /// </summary>
  4. /// <param name="pathfig">箭头所在的形状</param>
  5. /// <param name="startPoint">开始点</param>
  6. /// <param name="endPoint">结束点</param>
  7. /// <returns>计算好的形状</returns>
  8. private void CalculateArrow(PathFigure pathfig, Point startPoint, Point endPoint)
  9. {
  10. var polyseg = pathfig.Segments[] as PolyLineSegment;
  11. if (polyseg != null)
  12. {
  13. var matx = new Matrix();
  14. Vector vect = startPoint - endPoint;
  15. //获取单位向量
  16. vect.Normalize();
  17. vect *= ArrowLength;
  18. //旋转夹角的一半
  19. matx.Rotate(ArrowAngle / );
  20. //计算上半段箭头的点
  21. pathfig.StartPoint = endPoint + vect * matx;
  22.  
  23. polyseg.Points.Clear();
  24. polyseg.Points.Add(endPoint);
  25.  
  26. matx.Rotate(-ArrowAngle);
  27. //计算下半段箭头的点
  28. polyseg.Points.Add(endPoint + vect * matx);
  29. }
  30.  
  31. pathfig.IsClosed = IsArrowClosed;
  32. }

ArrowLine

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

  1. /// <summary>
  2. /// 两点之间带箭头的直线
  3. /// </summary>
  4. public class ArrowLine:ArrowBase
  5. {
  6. #region Fields
  7.  
  8. /// <summary>
  9. /// 线段
  10. /// </summary>
  11. private readonly LineSegment _lineSegment=new LineSegment();
  12.  
  13. #endregion Fields
  14.  
  15. #region Properties
  16.  
  17. /// <summary>
  18. /// 结束点
  19. /// </summary>
  20. public static readonly DependencyProperty EndPointProperty = DependencyProperty.Register(
  21. "EndPoint", typeof(Point), typeof(ArrowLine),
  22. new FrameworkPropertyMetadata(default(Point), FrameworkPropertyMetadataOptions.AffectsMeasure));
  23.  
  24. /// <summary>
  25. /// 结束点
  26. /// </summary>
  27. public Point EndPoint
  28. {
  29. get { return (Point) GetValue(EndPointProperty); }
  30. set { SetValue(EndPointProperty, value); }
  31. }
  32.  
  33. #endregion Properties
  34.  
  35. #region Protected Methods
  36.  
  37. /// <summary>
  38. /// 填充Figure
  39. /// </summary>
  40. protected override PathSegmentCollection FillFigure()
  41. {
  42. _lineSegment.Point = EndPoint;
  43. return new PathSegmentCollection
  44. {
  45. _lineSegment
  46. };
  47. }
  48.  
  49. /// <summary>
  50. /// 获取开始箭头处的结束点
  51. /// </summary>
  52. /// <returns>开始箭头处的结束点</returns>
  53. protected override Point GetStartArrowEndPoint()
  54. {
  55. return EndPoint;
  56. }
  57.  
  58. /// <summary>
  59. /// 获取结束箭头处的开始点
  60. /// </summary>
  61. /// <returns>结束箭头处的开始点</returns>
  62. protected override Point GetEndArrowStartPoint()
  63. {
  64. return StartPoint;
  65. }
  66.  
  67. /// <summary>
  68. /// 获取结束箭头处的结束点
  69. /// </summary>
  70. /// <returns>结束箭头处的结束点</returns>
  71. protected override Point GetEndArrowEndPoint()
  72. {
  73. return EndPoint;
  74. }
  75.  
  76. #endregion Protected Methods
  77.  
  78. }
  79. }

ArrowLineWithText

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

  1. /// <summary>
  2. /// 重载渲染事件
  3. /// </summary>
  4. /// <param name="drawingContext">绘图上下文</param>
  5. protected override void OnRender(DrawingContext drawingContext)
  6. {
  7. base.OnRender(drawingContext);
  8.  
  9. if (ShowText&&(Text != null))
  10. {
  11. var txt = Text.Trim();
  12. var startPoint = StartPoint;
  13. if (!string.IsNullOrEmpty(txt))
  14. {
  15. var vec = EndPoint - StartPoint;
  16. var angle = GetAngle(StartPoint, EndPoint);
  17.  
  18. //使用旋转变换,使其与线平行
  19. var transform = new RotateTransform(angle) { CenterX = StartPoint.X, CenterY = StartPoint.Y };
  20. drawingContext.PushTransform(transform);
  21.  
  22. var defaultTypeface = new Typeface(SystemFonts.StatusFontFamily, SystemFonts.StatusFontStyle,
  23. SystemFonts.StatusFontWeight, new FontStretch());
  24. var formattedText = new FormattedText(txt, CultureInfo.CurrentCulture,
  25. FlowDirection.LeftToRight,
  26. defaultTypeface, SystemFonts.StatusFontSize, Brushes.Black)
  27. {
  28. //文本最大宽度为线的宽度
  29. MaxTextWidth = vec.Length,
  30. //设置文本对齐方式
  31. TextAlignment = TextAlignment
  32. };
  33.  
  34. var offsetY = StrokeThickness;
  35. if (IsTextUp)
  36. {
  37. //计算文本的行数
  38. double textLineCount = formattedText.Width/formattedText.MaxTextWidth;
  39. if (textLineCount < )
  40. {
  41. //怎么也得有一行
  42. textLineCount = ;
  43. }
  44. //计算朝上的偏移
  45. offsetY = -formattedText.Height*textLineCount -StrokeThickness;
  46. }
  47. startPoint = startPoint +new Vector(,offsetY);
  48. drawingContext.DrawText(formattedText, startPoint);
  49. drawingContext.Pop();
  50. }
  51. }

ArrowBezierCurve和ArrowQuadraticBezier

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

AdjustableArrowQuadraticBezier

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

  1. /// <summary>
  2. /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseDown"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
  3. /// </summary>
  4. /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。此事件数据报告有关按下的鼠标按钮和已处理状态的详细信息。
  5. /// </param>
  6. protected override void OnMouseDown(MouseButtonEventArgs e)
  7. {
  8. base.OnMouseDown(e);
  9.  
  10. if (ShowControl&&(e.LeftButton == MouseButtonState.Pressed))
  11. {
  12. CaptureMouse();
  13. Point pt = e.GetPosition(this);
  14. Vector slide = pt - ControlPoint;
  15. //在控制点的圆圈之内
  16. if (slide.Length < EllipseRadius)
  17. {
  18. _isPressedControlPoint = true;
  19. }
  20. }
  21. }
  22.  
  23. /// <summary>
  24. /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseUp"/> 路由事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
  25. /// </summary>
  26. /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseButtonEventArgs"/>。事件数据将报告已释放了鼠标按钮。
  27. /// </param>
  28. protected override void OnMouseUp(MouseButtonEventArgs e)
  29. {
  30. base.OnMouseUp(e);
  31. ReleaseMouseCapture();
  32. _isPressedControlPoint = false;
  33. }
  34.  
  35. /// <summary>
  36. /// 当未处理的 <see cref="E:System.Windows.Input.Mouse.MouseMove"/> 附加事件在其路由中到达派生自此类的元素时,调用该方法。实现此方法可为此事件添加类处理。
  37. /// </summary>
  38. /// <param name="e">包含事件数据的 <see cref="T:System.Windows.Input.MouseEventArgs"/>。
  39. /// </param>
  40. protected override void OnMouseMove(MouseEventArgs e)
  41. {
  42. base.OnMouseMove(e);
  43. if ((ShowControl)&&(e.LeftButton == MouseButtonState.Pressed) && (_isPressedControlPoint))
  44. {
  45. //更新控制点
  46. ControlPoint = e.GetPosition(this);
  47. }
  48. }
  49.  
  50. /// <summary>
  51. /// 在派生类中重写时,会参与由布局系统控制的呈现操作。调用此方法时,不直接使用此元素的呈现指令,而是将其保留供布局和绘制在以后异步使用。
  52. /// </summary>
  53. /// <param name="drawingContext">特定元素的绘制指令。此上下文是为布局系统提供的。
  54. /// </param>
  55. protected override void OnRender(DrawingContext drawingContext)
  56. {
  57. base.OnRender(drawingContext);
  58.  
  59. if (ShowControl)
  60. {
  61. drawingContext.DrawLine(_linePen, StartPoint, ControlPoint);
  62. drawingContext.DrawEllipse(_ellipseBrush, _ellipsePen, ControlPoint, EllipseRadius, EllipseRadius);
  63. }
  64. }

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. kali linux之Msf-exploit模块,生成payload

    Exploit模块 Active exploit(主动地向目标机器发送payload并执行,使目标交出shell(反连等)) msf5 > use exploit/windows/smb/pse ...

  2. mysqli扩展库应用---批量执行sql语句

    1, mysqli批量执行sql语句.基本语法: $sqls=”sql1;sql2;………” mysqli::multi_query($sqls); 同一个$sqls要么是增删改语句集合,要么是查询语 ...

  3. [ActionScript 3.0] SharedObject的用法简介

    package com.models { import flash.net.SharedObject; /** * @author * @E-mail * @create 2015-6-12 下午2: ...

  4. Java-代理模式的理解

    引言 设计模式是语言的表达方式,它能让语言轻便而富有内涵.易读却功能强大.代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中 ...

  5. UDF-java获取名字中的姓

    1.使用方法 在hive中加载jar包,调用udf函数 #从名字中获取姓 add jar ${scriptDir}/GetLastNameUDF.jar; create temporary funct ...

  6. jquery全屏幻灯轮播焦点图

    <!--banner s--> <div class="banner"> <div class="hd"> <ul&g ...

  7. day0203 (whil else)

    count = 0while count <= 5 : count += 1 if count == 3:break print("Loop",count) else: pr ...

  8. PHP用curl远程下载图片

    function http_get_data($url){ $ch = curl_init (); curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'GET' ); ...

  9. python高级(一)—— python数据模型(特殊方法)

    本文主要内容 collections.namedtuple __getitem__ 和 __len__ __repr__和__str__ __abs__.__add__和__mul__ __bool_ ...

  10. history.back返回是浏览器错误码:ERR_CACHE_MISS

    解决方法: 如果访问的是php文件中添加:header("Cache-control: private"); 如果使用的是模板引擎(tp5):{php}header("C ...