WPF画箭头

前段时间,因工作需要利用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>

主要类关系如下:



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

我们知道,每个形状都继承自抽象的System.Windows.Shapes.Shape类,如下所示:

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

转到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的新实例。

在ArrowBase中,也重载了这个方法,如下:

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[0] as PolyLineSegment;
            if (polyseg != null)
            {
                var matx = new Matrix();
                Vector vect = startPoint - endPoint;
                //获取单位向量
                vect.Normalize();
                vect *= ArrowLength;
                //旋转夹角的一半
                matx.Rotate(ArrowAngle / 2);
                //计算上半段箭头的点
                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:带箭头的直线,该类非常简单,重载了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,可在直线上方或下方显示文字,继承了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 < 1)
                        {
                            //怎么也得有一行
                            textLineCount = 1;
                        }
                        //计算朝上的偏移
                        offsetY = -formattedText.Height*textLineCount -StrokeThickness;
                    }
                    startPoint = startPoint +new Vector(0,offsetY);
                    drawingContext.DrawText(formattedText, startPoint);
                    drawingContext.Pop();
                }
            }


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



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为可调整的贝塞尔曲线,代码与AdjustableArrowQuadraticBezier相似,只是从一个控制点变成两个控制点



代码请参见WPFArrows

时间: 2024-10-06 15:39:02

WPF画箭头的相关文章

WPF Path 画箭头

原文:WPF Path 画箭头 代码: <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height=

WPF画线问题,几千条以后就有明显的延迟了。

我现在是这么画的,class A { private GeometryGroup _lines; private Path _path; public A() {    _path.Data = _lines; } public Draw() {   LineGeometry line = new LineGeometry(p1, p2);   _lines.Children.Add(line); } }一开始的速度很好,但是线多了以后,就有明显的延迟了. 有什么更快速的方法不? 解决方案 ?

SVG 使用marker画箭头(一)

一.使用Marker画箭头 1.定义一个箭头的marker引用 <defs> <marker id='markerArrow' markerWidth='13' markerHeight='13' refx='2' refy='6' orient='auto'> <path d='M2,2 L2,11 L10,6 L2,2' style='fill:#00ff00' /> </marker> </defs> 注:orient="auto

使用Canvas画箭头

canvas是HTML5的一个新添加的元素,HTML5 canvas是一个原生HTML绘图薄,用于Javascript代码,不使用第三方工具. canvas部分方法列表: 方法 用途 getContext(contextId) 公开在 canvas 上绘图需要的 API.惟一(当前)可用的 contextID 是 2d. height 设置 canvas 的高度.默认值是 150 像素. width 设置 canvas 的宽度.默认值是 300 像素. createLinearGradient(

android 使用Canvas画箭头

public class MyCanvas extends View{        private Canvas myCanvas;    private Paint myPaint=new Paint();        public MyCanvas(Context context) {        super(context);        // TODO Auto-generated constructor stub    }     public MyCanvas(Context

Wpf 画刷

画刷类 1.SolidColorBrush 使用单一的连续颜色绘制区域. 2.LinearGradientBrush 使用简便填充绘制区域,渐变的阴影填充从一种颜色变化到另一种颜色. 3.RadialGradientBrush 使用径向简便填充绘制区域,除了是在圆形模式中从中心点向外部辐射渐变之外,这种画刷和线性检变化刷类似. 4.ImageBrush 使用可以被拉伸.缩放或平铺的图像绘制区域. 5.DrawingBrush 使用一个Drawing对象绘制区域.该对象可以包含已经定义的形状和位图

纯css 画箭头

原理: 把容器的边框设置大一点,容器本身的长宽为0,只设置一边的颜色让其自然形成 箭头 参考文章 http://yuiblog.com/blog/2010/11/22/css-quick-tip-css-arrows-and-shapes-without-markup/ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>

wpf 画刷的分类

System.Windows.Media.Brush最上一层画刷 System.Windows.Media.GradientBrush  线性画刷 ,下层主要有两种画刷 System.Windows.Media.LinearGradientBrush  线性渐变 System.Windows.Media.RadialGradientBrush焦点定义渐变的开变 System.Windows.Media.BitmapCacheBrush  使用缓存的内容绘制区域 System.Windows.Me

画箭头

using (Pen mypen = new Pen(Color.Black, 6)) { mypen.StartCap = LineCap.DiamondAnchor;//设置起始箭头 mypen.EndCap = LineCap.ArrowAnchor;//设置结尾箭头 e.Graphics.DrawLine(mypen, 20f, 20f, 100f, 20f);//绘制直线 };//创建钢笔对象