WPF画箭头
简介
参考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画箭头的更多相关文章
- 菱形实现气泡Bubble,菱形画箭头,菱形画三角形
菱形实现气泡Bubble,菱形画箭头,菱形画三角形 >>>>>>>>>>>>>>>>>>&g ...
- android 使用Canvas画箭头
public class MyCanvas extends View{ private Canvas myCanvas; private Paint myPaint=new Pai ...
- Swift实时画箭头的实现
iOS上实现画箭头,如果是指定了坐标点,那是很简单的,但如果需要做到实时绘制,就需要计算一下了 需求: 在白板上,根据手势落下点和移动点,实时绘制一条箭头直线(如下图) 实现代码: /// 获取箭头的 ...
- SVG 使用marker画箭头(一)
一.使用Marker画箭头 1.定义一个箭头的marker引用 <defs> <marker id='markerArrow' markerWidth='13' markerHeig ...
- WPF 画线动画效果实现
原文:WPF 画线动画效果实现 弄了将近三天才搞定的,真是艰辛的实现. 看了很多博客,都太高深了,而且想要实现的功能都太强大了,结果基础部分一直实现不了,郁闷啊~ 千辛万苦终于找到了一个Demo,打开 ...
- canvas画箭头demo
效果图: 代码: <!DOCTYPE html> <html> <title>canvas画箭头demo</title> <body> &l ...
- D2D画箭头的例子
原文:D2D画箭头的例子 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sunnyloves/article/details/50830102 用处 ...
- 如何用CorelDRAW画箭头?
CorelDRAW,简称为cdr,是一款专业的矢量绘图软件,在设计界也是常用的专业设计之一,在日常的设计工作中,我们常常需要绘制一些特殊的图形,比如箭头.很多对cdr不是特别熟练的小伙伴不知道如何用c ...
- 在matlab 画箭头
[转载]在matlab 画箭头 原文地址:在matlab 画箭头作者:纯情小郎君 完整见链接http://www.mathworks.com/matlabcentral/fx_files/14056/ ...
随机推荐
- kali linux之Msf-exploit模块,生成payload
Exploit模块 Active exploit(主动地向目标机器发送payload并执行,使目标交出shell(反连等)) msf5 > use exploit/windows/smb/pse ...
- mysqli扩展库应用---批量执行sql语句
1, mysqli批量执行sql语句.基本语法: $sqls=”sql1;sql2;………” mysqli::multi_query($sqls); 同一个$sqls要么是增删改语句集合,要么是查询语 ...
- [ActionScript 3.0] SharedObject的用法简介
package com.models { import flash.net.SharedObject; /** * @author * @E-mail * @create 2015-6-12 下午2: ...
- Java-代理模式的理解
引言 设计模式是语言的表达方式,它能让语言轻便而富有内涵.易读却功能强大.代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中 ...
- UDF-java获取名字中的姓
1.使用方法 在hive中加载jar包,调用udf函数 #从名字中获取姓 add jar ${scriptDir}/GetLastNameUDF.jar; create temporary funct ...
- jquery全屏幻灯轮播焦点图
<!--banner s--> <div class="banner"> <div class="hd"> <ul&g ...
- day0203 (whil else)
count = 0while count <= 5 : count += 1 if count == 3:break print("Loop",count) else: pr ...
- PHP用curl远程下载图片
function http_get_data($url){ $ch = curl_init (); curl_setopt ( $ch, CURLOPT_CUSTOMREQUEST, 'GET' ); ...
- python高级(一)—— python数据模型(特殊方法)
本文主要内容 collections.namedtuple __getitem__ 和 __len__ __repr__和__str__ __abs__.__add__和__mul__ __bool_ ...
- history.back返回是浏览器错误码:ERR_CACHE_MISS
解决方法: 如果访问的是php文件中添加:header("Cache-control: private"); 如果使用的是模板引擎(tp5):{php}header("C ...